Android 组件通信中有哪些不为人知的细节?,在一家公司干多长时间跳槽才合适

#ActivityManagerService.java

public int startActivity(IApplicationThread caller, String callingPackage,

Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,

int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {

return mActivityTaskManager.startActivity(caller, callingPackage, intent, resolvedType,

resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions);

}

重点关注两个参数:IApplicationThread caller和Intent intent。

Intent 我们很熟悉了,指明了要启动哪个Activity。

IApplicationThread 是个接口,里面定义了很多方法,列举部分如:

#IApplicationThread.java

{

//回调静态广播

void scheduleReceiver(in Intent intent, in ActivityInfo info,

in CompatibilityInfo compatInfo,

int resultCode, in String data, in Bundle extras, boolean sync,

int sendingUser, int processState);

@UnsupportedAppUsage

//回调创建服务

void scheduleCreateService(IBinder token, in ServiceInfo info,

in CompatibilityInfo compatInfo, int processState);

@UnsupportedAppUsage

//回调停止服务

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跳转的功能。

三、Service 与AMS 交互

=============================================================================

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 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 方法。

四、Broadcast 与AMS 交互

===============================================================================

静态注册

广播有两种注册方式:静态与动态。

先分析静态注册:

在AndroidManifest.xml 里声明广播,并指定接收的Action和处理该Action的类。

如下:

在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 与AMS 交互

=====================================================================================

ContentProvider 顾名思义:内容提供者。

以典型的手机通讯录为例,通讯录如何提供给其它进程使用其数据呢?根据前面的经验,通讯录需要暴露一个对外的接口,外部程序想使用通讯录那得拿到这个暴露出来的接口。

接下来看看如何实现自定义的ContentProvider:

声明ContentProvider

#AndroidManifest.xml

<provider

android:authorities=“com.fish.AMSTargetProvider”

android:name=“.ams.AMSTargetProvider”>

der>

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 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)

ContentObserver 本身没有跨进程的能力,因此需要将它包装起来:

#ContentObserver.java

public IContentObserver getContentObserver() {

synchronized (mLock) {

if (mTransport == null) {

mTransport = new Transport(this);

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

进阶学习视频

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

wyQo-1712358232396)]

[外链图片转存中…(img-PB4V3sZI-1712358232396)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

进阶学习视频

[外链图片转存中…(img-iTwd1hqm-1712358232396)]

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-sP20Sakg-1712358232397)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值