继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android App启动时Apk资源加载机制源码分析

吃鸡游戏
关注TA
已关注
手记 465
粉丝 55
获赞 339

在Andorid开发中我们要设置文字或图片显示,都直接通过Api一步调用就完成了,不仅是我们工程下res资源以及系统自带的framwork资源也可以,那这些资源打包成Apk之后是如何被系统加载从而显示出来的呢。

这里我要从Apk安装之后启动流程开始讲起,在桌面应用click事件之后 
会通过Binder机制通知ActivityManagerService启动,具体由ActivityManagerNative.getDefault返回ActivityManagerService的远程接口代理对象ActivityManagerProxy,通知ActivityManagerService执行startActivity进入启动流程. 该Service会进行些获取目标内容,检查权限之后,再检查对应进程的ProcessRecord是否存在了.如果ProcessRecord是null, ActivityManagerService会创建新的进程来实例化目标activity从而把App启动了起来,详细的App启动流程可以参考老罗的文章:http://blog.csdn.net/luoshengyang/article/details/6689748

进程的创建及绑定Application

  • 创建进程 
    ActivityManagerService调用startProcessLocked()方法来创建新的进程, 该方法会通过前面讲到的socket通道传递参数给Zygote进程. Zygote孵化自身, 并调用ZygoteInit.main()方法来实例化ActivityThread对象并最终返回新进程的pid。代码如下

 public final class ActivityManagerService extends ActivityManagerNative  
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {  

    ......  

    private final void startProcessLocked(ProcessRecord app,  
                String hostingType, String hostingNameStr) {  

        ......  

        try {  
            int uid = app.info.uid;  
            int[] gids = null;  
            try {  
                gids = mContext.getPackageManager().getPackageGids(  
                    app.info.packageName);  
            } catch (PackageManager.NameNotFoundException e) {  
                ......  
            }  


            int debugFlags = 0;  

            ......  

            int pid = Process.start("android.app.ActivityThread",  
                mSimpleProcessManagement ? app.processName : null, uid, uid,  
                gids, debugFlags, null);  

            ......  

        } catch (RuntimeException e) {  

            ......  

        }  
    }  

    ......  

}

这里主要是调用Process.start接口来创建一个新的进程,新的进程会导入android.app.ActivityThread类,并且执行它的main函数。

  • 绑定Application 
    这个是通过调用上文的ActivityThread对象中调用handleBindApplication方法完成的. 我们可以通过Thread.dumpStack()来查看流程:

 at java.lang.Thread.dumpStack(Thread.java:505)06-21 17:42:18.448 8620-8620/cn.terminal.egame.myphone W/System.err:     at cn.terminal.egame.myphone.MyApplication.onCreate(MyApplication.java:13)06-21 17:42:18.448 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1015)06-21 17:42:18.448 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4793)06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.app.ActivityThread.access$1600(ActivityThread.java:165)06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1437)06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.os.Looper.loop(Looper.java:150)06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5621)06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at java.lang.reflect.Method.invoke(Native Method)06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684)123456789101112

那么ActivityThread的handleBindApplication又是谁来调用的(这里就简单介绍下,不能偏离主题太远,深入太远,最后不知所云,没有了方向):

在ActivityThread的main方法中会执行ActivityThread对象的attach方法,会调用用了ActivityManagerService的远程接口本地代理对象ActivityManagerProxy的attachApplication函数通知attachApplication,并传入参数是mAppThread,这是ApplicationThread类型的Binder对象,用来接受ActivityManagerService的进程间消息。

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
            }

            ....
    }
}

ActivityManagerService在接受到attachApplication函数调用远程消息之后,一系列处理之后,会有两个重要Binder通信,一个就是通过传来的参数Binder参数ApplicationThread来通知ActivityThread中mAppThread远程中调用bindApplication(),另一个是scheduleLaunchActivity。在Ams中收到attachApplication时代码如下:

AMS @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;
    }

从上午可以看到在attachApplicationLocked中有两个比较重要的方法函数:

thread.bindApplication(…) : 绑定Application到ActivityThread
mStackSupervisor.attachApplicationLocked(app) : 启动Activity(7.0前为mMainStack.realStartActivityLocked())123

bindApplication

通过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资源

在handleBindApplication的具体实现中就可以看到资源加载:

private void handleBindApplication(AppBindData data) {      //..........
      // Context初始化(ContextImpl)
      final ContextImpl appContext = ContextImpl.createAppContext(this/*ActivityThread*/, data.info/*LoadedApk*/);      //........
  }

