返回

Android PendingIntent 与 IntentSender 详解:使用场景与权限控制

Android

IntentSender 和 PendingIntent:啥时候用哪个?

Android 开发文档里有 PendingIntentIntentSender 这俩玩意儿,但看完之后,总感觉有点迷糊:这俩到底有啥区别?啥时候该用哪个?很多看起来几乎一模一样,让人摸不着头脑。

先简单回顾一下,PendingIntent 文档上是这么说的:

对 Intent 以及要执行的目标操作的。 此类的实例是通过(...) 创建的;返回的对象可以交给其他应用程序,以便它们稍后可以代表你执行你描述的操作。

IntentSender 文档又是这样说的:

对 Intent 以及要执行的目标操作的描述。 返回的对象可以交给其他应用程序,以便它们可以代表你执行你描述的操作。

它俩都是 Parcelable,都能让接收者通过 sendsendIntent (签名都差不多) 来触发。

更让人困惑的是,要创建 IntentSender,你还得先有个 PendingIntent。这... 那我直接用 PendingIntent 不就完了,费这劲干啥?别急,咱们好好说道说道。

根源:权限与信任

问题的核心在于 "权限" 和 "信任" 两个词。PendingIntent, 简单理解, 就是你把一个 Intent 包起来,并把 你自己 的身份和权限也打包进去了。而IntentSender 本身不包含创建者的身份和权限,它更像是一个“启动 Intent 的凭证”。

更准确地说,拿到你创建的PendingIntent的应用,可以使用你应用的权限去执行intent。IntentSender 则不同, 它携带的是发布这个 IntentSender 的应用的权限。 这两个"应用"可能不同, 这就是区别的关键。

场景分析与解决方案

下面,通过几个典型场景,来详细解释一下:

1. PendingIntent: 大多数情况下的首选

如果你只是想让别的 App 在未来某个时间点执行一个操作(比如:点通知栏启动 Activity,设置定时闹钟),并且这个操作使用你自己的 App 权限执行就足够了,那么直接用 PendingIntent 就行了。

原理:

当你创建 PendingIntent 时,系统会记录下你的应用程序的身份(UID/PID)和权限。 当其他应用程序使用这个 PendingIntent 触发 Intent 时,系统会暂时 恢复 你的应用程序的身份和权限来执行这个 Intent。

示例代码:

// 创建一个 PendingIntent,用于稍后启动 MyActivity
Intent intent = new Intent(this, MyActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);

// ... 将 pendingIntent 交给其他 App,例如通过 NotificationManager ...

// 其他 App 稍后可以这样触发:
try {
    pendingIntent.send(); // 以创建该PendingIntent应用的身份,启动MyActivity.
} catch (PendingIntent.CanceledException e) {
    e.printStackTrace();
}

注意事项:

  • PendingIntent.FLAG_IMMUTABLE (或 PendingIntent.FLAG_MUTABLE) 标志位很重要,决定了 PendingIntent 是否可以被修改. 为了安全,推荐使用 FLAG_IMMUTABLE

2. IntentSender: 系统服务的青睐

IntentSender 的主要使用场景,是跟系统服务打交道的时候。 最典型例子:你使用 LocationManagerrequestLocationUpdates() 方法获取定位更新。

假设你在向LocationManager请求位置更新. 你把一个PendingIntent传给requestLocationUpdates(), 当位置更新可用时, LocationManager就会执行你的 PendingIntent。

那LocationManager会用的权限来执行你给的 PendingIntent 呢? 显然是用你的应用的权限,不然LocationManager还能随便用其他应用的权限乱搞吗?这不安全.

所以PendingIntent打包了创建它的应用的身份。

但是,有些情况下, 系统服务会希望, 其他应用使用系统服务的权限, 而不是用创建 PendingIntent 的那个应用的权限, 来做事情。

那如何做到这一点? 这就要用到 IntentSender.

原理:
当一个系统服务 (如 LocationManager, AccountManager) 需要返回一个可以执行操作的对象给其他 App 时,它 不能 直接返回一个 PendingIntent

