获取有关 PendingIntent 的 Intent 的详细信息

Posted

技术标签:

【中文标题】获取有关 PendingIntent 的 Intent 的详细信息【英文标题】:Get details about Intent of a PendingIntent 【发布时间】:2012-12-23 21:07:39 【问题描述】:

我想知道是否有可能从我自己没有创建的 PendingIntent 中获取更多信息。更准确地说:是否有可能以某种方式检索PendingIntent 的原始Intent?我不需要执行它,但想打印它的内容。

查看PendingIntent的代码,它显示了一个隐藏的方法:

/** @hide */
public IIntentSender getTarget() 
    return mTarget;

但是这个IIntentSender 也被隐藏了,并且与Binder 和更多IPC(我猜)相关的东西有关。没那么容易。有什么想法吗?

【问题讨论】:

我找了一会儿,没有找到任何东西。你找到了如何打印Intent的内容的解决方案吗? Commonsware 在这里回答了类似的问题 ***.com/a/23725068/2319390 【参考方案1】:

此方法适用于 android 4.2.2 及更高版本:

/**
 * Return the Intent for PendingIntent.
 * Return null in case of some (impossible) errors: see Android source.
 * @throws IllegalStateException in case of something goes wrong.
 * See @link Throwable#getCause() for more details.
 */
public Intent getIntent(PendingIntent pendingIntent) throws IllegalStateException 
    try 
        Method getIntent = PendingIntent.class.getDeclaredMethod("getIntent");
        return (Intent) getIntent.invoke(pendingIntent);
     catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) 
        throw new IllegalStateException(e);
    


以下是 Android 2.3 及更高版本的不完整实现。它需要额外编写一段原生 (JNI) 代码。那么也许它会起作用。更多详情见TODO评论。

/**
 * Return the Intent for PendingIntent.
 * Return null in case of some (impossible) errors: see Android source.
 * @throws IllegalStateException in case of something goes wrong.
 * See @link Throwable#getCause() and @link Throwable#getMessage() for more details.
 */
public Intent getIntent(PendingIntent pendingIntent) throws IllegalStateException 
    try 
        Method getIntent = PendingIntent.class.getDeclaredMethod("getIntent");
        return (Intent) getIntent.invoke(pendingIntent);
     catch (NoSuchMethodException e) 
        return getIntentDeep(pendingIntent);
     catch (InvocationTargetException | IllegalAccessException e) 
        throw new IllegalStateException(e);
    


private Intent getIntentDeep(PendingIntent pendingIntent) throws IllegalStateException 
    try 
        Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
        Method getDefault = activityManagerNativeClass.getDeclaredMethod("getDefault");
        Object defaultManager = getDefault.invoke(null);
        if (defaultManager == null) 
            throw new IllegalStateException("ActivityManagerNative.getDefault() returned null");
        
        Field mTargetField = PendingIntent.class.getDeclaredField("mTarget");
        mTargetField.setAccessible(true);
        Object mTarget = mTargetField.get(pendingIntent);
        if (mTarget == null) 
            throw new IllegalStateException("PendingIntent.mTarget field is null");
        
        String defaultManagerClassName = defaultManager.getClass().getName();
        switch (defaultManagerClassName) 
        case "android.app.ActivityManagerProxy":
            try 
                return getIntentFromProxy(defaultManager, mTarget);
             catch (RemoteException e) 
                // Note from PendingIntent.getIntent(): Should never happen.
                return null;
            
        case "com.android.server.am.ActivityManagerService":
            return getIntentFromService(mTarget);
        default:
            throw new IllegalStateException("Unsupported IActivityManager inheritor: " + defaultManagerClassName);
        
     catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) 
        throw new IllegalStateException(e);
    