最终会调用到ContextImpl这个构造函数:

 private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {            //.......
            // LoadedApk赋值 
            mPackageInfo = packageInfo;
            mResourcesManager = ResourcesManager.getInstance();            // resources初始化:通过LoadedApk.getResources来创建一个Resources实例
            Resources resources = packageInfo.getResources(mainThread);            if (resources != null) {            if (displayId != Display.DEFAULT_DISPLAY
                    || overrideConfiguration != null
                    || (compatInfo != null && compatInfo.applicationScale
                            != resources.getCompatibilityInfo().applicationScale)) {
                resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
                        packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
                        packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
                        overrideConfiguration, compatInfo);
            }
        }
            mResources = resources;// 赋值
            //......
            mContentResolver = new ApplicationContentResolver(this, mainThread, user);
    }

其中 packageInfo.getResources(mainThread)是指 LoadedApk.getResources():

 public Resources getResources(ActivityThread mainThread) {        if (mResources == null) {            // ActivityThread.getTopLevelResources()
            mResources = mainThread.getTopLevelResources(mResDir/*APK文件位置*/, mSplitResDirs, mOverlayDirs,
                    mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
        }        return mResources;
    }

即而又调用到ActivityThread.getTopLevelResources():

 /**
     * Creates the top level resources for the given package.
     */
    Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
            String[] libDirs, int displayId, Configuration overrideConfiguration,
            LoadedApk pkgInfo) {        return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
                displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo());
    }

mResourcesManager是ResourcesManager的实例,最后资源加载是交给ResourcesManager来完成。 
ResourcesManager.getTopLevelResources:

Resources getTopLevelResources(String resDir, String[] splitResDirs,
            String[] overlayDirs, String[] libDirs, int displayId,
            Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
        final float scale = compatInfo.applicationScale;
        Configuration overrideConfigCopy = (overrideConfiguration != null)
                ? new Configuration(overrideConfiguration) : null;
        ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
        Resources r;
        synchronized (this) {            // Resources is app scale dependent.
            if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);            // Resources是以ResourcesKey为key以弱应用的方式保存在mActiveResources这个Map中
            WeakReference<Resources> wr = mActiveResources.get(key);
            r = wr != null ? wr.get() : null;            //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
            if (r != null && r.getAssets().isUpToDate()) {/                // 缓存里面有,并且是最新的
                if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
                        + ": appScale=" + r.getCompatibilityInfo().applicationScale
                        + " key=" + key + " overrideConfig=" + overrideConfiguration);                return r;
            }
        }        //if (r != null) {
        //    Log.w(TAG, "Throwing away out-of-date resources!!!! "
        //            + r + " " + resDir);
        //}
        // AssetManager创建
        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.

        //加载apk资源
        if (resDir != null) {            if (assets.addAssetPath(resDir) == 0) {                return null;
            }
        }

       ......        if (libDirs != null) {            for (String libDir : libDirs) {                if (libDir.endsWith(".apk")) {                    // Avoid opening files we know do not have resources,
                    // like code-only .jar files.
                    if (assets.addAssetPath(libDir) == 0) {
                        Log.w(TAG, "Asset path '" + libDir +                                "' does not exist or contains no resources.");
                    }
                }
            }
        }        //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
        DisplayMetrics dm = getDisplayMetricsLocked(displayId);
        Configuration config;

        ......          //config初始化赋值
        .....        // 创建Resources
        r = new Resources(assets, dm, config, compatInfo);           //缓存Resources
        synchronized (this) {            // 可能其他线程已经创建好了,则直接返回
            WeakReference<Resources> wr = mActiveResources.get(key);
            Resources existing = wr != null ? wr.get() : null;            if (existing != null && existing.getAssets().isUpToDate()) {                // Someone else already created the resources while we were
                // unlocked; go ahead and use theirs.
                r.getAssets().close();                return existing;
            }            // XXX need to remove entries when weak references go away
            // 把最新的对象保存到缓存中
            mActiveResources.put(key, new WeakReference<>(r));            if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size());            return r;
        }
    }

可以看到ResourcesManager先从缓存找已经加载好的资源Resource,如果没有就重新加载,通过初始化AssetManager和Resources来完成,并缓存。 
其中关键函数就是AssetManager的addAssetPath(resDir)来完成加载并交给Resource来暴露接口。

先看下AssetManager初始化其,构造函数:

/**
     * Create a new AssetManager containing only the basic system assets.
     * Applications will not generally use this method, instead retrieving the
     * appropriate asset manager with {@link Resources#getAssets}.    Not for
     * use by applications.
     * {@hide}
     */
    public AssetManager() {        synchronized (this) {            //......
            init(false);            // 确保有能够访问系统资源的AssetManager对象
            ensureSystemAssets();
        }
    }

