---恢复内容开始---
昨天好不容易把Binder的使用方式搞清楚了,今天看代码的时候又被轰炸地体无完肤。最主要的问题是,在系统service和client的交互中,Binder使用的太频繁了,一次调用可能伴随着多个Binder的多次传递,不搞清楚这些Binder的来源和具体作用,往往就无法深刻理解交互是如何进行的。另外,对于Binder子类的命名方式又非常繁杂,这间接导致了代码阅读理解的困难。今天决定花些时间好好梳理下交互过程中Binder的具体使用,在此记录。
在开始前,先声明下个人对Binder的作用的理解,归纳起来就两句话:服务端创建的Binder用于给客户端调用服务端方法;客户端创建的Binder用于服务端执行异步回调。这里的服务端时指系统service,客户端是指调用系统service的其他类。
接下去以startActivity(intent)做例子,说明下在客户端执行startActivity(intent)后,Binder的传递和使用。
Intent intent=new Intent(ActivityA.this,ActivityB.class); startActivity(intent);
很简单的一个例子,一步步跟踪下去,首先是startActivity
@Override public void startActivity(Intent intent) { startActivity(intent, null); }
继续跟踪
@Override public void startActivity(Intent intent, Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } }
到startActivityForResult
public void startActivityForResult(Intent intent, int requestCode, Bundle options) { if (mParent == null) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); }
mInstrumentation对象是在创建activity时被赋值的,它实际来源于ActivityThread,每个程序只有一个。在此我们不关心它的其他功能,只需看它和服务端交互的部分。
我们注意到参数有很多个,其中包含两个Binder:mMainThread.getApplicationThread()和mToken。
mMainThread就是ActivityThread对象,每个程序只有一个,getApplicationThread获取的是一个ApplicationThread,该对象继承于ApplicationThreadNative类,而ApplicationThreadNative类继承于Binder。它是在Activity实例化时直接赋值的。注意,虽然ApplicationThread取名为Thread,但它和Thread没有关系,本质是一个Binder。
final ApplicationThread mAppThread = new ApplicationThread();
记住,ActivityThread是作为客户端存在的,所以该Binder的作用是传递给服务端,让服务端做回调。
那mToken呢?继续跟踪
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config) { attachBaseContext(context); ..... mInstrumentation = instr; mToken = token; mIdent = ident;
mToken是在attach方法里赋值,该方法在ActivityThread内调用
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); ... activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config);
原来该token来源于ActivityClientRecord,向上查询performLaunchActiviity调用。
有两个方法调用了performLaunchActiviity
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... Activity a = performLaunchActivity(r, customIntent);
public final Activity startActivityNow(Activity parent, String id, Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, Activity.NonConfigurationInstances lastNonConfigurationInstances) { ActivityClientRecord r = new ActivityClientRecord(); r.token = token; ... return performLaunchActivity(r, null); }
startActivityNow里创建了ActivityClientRecord对象,但是它的toke也是参数传入的,而handleLaunchActivity,它实际上是通过一个handler被异步调用的,真正调用它的地方在scheduleLaunchActivity
// we use token to identify this activity without having to send the // activity itself back to the activity manager. (matters more with ipc) public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, int procState, Bundle state, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { updateProcessState(procState, false); ActivityClientRecord r = new ActivityClientRecord(); r.token = token; ... queueOrSendMessage(H.LAUNCH_ACTIVITY, r); }
在queueOrSendMessage方法中压入了handler的消息队列,最后执行到handleLaunchActivity
但是不管是在scheduleRelaunchActivity还是startActivityNow中,token都是作为参数传入的,事实上,scheduleRelaunchActivity和startActivityNow方法都是服务端通过传递过去的ApplicationThread回调的,也就是说token对象其实是在服务端创建的。这里的服务端便是指ActivityManagerService服务。
接下去看mInstrumentation的调用
try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(); int result = ActivityManagerNative.getDefault() .startActivityAsUser(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, null, options, user.getIdentifier()); checkStartActivityResult(result, intent); } catch (RemoteException e) { }
ActivityManagerNative.getDefault()获取了一个服务端ActivityManagerService的引用并且转为了一个IActivityManager接口。如果熟悉aidl使用的话会知道Android习惯把Binder实现分为三部分:IService、IServiec.Stub、IService.Stub.Proxy,其中IService是个接口,定义了这个服务能提供哪些方法,IService.Stub是个继承了Binder与IService接口的抽象类,是具体服务的父类,服务类继承Stub类并且实现IService接口的方法,作为服务端存在。IService.Stub.Proxy则只继承了IService,当客户端获取了服务端的Binder引用后,会转为Proxy类,这样客户端就能直接调用服务端方法了。在这里,ActivityManagerNative实际上就等价于IActivityManager.Stub,不过由于IActivityManager方法太多,Android将其独立了出来。
直接查看ActivityManagerService的startActivityAsUser方法
@Override public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) { enforceNotIsolatedCaller("startActivity"); userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "startActivity", null); // TODO: Switch to user app stacks here. return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, null, null, options, userId); }
留意caller和resultTo两个Binder,caller是客户端ActivityThread创建的Binder,用于服务端回调,接下去就能看到,但是resultTo是服务端自己创建的,这里要拿来干什么呢?而且服务端究竟是何时创建每个Activity对应的Binder的?继续看下去。
mStackSupervisor对象是一个ActivityStackSupervisor类,我们不关心该类的作用,只看它的startActivityMayWait方法
final int startActivityMayWait(IApplicationThread caller, int callingUid, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config, Bundle options, int userId) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); } boolean componentSpecified = intent.getComponent() != null; // Don‘t modify the client‘s object! intent = new Intent(intent); // Collect information about the target of the Intent. ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, profileFile, profileFd, userId); synchronized (mService) { int callingPid; if (callingUid >= 0) { callingPid = -1; } else if (caller == null) { callingPid = Binder.getCallingPid(); callingUid = Binder.getCallingUid(); } else { callingPid = callingUid = -1; } final ActivityStack stack = getFocusedStack(); stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0; if (DEBUG_CONFIGURATION) Slog.v(TAG, "Starting activity when config will change = " + stack.mConfigWillChange); final long origId = Binder.clearCallingIdentity(); if (aInfo != null && (aInfo.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { // This may be a heavy-weight process! Check to see if we already // have another, different heavy-weight process running. if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { if (mService.mHeavyWeightProcess != null && (mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid || !mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) { int realCallingUid = callingUid; if (caller != null) { ProcessRecord callerApp = mService.getRecordForAppLocked(caller); if (callerApp != null) { realCallingUid = callerApp.info.uid; } else { Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + callingPid + ") when starting: " + intent.toString()); ActivityOptions.abort(options); return ActivityManager.START_PERMISSION_DENIED; } } IIntentSender target = mService.getIntentSenderLocked( ActivityManager.INTENT_SENDER_ACTIVITY, "android", realCallingUid, userId, null, null, 0, new Intent[] { intent }, new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, null); Intent newIntent = new Intent(); if (requestCode >= 0) { // Caller is requesting a result. newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); } newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, new IntentSender(target)); if (mService.mHeavyWeightProcess.activities.size() > 0) { ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0); newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, hist.packageName); newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, hist.task.taskId); } newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, aInfo.packageName); newIntent.setFlags(intent.getFlags()); newIntent.setClassName("android", HeavyWeightSwitcherActivity.class.getName()); intent = newIntent; resolvedType = null; caller = null; callingUid = Binder.getCallingUid(); callingPid = Binder.getCallingPid(); componentSpecified = true; try { ResolveInfo rInfo = AppGlobals.getPackageManager().resolveIntent( intent, null, PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS, userId); aInfo = rInfo != null ? rInfo.activityInfo : null; aInfo = mService.getActivityInfoForUser(aInfo, userId); } catch (RemoteException e) { aInfo = null; } } } } int res = startActivityLocked(caller, intent, resolvedType, aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, startFlags, options, componentSpecified, null); if (stack.mConfigWillChange) { // If the caller also wants to switch to a new configuration, // do so now. This allows a clean switch, as we are waiting // for the current activity to pause (so we will not destroy // it), and have not yet started the next activity. mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, "updateConfiguration()"); stack.mConfigWillChange = false; if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating to new configuration after starting activity."); mService.updateConfigurationLocked(config, null, false, false); } Binder.restoreCallingIdentity(origId); if (outResult != null) { outResult.result = res; if (res == ActivityManager.START_SUCCESS) { mWaitingActivityLaunched.add(outResult); do { try { mService.wait(); } catch (InterruptedException e) { } } while (!outResult.timeout && outResult.who == null); } else if (res == ActivityManager.START_TASK_TO_FRONT) { ActivityRecord r = stack.topRunningActivityLocked(null); if (r.nowVisible) { outResult.timeout = false; outResult.who = new ComponentName(r.info.packageName, r.info.name); outResult.totalTime = 0; outResult.thisTime = 0; } else { outResult.thisTime = SystemClock.uptimeMillis(); mWaitingActivityVisible.add(outResult); do { try { mService.wait(); } catch (InterruptedException e) { } } while (!outResult.timeout && outResult.who == null); } } } return res; } }