private Intent getIntentFromProxy(Object defaultManager, Object sender) throws RemoteException 
    Class<?> activityManagerProxyClass;
    IBinder mRemote;
    int GET_INTENT_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 160;
    String iActivityManagerDescriptor = "android.app.IActivityManager";
    try 
        activityManagerProxyClass = Class.forName("android.app.ActivityManagerProxy");
        Field mRemoteField = activityManagerProxyClass.getDeclaredField("mRemote");
        mRemoteField.setAccessible(true);
        mRemote = (IBinder) mRemoteField.get(defaultManager);
     catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) 
        throw new IllegalStateException(e);
    

    // From ActivityManagerProxy.getIntentForIntentSender()
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(iActivityManagerDescriptor);
    data.writeStrongBinder(((IInterface) sender).asBinder());
    transact(mRemote, data, reply, 0);
    reply.readException();
    Intent res = reply.readInt() != 0
            ? Intent.CREATOR.createFromParcel(reply) : null;
    data.recycle();
    reply.recycle();
    return res;


private boolean transact(IBinder remote, Parcel data, Parcel reply, int i) 
    // TODO: Here must be some native call to convert ((BinderProxy) remote).mObject int
    // to IBinder* native pointer and do some more magic with it.
    // See android_util_Binder.cpp: android_os_BinderProxy_transact() in the Android sources.


private Intent getIntentFromService(Object sender) 
    String pendingIntentRecordClassName = "com.android.server.am.PendingIntentRecord";
    if (!(sender.getClass().getName().equals(pendingIntentRecordClassName))) 
        return null;
    
    try 
        Class<?> pendingIntentRecordClass = Class.forName(pendingIntentRecordClassName);
        Field keyField = pendingIntentRecordClass.getDeclaredField("key");
        Object key = keyField.get(sender);
        Class<?> keyClass = Class.forName("com.android.server.am.PendingIntentRecord$Key");
        Field requestIntentField = keyClass.getDeclaredField("requestIntent");
        requestIntentField.setAccessible(true);
        Intent requestIntent = (Intent) requestIntentField.get(key);
        return requestIntent != null ? new Intent(requestIntent) : null;
     catch (ClassCastException e) 
     catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) 
        throw new IllegalStateException(e);
    
    return null;

【讨论】:

该方法标有@hide,表示无法正常调用。不过,反射可能会起作用,除非它需要系统级权限或其他东西。 当然我们这里需要反射,因为没有正常的方法可以做到这一点。而且我认为不需要一些额外的权限。无论如何,这很容易检查。唯一明显的问题是这个调用从 Android 4.2.2 开始可用,所以不能在以前的版本上使用。并且可能(但不太可能)它可能会在未来的版本中消失。 我已经测试过这个方法,看起来不错。对由 Google+ 登录创建的PendingIntents 工作正常。对于 4.2.2 之前的设备有类似的解决方案会很好,因为此代码在生产中使用不安全。我认为公共方法应该捕获所有异常,这意味着无法获取Intent 并返回null 而不是使应用程序崩溃。 此代码应该可以在 Android 2.3 及更高版本上运行。此代码中引发的唯一异常 - IllegalStateException 包含有关问题的详细信息。如果你不需要这些细节,没问题,抓住它并返回 null。 好吧,它不适用于早期版本,因为 GET_INTENT_FOR_INTENT_SENDER_TRANSACTION 常量及其处理是在 Android 4.2.2 中添加的。【参考方案2】:

如果您希望 Intent 用于 Robolectric 中的测试目的,请使用 ShadowPendingIntent

public static Intent getIntent(PendingIntent pendingIntent) 
    return ((ShadowPendingIntent) ShadowExtractor.extract(pendingIntent))
        .getSavedIntent();

【讨论】:

【参考方案3】:

您可以使用IntentSender,它可以从PendingIntent.getIntentSender() 获得。 IntentSender的函数getCreatorPackage(),将给出创建这个PendingIntent的包。

【讨论】:

是的,我明白这一点,但我需要原始 Intent。这不会给我那个。 或者更重要的是,您如何从 IntentSender 对象的 Intent 中获取 Extras?

以上是关于获取有关 PendingIntent 的 Intent 的详细信息的主要内容,如果未能解决你的问题,请参考以下文章

使用PendingIntent获取位置始终返回Null

PendingIntent

从通知中获取 PendingIntent 结果到目标活动中

如何从 PendingIntent 中获取 Intent

如何获取和取消 PendingIntent?

PendingIntent 可以获取一个类作为参数吗?