首页 > 代码库 > Binders 与 Window Tokens(窗口令牌)

Binders 与 Window Tokens(窗口令牌)

原文地址:http://www.androiddesignpatterns.com/2013/07/binders-window-tokens.html

安卓的一项核心设计思想是希望能提供一个不需要依赖中央检验部门来检验程序请求的开放平台.为此,Android使用了程序沙盒和Linux的进程分离来防止程序以无法控制和不安全的方式访问系统内部或者其他程序.这种架构是开发者与使用者共同选择的:既不需要额外的保护来防止恶意程序,同时系统要自动的处理好所有事情.
在很长一段时间我对这种安全机制都是知其然却不知其所以然,但是最近我开始好奇了起来.到底是什么机制能防止我们欺骗系统,例如使我们不能通过一个程序来隐藏另一个程序的界面?简单来说,Android的核心系统服务如何既高效又安全地响应第三方应用程序的请求的呢?

出乎我意料的是,这所有问题的答案都异常简单:是Binder.Binders是Android系统架构的基石;他们为开发者抽象了IPC底层的许多细节,使程序能简单地与系统服务或者其他远程服务组件对话.而且Binder还有许多其他很cool的功能,所以它在系统中被广泛的使用,贯穿整个系统,使底层framework能解决好安全问题.这篇文章将详细解读这些功能中的其中一种,Binder 令牌(tokens).

Binder 令牌(Tokens)

binder有一个有趣的属性:无论跨越了多少个进程,每一个实例在整个系统中都只维护同一个唯一的ID.这是由Binder内核驱动通过分析每个Binder的transaction后为其分配的一个32位int值.为了保证Java中的"=="操作能适用于Binder的唯一性与跨进程的对象身份约定,Binder对象的引用的判断相对于其他对象有一些不同.准确的讲,每个Binder对象引用都是由以下两者之一分配的:
  1. 同一个进程中指向一个Binder对象的虚拟内存地址,或
  2. 一个唯一的32位句柄(由Binder内核驱动分配的)指向不同进程中Binder的虚拟内存地址.
Binder内核驱动中为每一个Binder都维护了一个本地地址与远程Binder句柄的Map(反之亦然),然后为每一个Binder对象的引用都分配了一个合适的值,保证他们在远程进程中也能同样的按预期工作.
Binder的唯一对象ID规则使它们有了一项特殊的用途:共享,安全访问令牌(shared,security access token)(文档中明确提示了Binder可以被这样使用"你可以简单的实例化一个原始的Binder对象直接用于跨进程共享").Binders是全局唯一的,这意味着你生成了一个,没有其他任何人能生成另一个完全相同的.因此,系统的frameword广泛地使用Binder令牌来保证跨进程交互的安全性:一个客户端可以通过创建一个Binder对象作为令牌与服务进程共享,并且服务端能使用它验证来自客户端的请求排除所有伪造请求.

我们来通过一个简单的例子看看它是如何工作的.假如一个程序向PowerManager发出了一个请求,请求获得屏幕唤醒锁(后面会释放掉):

/**
 * An example activity that acquires a wake lock in onCreate() 
 * and releases it in onDestroy().
 */
public class MyActivity extends Activity {

  private PowerManager.WakeLock wakeLock;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "My Tag");
    wakeLock.acquire();
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();

    wakeLock.release();
  }
}

阅读PowerManager的源码可以帮助我们理解底层发生了 一些什么(源代码经过精简):
/**
 * The interface that applications use to talk to the global power manager
 * system service.
 *
 * @see frameworks/base/core/java/android/os/PowerManager.java
 */
public final class PowerManager {

  // Our handle on the global power manager service.
  private final IPowerManager mService;

  public WakeLock newWakeLock(int levelAndFlags, String tag) {
    return new WakeLock(levelAndFlags, tag);
  }

  public final class WakeLock {
    private final IBinder mToken;
    private final int mFlags;
    private final String mTag;

    WakeLock(int flags, String tag) {
      // Create a token that uniquely identifies this wake lock.
      mToken = new Binder();
      mFlags = flags;
      mTag = tag;
    }

    public void acquire() {
      // Send the power manager service a request to acquire a wake
      // lock for the application. Include the token as part of the 
      // request so that the power manager service can validate the
      // application‘s identity when it requests to release the wake
      // lock later on.
      mService.acquireWakeLock(mToken, mFlags, mTag);
    }

    public void release() {
      // Send the power manager service a request to release the
      // wake lock associated with ‘mToken‘.
      mService.releaseWakeLock(mToken);
    }
  }
}

发生了一些什么呢?我们来一步步阅读代码:
  1. 客户端程序在onCreate()中请求了PowerManager类的一个实例.PowerManager类提供给客户端程序一个与运行在系统服务进程中的负责设备电源状态(例如决定屏幕亮度,检查设备是否插入dock等)的全局PowerManagerService对话的接口.
  2. 客户端程序在onCreate()中创建并获得了一个唤醒锁(wake lock).PowerManager发送了一个WakeLock的创建的唯一的Binder令牌作为acquire()请求的参数.当PowerManagerService接收到了请求,它将接收到的令牌安全的保存起来,并强制设备保持唤醒状态,直到...
  3. 客户程序在onDestroy()中释放了唤醒锁.PowerManager发送了WakeLock创建的唯一的Binder令牌作为请求的参数.当PowerManagerService接收到请求,它将这个令牌与它保存的所有令牌进行比较,如果发现相同的就释放唤醒锁.这额外的一步"确认步骤"是保证PowerManagerService不会被别的应用程序欺骗而释放唤醒锁的重要安全措施.