init是个native方法,实现如下: 
android_util_AssetManager.android_content_AssetManager_init()

static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{    if (isSystem) {// false
        verifySystemIdmaps();
    }
    AssetManager* am = new AssetManager();    if (am == NULL) {
        jniThrowException(env, "java/lang/OutOfMemoryError", "");        return;
    }

    am->addDefaultAssets();

    ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
    env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}

AssetManager.cpp:addDefaultAssets()是添加系统默认资源路径:/system/framework/framework-res.apk

bool AssetManager::addDefaultAssets()
{    // root = /system/
    const char* root = getenv("ANDROID_ROOT");
    LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");

    String8 path(root);    // path = /system/framework/framework-res.apk
    path.appendPath(kSystemAssets);    return addAssetPath(path, NULL);
}

再通过addAssetPath添加资源路径到mAssetPaths并加载openNonAssetInPathLocked:

bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
    AutoMutex _l(mLock);

    asset_path ap;

    String8 realPath(path);    if (kAppZipName) {        // 如果kAppZipName不为NULL(classes.jar),这里这个值是为NULL的
        realPath.appendPath(kAppZipName);
    }
    ap.type = ::getFileType(realPath.string());    if (ap.type == kFileTypeRegular) {// kAppZipName不为NULL
        ap.path = realPath;
    } else {    // kAppZipName为NULL
        ap.path = path;//ap.path指向APK文件
        ap.type = ::getFileType(path.string());        if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
            ALOGW("Asset path %s is neither a directory nor file (type=%d).",
                 path.string(), (int)ap.type);            return false;
        }
    }    // Skip if we have it already.
    for (size_t i=0; i<mAssetPaths.size(); i++) {        if (mAssetPaths[i].path == ap.path) {            if (cookie) {
                *cookie = static_cast<int32_t>(i+1);
            }            return true;
        }
    }

    ALOGV("In %p Asset %s path: %s", this,
         ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());    // Check that the path has an AndroidManifest.xml
    Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
            kAndroidManifest, Asset::ACCESS_BUFFER, ap);    if (manifestAsset == NULL) {        // This asset path does not contain any resources.
        delete manifestAsset;        return false;
    }    delete manifestAsset;

    mAssetPaths.add(ap);    // new paths are always added at the end
    if (cookie) {
        *cookie = static_cast<int32_t>(mAssetPaths.size());
    }#ifdef __ANDROID__
    // Load overlays, if any
    asset_path oap;    for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
        mAssetPaths.add(oap);
    }#endif

   ......    return true;
}

初始化完成AssetManager之后就是初始Resource,其作用就是缓存mAssets,并暴露接口对外加载资源,实际都是通过AssetManager来完成的:

public class Resources {
.....   public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
            CompatibilityInfo compatInfo) {
        mAssets = assets;
        mMetrics.setToDefaults();        if (compatInfo != null) {
            mCompatibilityInfo = compatInfo;
        }        // 设备相关配置信息更新处理
        updateConfiguration(config, metrics);        // 创建字符串资源池
        assets.ensureStringBlocks();
    }    //实际加载都是转给mAssets即AssetManager
    public CharSequence getText(@StringRes int id) throws NotFoundException {
        CharSequence res = mAssets.getResourceText(id);        if (res != null) {            return res;
        }        throw new NotFoundException("String resource ID #0x"
                                    + Integer.toHexString(id));
    }    //加载图片
     private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {

        final Drawable dr;

        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);        try {            if (file.endsWith(".xml")) {
                final XmlResourceParser rp = loadXmlResourceParser(
                        file, id, value.assetCookie, "drawable");
                dr = Drawable.createFromXml(this, rp, theme);
                rp.close();
            } else {
                final InputStream is = mAssets.openNonAsset(                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                dr = Drawable.createFromResourceStream(this, value, is, file, null);                is.close();
            }
        } catch (Exception e) {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
            final NotFoundException rnf = new NotFoundException(                    "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
            rnf.initCause(e);            throw rnf;
        }
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);        return dr;
    }
    ......
}

从上那么多可以看到开发中不论是设啥资源都是如此AssetManager完成的,到这里就讲完了整个Apk资源加载。

总结

通过上文就发现资源apk(resDir)通过AssetManager.addAssetPath(resDir)来完成加载,并初始化resource来处理暴露资源加载的流程。那么我们就可以通过自定义资源的加载,使用AssetManager来加载我们的单独资源apk不就可以了么,请看我的另一篇文章:打造自己的框架-实现动态加载两种方式 以及Resource是如何暴露出资源加载的流程,系统如何加载显示res下资源。

原文链接:http://www.apkbus.com/blog-822415-78080.html

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP