Android 组件通信中有哪些不为人知的细节?,面试题分享
void scheduleStopService(IBinder token);
//回调绑定 Application
void bindApplication(in String packageName, in ApplicationInfo info,
in ProviderInfoList providerList, in ComponentName testName,
in ProfilerInfo profilerInfo, in Bundle testArguments,
IInstrumentationWatcher testWatcher, IUiAutomationConnection uiAutomationConnection,
int debugMode, boolean enableBinderTracking, boolean trackAllocation,
boolean restrictedBackupMode, boolean persistent, in Configuration config,
in CompatibilityInfo compatInfo, in Map services,
in Bundle coreSettings, in String buildSerial, in AutofillOptions autofillOptions,
in ContentCaptureOptions contentCaptureOptions, in long[] disabledCompatChanges);
//回调 Service onStartCommand
void scheduleServiceArgs(IBinder token, in ParceledListSlice args);
//回调绑定服务
void scheduleBindService(IBinder token,
in Intent intent, boolean rebind, int processState);
@UnsupportedAppUsage
//回调解绑服务
void scheduleUnbindService(IBinder token,
in Intent intent);
//回调 Activity 生命周期相关
void scheduleTransaction(in ClientTransaction transaction);
}
而 ActivityThread.java 里实现了该接口(Android 8.0 之后使用了 AIDL 定义,此处以此为例分析)。
#ActivityThread.java
private class ApplicationThread extends IApplicationThread.Stub {
...
//实现了 IApplicationThread 接口里定义的方法
}
IApplicationThread 有啥用呢?我们调用 AMS 方法后,有些方法并没有返回值或者仅仅只返回 int,比如 startActivity(xx),那么我们的进程如何接收 Activity 的生命周期的回调呢,不仅 Activity 的生命周期回调,还有 Service 等的回调。这个时候就得依靠 IApplicationThread 接口回调了。
因此,当调用 AMS 方法的时候,传入 IApplicationThread 实例,当 AMS 完成某个动作后通过 IApplicationThread 回调给应用进程。
实际上,AMS 里针对每个应用进程只保存了一个 IApplicationThread 实例,而第一次传递给 AMS 是在进程启动的时候:
#ActivityThread.java
private void attach(boolean system, long startSeq) {
...
if (!system) {
//获取 AMS 引用
final IActivityManager mgr = ActivityManager.getService();
try {
//传入 IApplicationThread 实例给 AMS
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
...
}
此时 AMS 将 IApplicationThread 实例保存下来,后续有应用进程调用 AMS 方法时,也会传入 IApplicationThread 实例,AMS 通过查找是否存在实例,存在就直接拿出来用。
当调用 AMS startActivity()后,AMS 将检测目标 Activity 所在进程是否存活,若没有则启动进程,若有则将 Activity 移动到栈顶,而后处理之前被移出栈顶的 Activity,做完了这些操作之后需要通知涉及到的各个应用进程,而这个通知是通过 IApplicationThread 回调的。
假设 AMSActivity、AMSTargetActivity 处在同一个进程里,此时 AMS 会回调 IApplicationThread 里的 scheduleTransaction()方法:
#ApplicationThread
public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
ActivityThread.this.scheduleTransaction(transaction);
}
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
//最终调用到 ActivityThread 里的 handleLaunchActivity()等方法
//进而回调目标 Activity 的 onCreate/onPause/onResume 等方法
mTransactionExecutor.execute(transaction);
break;
}
#ClientTransactionHandler.java
void scheduleTransaction(ClientTransaction transaction) {
transaction.preExecute(this);
sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}
可以看出,从 AMS 回调后,通过 Handler 发送到主线程执行了,在 Handler 处理 Message 时,会调用到 Activity 的 onCreate()/onResume()/等方法。
这也是为什么我们经常说的 Activity 的重写方法都在主线程执行的原因了。
由上可知,当应用进程发起 startActivity 动作后,AMS 管理了目标 Activity 的生命周期,我们仅仅只需要在应用进程里重写目标 Activity 对应方法,并在里面处理相应的逻辑,即可实现一次 Activity 跳转的功能。
=============================================================================
start 启动 Service
Intent intent = new Intent(AMSActivity.this, AMSTargetService.class);
startService(intent);
调用栈如下:
ContextWrapper.startService-->
ContextImpl.startServiceCommon
-->ActivityManager.getService().startService(xx)
依然是先拿到 AMS 接口,进而调用 startService(xx):
#ContextImpl.java
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, boolean requireForeground, String callingPackage,
String callingFeatureId, int userId)
假设 AMSActivity 与 AMSTargetService 在同一进程。
AMS 收到 startService 请求后,寻找目标 Service,若是 Service 还没有创建,最终则通过回调 IApplicationThread 方法 scheduleCreateService(xx)、scheduleServiceArgs(xx),这些方法处理如下:
#ActivityThread.java
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
...
//切换到主线程执行
sendMessage(H.CREATE_SERVICE, s);
}
public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) {
List<ServiceStartArgs> list = args.getList();
for (int i = 0; i < list.size(); i++) {
...
//切换到主线程执行
sendMessage(H.SERVICE_ARGS, s);
}
}
public void handleMessage(Message msg) {
switch (msg.what) {
case CREATE_SERVICE:
//1、该方法里创建 Service 实例
//2、回调 Service onCreate 方法
handleCreateService((CreateServiceData)msg.obj);
break;
case SERVICE_ARGS:
//回调 onStartCommand 方法
handleServiceArgs((ServiceArgsData)msg.obj);
break;
}
}
与 Activity 类似,Service 生命周期回调最终切换到主线程执行。
这也就是我们常说的为什么 Service 里不能执行耗时任务,否则容易发生 ANR,因为回调方法在主线程执行的。
bind 启动 Service
假设 AMSActivity 与 AMSTargetService 不在同一进程,以 AMSActivity 所在进程为客户端,AMSTargetService 所在进程 为服务端,有如下图:
前提是客户端、服务端已经绑定到 AMS 里了(传递 IApplicationThread)。
1、客户端发起绑定请求。
2、AMS 寻找目标 Service,通过 IApplicationThread 回调 scheduleCreateService 创建服务端 Service,并通过 scheduleBindService 绑定服务。
3、服务端创建、绑定成功并传递 IBinder 给 AMS。
4、AMS 收到 IBinder 引用通过 IServiceConnection 回调 connected 方法告诉客户端服务绑定成功,也就是回调客户端绑定时注册的 ServiceConnection.onServiceConnected 方法。
===============================================================================
静态注册
广播有两种注册方式:静态与动态。
先分析静态注册:
在 AndroidManifest.xml 里声明广播,并指定接收的 Action 和处理该 Action 的类。
如下:
<receiver android:name=".ams.AMSTargetBroadcast">
<intent-filter>
<action android:name="my_action"></action>
</intent-filter>
</receiver>
在 AMSActivity 里 发送广播,发送广播调用栈如下:
ContextWrapper.sendBroadcast-->ContextImpl.sendBroadcast
-->ActivityManager.getService().broadcastIntent(xx)
依然是先拿到 AMS 接口,进而调用 broadcastIntent(xx):
#ContextImpl.java
int broadcastIntent(in IApplicationThread caller, in Intent intent,
in String resolvedType, in IIntentReceiver resultTo, int resultCode,
in String resultData, in Bundle map, in String[] requiredPermissions,
int appOp, in Bundle options, boolean serialized, boolean sticky, int userId);
假设 AMSActivity 与 AMSTargetBroadcast 在同一进程。
AMS 收到 broadcastIntent 请求后,转发给 BroadcastQueue 处理,若广播接收器是静态注册,则通过 回调 IApplicationThread scheduleReceiver 方法。后面的处理过程与 Activity/Service 类似。
AMSActivity 所在进程收到 scheduleReceiver 回调后,切换到主线程,然后反射构造 AMSTargetBroadcast 实例,并调用 onReceive(xx)方法,而 onReceive(xx) 方法里正是我们重写的接收广播后的处理逻辑。
因此,onReceive(xx) 方法也是在主线程执行的,不能执行耗时操作,否则容易发生 ANR。
动态注册
动态注册如下:
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AMSTargetBroadcast.MY_ACTION);
registerReceiver(new AMSTargetBroadcast(), intentFilter);
registerReceiver(xx)最终调用到:
#ContextImpl.java
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context, int flags) {
//AIDL 实现
IIntentReceiver rd = null;
if (receiver != null) {
if (mPackageInfo != null && context != null) {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
//构造接收器回调
rd = mPackageInfo.getReceiverDispatcher(
receiver, context, scheduler,
mMainThread.getInstrumentation(), true);
} else {
...
}
}
try {
//向 AMS 注册
final Intent intent = ActivityManager.getService().registerReceiver(
mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
broadcastPermission, userId, flags);
...
return intent;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
在向 AMS 注册前,构造了 IIntentReceiver 对象,该接口是 AIDL 声明的,也就是说向 AMS 注册了个回调接口,当 AMS 接收到发送广播的请求后,发现是动态注册的,于是通过回调 IIntentReceiver 接口的 performReceive(xx)方法,进而调用 BroadcastReceiver 里的 onReceive(xx)方法,貌似没有看到切换到主线程执行呢?看看 IIntentReceiver performReceive(xx)的处理:
#LoadedApk.java
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
...
//mActivityThread.post 切换到主线程执行
if (intent == null || !mActivityThread.post(args.getRunnable())) {
...
}
}
public final Runnable getRunnable() {
return () -> {
final BroadcastReceiver receiver = mReceiver;
...
try {
...
//注册时传入的 BroadcastReceiver
receiver.onReceive(mContext, intent);
} catch (Exception e) {
...
}
};
}
很明显,此处是切换到主线程执行了。
可以看出,不管是静态注册,抑或是动态注册,最终都是在主线程回调 onReceive(xx)方法。
Broadcast 与 AMS 交互图如下:
由上还可以总结出:
1、每发送一次广播,都需要走两次 IPC(请求 AMS/AMS 回调),因此若是广播只在同一进程里发送/接收,没必要使用广播,推荐使用本地广播:LocalBroadcastManager。
2、若广播是静态注册的,AMS 每次回调时都会反射重新创建 BroadcastReceiver 实例,因此在广播发送/接收很频繁的情况下不建议使用静态注册,推荐使用动态注册。
=====================================================================================
ContentProvider 顾名思义:内容提供者。
以典型的手机通讯录为例,通讯录如何提供给其它进程使用其数据呢?根据前面的经验,通讯录需要暴露一个对外的接口,外部程序想使用通讯录那得拿到这个暴露出来的接口。
接下来看看如何实现自定义的 ContentProvider:
声明 ContentProvider
#AndroidManifest.xml
<provider
android:authorities="com.fish.AMSTargetProvider"
android:name=".ams.AMSTargetProvider">
</provider>de
r>
public class AMSTargetProvider extends ContentProvider {
public static String AUTHORITY = "com.fish.AMSTargetProvider";
private static int MATCH_CODE = 1000;
private static UriMatcher uriMatcher;
...
//重写增删改查方法,处理具体的逻辑
}
接口有了,处理逻辑也有了,那么需要对外展示自己的能力。当然这个过程不需要我们完成,系统自动处理了。
发布 ContentProvider 到 AMS
还记得之前我们说的应用进程启动后,会向 AMS 绑定(注册)IApplicationThread,绑定成功后会通过 IApplicationThread 的 bindApplication(xx)回调应用进程,进而切换到主线程执行 handleBindApplication(xx),在该方法里会反射创建 Application 实例,然后处理 AndroidManifest.xml 里声明的 Provider。
调用栈如下:
ActivityThread.handleBindApplication-->ActivityThread.installContentProviders
-->ActivityManager.getService().publishContentProviders(xx)
在 ActivityThread.installContentProviders 里会实例化 ContentProvider,并将其引用保存到 Map 里,最后调用 AMS publishContentProviders(xx) 传递给 AMS。
#AMS
public final void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers)
ContentProviderHolder 实现了 Parcelable 接口,因此其可以跨进程传递,其内部持有 IContentProvider 成员变量。此处你可能有疑惑,IContentProvider 并不能跨进程传递!实际上 IContentProvider 只是个接口,它的具体实现类是:ContentProvider.Transport,其声明如下:
#ContentProvider.java
class Transport extends ContentProviderNative {
...
}
#ContentProviderNative.java
abstract public class ContentProviderNative extends Binder implements IContentProvider {
...
}
因此 ContentProvider.Transport 可以跨进程传递。
获取 ContentProvider
AMS 里已经存留了 ContentProvider 相关信息,当有进程需要使用 ContentProvider 时,以插入数据为例,使用方法如下:
{
ContentValues contentValues = new ContentValues();
getContentResolver().insert(uri, contentValues);
}
调用 insert(xx)方法时,调用栈如下:
ContentResolver.insert-->ContentResolver.acquireProvider-->ApplicationContentResolver.acquireProvider
-->ActivityThread.acquireProvider-->ActivityThread.acquireExistingProvider
-->ActivityManager.getService().getContentProvider(xx)
其中,ActivityThread.acquireExistingProvider 会先查询本地是否缓存有 ContentProvider,若有则直接返回(对应 AMSTargetProvider 实例与调用者同一个进程内),此时直接返回 ContentProvider 实例,无需再查询 AMS 了。
若是不同的进程,则需要通过 AMS 查询 ContentProvider:
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String callingPackage, String name, int userId,
boolean stable)
若是 AMS 之前缓存了 ContentProvider,则直接返回,否则查看目标进程是否存活,不存活拉起来,再拿 ContentProvider。
有个点需要注意的是:
拿到远程的 ContentProvider 也会缓存的,只是在 ContentResolver 里的 insert()/delete()/query() 方法的最后都会调用 releaseProvider(xx)释放缓存。因此对于远程的 ContentProvider,每次都是通过 AMS 重新获取的。
用图表示 ContentProvider 与 AMS 的交互:
ContentProvider 数据变更
当 ContentProvider 数据变更时,需要通知给监听者,而其它进程想要监听变化,则需要注册观察者。
注册观察者如下:
Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
Uri uri = Uri.parse("content://" + AMSTargetProvider.AUTHORITY + "/ams");
getContentResolver().registerContentObserver(uri, false, new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
});
registerContentObserver 调用栈如下:
ContentResolver.registerContentObserver
-->ContentResolver.registerContentObserver
-->getContentService().registerContentObserver(xx)
评论