由于它们的对象唯一性,Binder令牌在系统中被广泛(随意选择一个文件 frameworks/base/services/java/com/android/server,就能发现它使用了几种形式的Binder令牌.另一个很cool的例子涉及状态栏,通知管理,和系统UI.具体的说,StatusBarManagerService维护一个全局的Binder令牌到通知的Map.当NotificationManagerService发出一个请求使状态栏管理器添加一个通知到状态栏,状态栏管理器就生成一个唯一的Binder令牌同时传递到通知管理器和系统UI.这样三方都知道了通知的Binder令牌,任何通知的改变(例如通知管理器取消了一条通知或者系统UI侦测到用户将一条通知划掉)都会首先通过状态管理器.这使的3个系统服务能更易保持同步:状态栏管理器能集中控制所有的当前正在显示的通知而不用与系统UI和通知管理器互相交互.)用于安全保障.或许是在所有framework中最有意思的例子就是"窗口令牌"(window token)了,接下来我们来讨论它.

窗口令牌(Window Tokens)

如果你曾翻阅过官方关于View类的文档,你可能会困惑于getWindowToken()方法不知道它的意义.顾名思义,一个窗口令牌是一种特殊的Binder令牌,窗口管理器用于唯一标识系统中的一个窗口.窗口令牌对于安全十分重要,因为它们会阻止恶意程序出现在其他程序界面之上.窗口管理器通过要求应用程序将它们的窗口令牌作为添加或者删除一个窗口的参数传递过来(拥有 android.permission.SYSTEM_ALERT_WINDOW 权限的程序,即"在其他程序界面上绘制"权限,对此规则是个例外.Facebook Messenger和DicePlayer是两个常用的需求这个权限的程序,并且用此在后台服务中在其他程序的界面上添加窗口).如果令牌不匹配,窗口管理器拒绝请求并抛出一个BadTokenException,如果没有了窗口令牌,这必要的身份建议步骤就不可能实现,窗口管理器就没办法防止防止恶意程序了.

通过这一点,你可能想知道自己在实际开发中何时需要一个窗口令牌.这里有几个例子:

  • 当一个应用程序第一次启动,ActivityMangerService(这是一个全局系统服务,运行于系统服务进程中,负责启动和管理新的组件,例如Activities和Services,同时它还涉及维护OOM调整,被用于内核低内存时的处理,权限,任务管理等)创建了一个特殊的窗口令牌称之为应用程序窗口令牌(application window token),它唯一地标识应用程序顶层容器的窗口(你可以通过调用getApplicationWindowToken()获得一个引用).Activity管理器将这个令牌同时传给应用程序和窗口管理器,然后每次应用程序想要添加窗口的时候都要向窗口管理器传入这个令牌.这确保了应用程序与窗口管理器的安全交互(因为使得别的程序不可能向顶层添加窗口),同时也让Activity管理器向窗口管理器直接发送请求变得更简单.例如,Activity管理器可以说,"隐藏这个令牌的所有窗口",然后窗口管理器就能正确的选择出需要关闭的窗口了.
  • 实现自己定制桌面程序(Launchers)的开发者可以与动态壁纸窗口交互,通过调用sendWallpaperCommand(IBinder windowToken, String action, int x, int y, int z, Bundle extras)使之直接位于后面.为了确保除了桌面没有别的应用程序能够与动态壁纸交互,framework需要开发者传入一个窗口令牌作为该方法的第一个参数.如果窗口令牌与当前位于壁纸前的Activity的窗口不匹配,这条命令将被忽略并且打印出一条警告.
  • 应用程序能通过调用hideSoftInputFromWindow(IBinder windowToken, int flags)方法请求InputMethodManager隐藏软键盘,但是必须提供一个窗口令牌作为参数,如果令牌不匹配当前接受输入的窗口令牌,InputMethodManager会拒绝请求,这确保恶意程序无法强制关闭由其他程序打开的软键盘.
  • 手动添加新窗口到屏幕上的应用程序(例如,使用addView(View,WindowManager.LayoutParams)方法)可能需要通过设置WindowManager.LayoutParams.token属性来指定他们应用程序的窗口令牌.一般正常的程序都不太会这样做,因为在使用getWindowManager()方法时返回的WindowManager对象已经自动为你设置好了令牌值.也就是说,如果在以后你遇到需要从后台服务向屏幕添加一个窗口这种情况时,你要知道你应该手动设置你程序的窗口令牌才能成功.

总结

尽管它们的存在对于大部分开发者来说是屏蔽掉的,但是Binder令牌在系统被广泛应用于安全性.Android是一个大规模的分布式协作系统依赖于Binder对象在整个设备上的所有进程中都是唯一的。Binder令牌是整个framework相互协作的背后驱动力,如果没有他们保证应用程序进程间的安全交互,整个系统将会很难运作.