zoukankan      html  css  js  c++  java
  • Launcher 启动 app 和 apk 资源的加载流程 (App 换肤原理 2)

    Android开发中我们的apk资源是如何被加载到的,我们知道当我们点击桌面launcher 的图标拉起我们的app显示我们的页面资源。这个是如何被加载的呢?今天就引出两个看源码的问题 基于 8.0 源码:

    不同版本源码实现略有差异。

    Launcher启动app的流程?
    Apk资源是如何被加载到页面上的?
    1. Launcher 启动app的流程

    App启动流程

    首先我们回顾一下App启动流程,还不了解的可以看我之前写的这篇文章

    • 首先是点击App图标,此时是运行在Launcher进程,通过ActivityManagerServiceBinder IPC的形式向system_server进程发起startActivity的请求
    • system_server进程接收到请求后,通过Process.start方法向zygote进程发送创建进程的请求
    • zygote进程fork出新的子进程,即App进程
    • 然后进入ActivityThread.main方法中,这时运行在App进程中,通过ActivityManagerServiceBinder IPC的形式向system_server进程发起attachApplication请求
    • system_server接收到请求后,进行一些列准备工作后,再通过Binder IPC向App进程发送scheduleLaunchActivity请求
    • App进程binder线程(ApplicationThread)收到请求后,通过Handler向主线程发送LAUNCH_ACTIVITY消息
    • 主线程收到Message后,通过反射机制创建目标Activity,并回调Activity的onCreate

    首先我们看第四步,attachApplication方法,最终会调用thread#bindApplication然后调用ActivityThread#handleBindApplication方法,我们从这个方法开始看


    做过 launcher app 的同学都知道,我们 launcher 是如何在点击app的时候启动我们的app的,之前做过 launcher 的app,所以对着一点个人还是比较熟悉的,接下来就引出app的启动。

    其实launcher启动app很简单,我们知道我们每个 app 在manifast.xml中都会配置一个主activity/根activity。

    <activity android:name=".MainActivity">
    <intent-filter>
    /**
    * Activity Action: Start as a main entry point, does not expect to
    * receive data.
    */
    <action android:name="android.intent.action.MAIN" />

    /**
    * Should be displayed in the top-level launcher.
    */
    <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    </activity>
    可以知道我们配置了 action 和 category ,这两个标签其实就标志我们app启动的地一个 activity 页面。所以在launcher中我们要想启动我们的activity 就是要根据这两个参数 去start activity。

    Intent intent = new Intent(Intent.ACTION_MAIN);
    intent.addCategory(Intent.CATEGORY_LAUNCHER);
    intent.setComponent(new ComponentName(pkg,cls));
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    startActivity(mContext, intent, null);
    根据actiion 和 category 启动我们具体的 那个 package 下的主activity。(包括launcher的查询app显示到页面,也是根据查询action 和 category 属性来确定有那些 app list列表的)

    接下来就要根据 startActivity(mContext, intent, null); 入手整个 app 启动流程。

    @Override
    public void startActivity(Intent intent, Bundle options) {
    warnIfCallingFromSystemProcess();

    // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
    // generally not allowed, except if the caller specifies the task id the activity should
    // be launched in.

    mMainThread.getInstrumentation().execStartActivity(
    getOuterContext(), mMainThread.getApplicationThread(), null,
    (Activity) null, intent, -1, options);
    }
    跟着流程走,其中涉及到的其他类,不要太关心,防止迷失~,然后就触发了 Instrumentation 这个类,

    public ActivityResult execStartActivity(
    Context who, IBinder contextThread, IBinder token, Activity target,
    Intent intent, int requestCode, Bundle options) {

    ..........

    try {
    intent.migrateExtraStreamToClipData();
    intent.prepareToLeaveProcess(who);
    切换进程
    int result = ActivityManager.getService()
    .startActivity(whoThread, who.getBasePackageName(), intent,
    intent.resolveTypeIfNeeded(who.getContentResolver()),
    token, target != null ? target.mEmbeddedID : null,
    requestCode, 0, null, options);
    checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
    throw new RuntimeException("Failure from system", e);
    }
    return null;
    }
    在这里通过 ActivityManager.getService() 拿到 AMS 的服务,调用了AMS 的startActivity() 方法。追到 AMS 中

    @Override
    public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
    Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
    int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
    enforceNotIsolatedCaller("startActivity");
    userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
    userId, false, ALLOW_FULL_ONLY, "startActivity", null);
    // TODO: Switch to user app stacks here.
    return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
    resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
    profilerInfo, null, null, bOptions, false, userId, null, "startActivityAsUser");
    }
    中间的调用过程就用UML 图代替了,防止代码太多,到ApplicationThread就是我们比较熟悉的流程了。

    在第8步有两个方法需要注意

    //先从task里面去取,如果有之前启动过就走当前task里面的APPlicationThread
    //的relauncheractivity,反之走下面
    next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
    mService.isNextTransitionForward(), resumeAnimOptions);

    //如果task里面没有,上面方法抛出异常走 ,重新创建新的activity
    mStackSupervisor.startSpecificActivityLocked(next, true, false);
    源码是这样的:

    走到 10 的时候回去判断当前进程是否存活,存活就走realstartactivity方法,否则就start process 进程。

    1. 走realStartActivityLocked方法回去调用ApplicationThread的scheduleLauncherActivity方法,后面就是我们熟悉的启动activity流程了。

    2. 走startProcessLocked方法,创建进程(后面文章单独分析 // TODO :)

    其实如果我们首次从launcher点开我们app 应该先走 start process 创建进程,然后调用ActivityThread的main()方法。

    到这里整个应用进程就被创建了,接下来就是进程解析 apk 加载资源。

    2. 加载 apk 资源
    通过 问题一我们知道了,初始化新的进程后会 触发 ActivityThread 的 main 方法:


    public final class ActivityThread {

    ......

    public static void main(String[] args) {
    .....
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    .....

    }

    private void attach(boolean system) {
    .......
    final IActivityManager mgr = ActivityManagerNative.getDefault();
    try {
    mgr.attachApplication(mAppThread);
    } catch (RemoteException ex) {
    // Ignore
    }

    ....
    }
    }
    通过main方法调用了 attach 方法,然后获取 AMS 调用 attachApplication(IApplicationThread var1) 方法:

    @Override
    public final void attachApplication(IApplicationThread thread) {
    synchronized (this) {
    //获取applicationThread的进程id(也就是淘宝应用进程)
    int callingPid = Binder.getCallingPid();
    final long origId = Binder.clearCallingIdentity();
    attachApplicationLocked(thread, callingPid);
    Binder.restoreCallingIdentity(origId);
    }
    }


    private final boolean attachApplicationLocked(IApplicationThread thread,
    int pid) {

    // Find the application record that is being attached... either via
    // the pid if we are running in multiple processes, or just pull the
    // next app record if we are emulating process with anonymous threads.
    ProcessRecord app;
    if (pid != MY_PID && pid >= 0) {
    synchronized (mPidsSelfLocked) {
    app = mPidsSelfLocked.get(pid);
    }
    } else {
    app = null;
    }

    //因为进程由AMS启动,所以在AMS中一定会有ProcessRecord(进程记录)
    //如果没有ProcessRecord,则需要杀死该进程并退出
    if (app == null) {
    ``````
    return false;
    }

    // If this application record is still attached to a previous
    // process, clean it up now.
    if (app.thread != null) {
    //如果从ProcessRecord中获取的IApplicationThread不为空,则需要处理该IApplicationThread
    //因为有可能此Pid为复用,旧应用进程刚释放,内部IApplicationThread尚未清空,
    //同时新进程又刚好使用了此Pid
    handleAppDiedLocked(app, true, true);
    }


    //创建死亡代理(进程kill后通知AMS)
    AppDeathRecipient adr = new AppDeathRecipient(app, pid, thread);

    //进程注册成功,移除超时通知
    mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);

    ``````
    try {
    //******绑定Application******
    thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
    profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
    app.instrumentationUiAutomationConnection, testMode,
    mBinderTransactionTrackingEnabled, enableTrackAllocation,
    isRestrictedBackupMode || !normalMode, app.persistent,
    new Configuration(mConfiguration), app.compat,
    getCommonServicesLocked(app.isolated),
    mCoreSettingsObserver.getCoreSettingsLocked());

    updateLruProcessLocked(app, false, null);
    } catch (Exception e) {

    ``````
    //bindApplication失败后,重启进程
    startProcessLocked(app, "bind fail", processName);
    return false;
    }

    try {
    //******启动Activity(启动应用MainActivity)******
    if (mStackSupervisor.attachApplicationLocked(app)) {
    didSomething = true;//didSomething表示是否有启动四大组件
    }
    } catch (Exception e) {
    badApp = true;
    }

    ``````
    //绑定service和Broadcast的Application


    if (badApp) {
    //如果以上组件启动出错,则需要杀死进程并移除记录
    app.kill("error during init", true);
    handleAppDiedLocked(app, false, true);
    return false;
    }

    //如果以上没有启动任何组件,那么didSomething为false
    if (!didSomething) {
    //调整进程的oom_adj值, oom_adj相当于一种优先级
    //如果应用进程没有运行任何组件,那么当内存出现不足时,该进程是最先被系统“杀死”
    updateOomAdjLocked();
    }
    return true;
    }
    两个重要的方法:

    thread.bindApplication(…) : 绑定Application到ActivityThread
    mStackSupervisor.attachApplicationLocked(app) : 启动Activity(7.0前为mMainStack.realStartActivityLocked())
    通过AIDL接口IApplicationThread远程通知到ApplicationThreadNative的onTransact方法指定执行BIND_APPLICATION_TRANSACTION方法,而ActivityThread的内部类ApplicationThread实现ApplicationThreadNative抽象类bindApplication(),由于bindApplication()是运行在服务端Binder的线程池中,所以bindApplication会通过Handler发送BIND_APPLICATION的Message消息,ActivityThread中handler接受到之后调用handleBindApplication。

    public final class ActivityThread {


    private class ApplicationThread extends ApplicationThreadNative {
    .....
    public final void bindApplication(String processName, ApplicationInfo appInfo,
    List<ProviderInfo> providers, ComponentName instrumentationName,
    ProfilerInfo profilerInfo, Bundle instrumentationArgs,
    IInstrumentationWatcher instrumentationWatcher,
    IUiAutomationConnection instrumentationUiConnection, int debugMode,
    boolean enableOpenGlTrace, boolean trackAllocation, boolean isRestrictedBackupMode,
    boolean persistent, Configuration config, CompatibilityInfo compatInfo,
    Map<String, IBinder> services, Bundle coreSettings) {

    .........

    AppBindData data = new AppBindData();
    ......
    sendMessage(H.BIND_APPLICATION, data);
    }
    .....
    }

    private class H extends Handler {
    .....
    public void handleMessage(Message msg) {
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
    switch (msg.what) {
    case LAUNCH_ACTIVITY: {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

    r.packageInfo = getPackageInfoNoCheck(
    r.activityInfo.applicationInfo, r.compatInfo);
    handleLaunchActivity(r, null);
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    } break;
    ....
    case BIND_APPLICATION:
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
    AppBindData data = (AppBindData)msg.obj;
    handleBindApplication(data);
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    break;

    .....

    }

    ....
    }
    }
    初始化ContextImpl加载Apk资源

    private void handleBindApplication(AppBindData data) {
    //..........
    // Context初始化(ContextImpl)
    final ContextImpl appContext = ContextImpl.createAppContext(this/*ActivityThread*/, data.info/*LoadedApk*/);
    //........
    在这里创建ContextImpl对象:

    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
    if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
    null);
    context.setResources(packageInfo.getResources());
    return context;
    }
    可以看出在这里 new 了一个 ContextImp 并且把resources对象传入 : context.setResources(packageInfo.getResources());

    LoadApk中获得 Resources对象

    public Resources getResources() {
    if (mResources == null) {
    final String[] splitPaths;
    try {
    splitPaths = getSplitPaths(null);
    } catch (NameNotFoundException e) {
    // This should never fail.
    throw new AssertionError("null split not found");
    }

    mResources = ResourcesManager.getInstance().getResources(null, mResDir,
    splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
    Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
    getClassLoader());
    }
    return mResources;
    }
     ResourcesManager中:

    public @Nullable Resources getResources(@Nullable IBinder activityToken,
    @Nullable String resDir,
    @Nullable String[] splitResDirs,
    @Nullable String[] overlayDirs,
    @Nullable String[] libDirs,
    int displayId,
    @Nullable Configuration overrideConfig,
    @NonNull CompatibilityInfo compatInfo,
    @Nullable ClassLoader classLoader) {
    try {
    Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
    final ResourcesKey key = new ResourcesKey(
    resDir,
    splitResDirs,
    overlayDirs,
    libDirs,
    displayId,
    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
    compatInfo);
    classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
    return getOrCreateResources(activityToken, key, classLoader);
    } finally {
    Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    }
    }
    通过封装 key 来创建 对象:

    private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
    @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
    synchronized (this) {

    // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
    ResourcesImpl resourcesImpl = createResourcesImpl(key);
    if (resourcesImpl == null) {
    return null;
    }

    ................

    resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
    return resources;
    }
    }
    最终要的一个方法 createResourcesImpl();创建 AssetManager:

    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
    final AssetManager assets = createAssetManager(key);
    if (assets == null) {
    return null;
    }

    final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
    return impl;
    }
    在创建 ResourcesImpl对象之前先创建了 AssetManager 对象,这里是非常重要的信息,在这里会把 apk 路径传给 AssetManager对象: 

    protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
    AssetManager assets = new AssetManager();

    // resDir can be null if the 'android' package is creating a new Resources object.
    // This is fine, since each AssetManager automatically loads the 'android' package
    // already.
    if (key.mResDir != null) {
    if (assets.addAssetPath(key.mResDir) == 0) {
    Log.e(TAG, "failed to add asset path " + key.mResDir);
    return null;
    }
    }

    ......................

    return assets;
    }
    在这里把 resDir 传给了 addAssetPath(key.mResDir) 这个方法对我们换肤起到至关重要作用,用这个方法可以替换我们的 apk 资源。再往下就是一些 native 源码jni 方面的,篇幅 太长,这块可以去参考 :
    ————————————————
    版权声明:本文为CSDN博主「Wang Rain」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/WangRain1/article/details/103080641

    Always Believe Something Beauitful Will Be Happen
  • 相关阅读:
    采用C/C++语言如何实现复数抽象数据类型Complex
    单链表的插入伪算法和用C语言创建单链表,并遍历
    SQL多列查询最大值
    修改网页页面显示内容
    成为智者的四个敌人——唐望
    从0到1:构建强大且易用的规则引擎(转)
    身份采集、活体检测、人脸比对...旷视是如何做FaceID的? (转)
    drools -Rete算法(转)
    风控决策引擎系统的搭建设计指南(转载)
    [上市与资本运作] 【干货】创业公司天使轮、A轮、B轮……IPO融资时如何分配股权?(转载)
  • 原文地址:https://www.cnblogs.com/Oude/p/13237550.html
Copyright © 2011-2022 走看看