原因: 前面说了, PendingIntent 带的是创建者的权限,如果系统服务返回PendingIntent, 那不就意味着任何拿到这个PendingIntent 的 App 都可以用系统服务的权限干活了?那还得了!

系统服务会使用PendingIntent.getIntentSender()获取对应的IntentSender,然后把IntentSender交给其他应用。其他应用通过IntentSender.send() 触发操作的时候,会使用 发布 IntentSender 的系统服务 的身份,而不是创建原始 PendingIntent 的那个 App 的身份。

举例:

 // 假设 LocationManager 内部是这样工作的(只是举例,实际代码可能更复杂):

// LocationManager 接收到一个请求,要求获取定位更新
public void requestLocationUpdates(..., PendingIntent pendingIntent) {

    // 将 PendingIntent 转换成 IntentSender
    IntentSender intentSender = pendingIntent.getIntentSender();

    // ... 把 intentSender 存起来 ...

    // 当有新的位置信息时,使用intentSender, 以LocationManager自己的权限来执行。
    //  intentSender 代表LocationManager本身,
    //  而不是一开始调用requestLocationUpdates的应用。

    try{
          intentSender.sendIntent(context, 0, intent, null, null);
    }catch (IntentSender.SendIntentException e){

    }

}

LocationManager调用sendIntent,将会导致获得LocationManager的权限去运行。
这可以防止恶意应用通过滥用PendingIntent来获得超出他们原本应有的权限.

3. 你应该使用IntentSender的情景.

上面的LocationManager的例子解释了系统是如何使用IntentSender的. 但是你也可以使用IntentSender.

如果你实现了一个服务, 该服务将返回一个 PendingIntent给调用者. 你的服务或许会出于安全原因选择返回一个 IntentSender,而不是直接返回 PendingIntent, 这迫使你的服务的调用者以你的服务的权限来运行intent, 而不是创建 PendingIntent的调用者的应用的权限.

简而言之: 当你想要授予其他应用, 使用"发布IntentSender 的应用"的权限去执行操作,而不是使用调用者的权限的时候 ,使用IntentSender 。 这是一种更严格的权限控制手段.

进阶:createIntentSender()

还有一个相对少见的情况:Activity.createIntentSender()。 这个方法允许你创建一个 IntentSender,当这个 IntentSender 被触发时,Intent 会以类似 startIntentSenderForResult() 的方式启动,这意味着你的 Activity 可以收到结果回调(就像调用 startActivityForResult() 一样)。

这个功能主要用于需要更细粒度控制 intent 启动流程的场景。 比如,Google Play 服务的安装/更新流程,就会使用这个方法。

示例(Google Play 服务安装):


//检测是否缺少 Google Play
int code= GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this.getApplicationContext());

    if(code !=ConnectionResult.SUCCESS){
          Dialog dlg = GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_SIGN_IN);
          dlg.show();

          //内部通过 intentSender 实现
          //intentSender 将会引导用户到应用商店去下载安装Google Play。
    }

// ...假设这里有一个通过用户点击按钮 启动 Google Play 安装流程...

说明:

Google Play服务在检查到设备缺少必要组件时,会显示一个对话框,引导用户去应用商店安装。 这个安装流程, 需要使用调用者的Context(而非Play Service自己),这就是用 IntentSender 的另一个原因. 框架内部会通过startIntentSenderForResult() 去启动.

总结一下

  • 大多数时候, 如果只需要简单地让别的应用帮你执行个操作, 选 PendingIntent 就好.
  • 如果涉及到系统服务 (location、account 等) 需要把"操作能力" 授权出去,而且需要精确控制权限,那就用系统服务创建的 IntentSender.
  • 如果你构建了一个服务, 这个服务创建了PendingIntent,为了严格限制接收服务的应用必须使用你的服务的权限而不是他们自己的, 可以把PendingIntent转化成IntentSender,再发给别的应用。
  • 需要通过 startIntentSenderForResult() 启动并获取结果回调时, 使用 Activity.createIntentSender()