8

Android APK资源加载流程

 3 years ago
source link: http://www.androidchina.net/10197.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Android APK资源加载流程 – Android开发中文站
你的位置:Android开发中文站 > 热点资讯 > Android APK资源加载流程

我们在Activity中访问资源(图片,字符串,颜色等)是非常方便的,只需要getResources()获取一个Resources对象,然后就可以访问各种资源了,那这些资源到底是怎么被加载的呢?下面我们就分析一下资源加载机制

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方法,我们从这个方法开始看

ActivityThread#handleBindApplication
private void handleBindApplication(AppBindData data) {
    ...
    final InstrumentationInfo ii;
    ...
    // 创建 mInstrumentation 实例
    if (ii != null) {
        final ApplicationInfo instrApp = new ApplicationInfo();
        ii.copyTo(instrApp);
        instrApp.initForUser(UserHandle.myUserId());
        final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
                appContext.getClassLoader(), false, true, false);
        final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);

        try {
            final ClassLoader cl = instrContext.getClassLoader();
            mInstrumentation = (Instrumentation)
                cl.loadClass(data.instrumentationName.getClassName()).newInstance();
        } catch (Exception e) {
            ...
        }
        ...
    } else {
        mInstrumentation = new Instrumentation();
    }
    ...
    Application app;
    ...
    // 创建 Application 实例
    try {
        ...
        app = data.info.makeApplication(data.restrictedBackupMode, null);
        mInitialApplication = app;
        ...
        try {
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            ...
        }
    } finally {
        ...
    }
    ...
}



// http://androidxref.com/8.1.0_r33/xref/frameworks/base/core/java/android/app/LoadedApk.java#959
public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    ...

    try {
        ...
        //注释1
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        appContext.setOuterContext(app);
    } catch (Exception e) {
        ...
    }
    ...
    return app;
}

 static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
        return new ContextImpl(null, mainThread,
                packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
    }

这个方法我们只留下了最核心的内容,我们看下注释1, ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);这个方法会直接new一个新的ContextImpl

    private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
          ....
        //LoadApk赋值
        mPackageInfo = packageInfo;
        mResourcesManager = ResourcesManager.getInstance();

       ...
        //通过LoadApk.getResources获取Resources对象
        Resources resources = packageInfo.getResources(mainThread);
        if (resources != null) {
            if (displayId != Display.DEFAULT_DISPLAY
                    || overrideConfiguration != null
                    || (compatInfo != null && compatInfo.applicationScale
                            != resources.getCompatibilityInfo().applicationScale)) {

                if (container != null) {
                    // This is a nested Context, so it can't be a base Activity context.
                    // Just create a regular Resources object associated with the Activity.
                    resources = mResourcesManager.getResources(
                            activityToken,
                            packageInfo.getResDir(),
                            packageInfo.getSplitResDirs(),
                            packageInfo.getOverlayDirs(),
                            packageInfo.getApplicationInfo().sharedLibraryFiles,
                            displayId,
                            overrideConfiguration,
                            compatInfo,
                            packageInfo.getClassLoader());
                } else {
                    // This is not a nested Context, so it must be the root Activity context.
                    // All other nested Contexts will inherit the configuration set here.
                    resources = mResourcesManager.createBaseActivityResources(
                            activityToken,
                            packageInfo.getResDir(),
                            packageInfo.getSplitResDirs(),
                            packageInfo.getOverlayDirs(),
                            packageInfo.getApplicationInfo().sharedLibraryFiles,
                            displayId,
                            overrideConfiguration,
                            compatInfo,
                            packageInfo.getClassLoader());
                }
            }
        }
        //为mResources变量赋值
        mResources = resources;

       ...
    }

packageInfo.getResources,packageInfo是LoadApk类型的,我们看下这个方法

LoadApk#getResources
 public Resources getResources(ActivityThread mainThread) {
        if (mResources == null) {
            mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
                    mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
        }
        return mResources;
    }

其中调用了ActivityThread的getTopLevelResources方法,我们继续看一下

ActivityThread#getTopLevelResources
 Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
            String[] libDirs, int displayId, LoadedApk pkgInfo) {
        return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
                displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader());
    }

继续调用了mResourcesManager的getResources方法,我么继续跟下去

ResourcesManager#getResources
  public @NonNull 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);
        }
    }


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

        // 创建ResourcesImpl
        ResourcesImpl resourcesImpl = createResourcesImpl(key);
        ....

            final Resources resources;
            if (activityToken != null) {
                resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                        resourcesImpl);
            } else {
                resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
            }
            return resources;
        }
    }


 private @NonNull ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);

        final AssetManager assets = createAssetManager(key);
        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
        final Configuration config = generateConfig(key, dm);
        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
    }


  protected @NonNull 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) {
                throw new Resources.NotFoundException("failed to add asset path " + key.mResDir);
            }
        }

        if (key.mSplitResDirs != null) {
            for (final String splitResDir : key.mSplitResDirs) {
                if (assets.addAssetPath(splitResDir) == 0) {
                    throw new Resources.NotFoundException(
                            "failed to add split asset path " + splitResDir);
                }
            }
        }

        if (key.mOverlayDirs != null) {
            for (final String idmapPath : key.mOverlayDirs) {
                assets.addOverlayPath(idmapPath);
            }
        }

        if (key.mLibDirs != null) {
            for (final String libDir : key.mLibDirs) {
                if (libDir.endsWith(".apk")) {
                    // Avoid opening files we know do not have resources,
                    // like code-only .jar files.
                    if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
                        Log.w(TAG, "Asset path '" + libDir +
                                "' does not exist or contains no resources.");
                    }
                }
            }
        }
        return assets;
    }


     private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
            @NonNull ResourcesImpl impl) {
        // Find an existing Resources that has this ResourcesImpl set.
        final int refCount = mResourceReferences.size();
        for (int i = 0; i < refCount; i++) {
            WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
            Resources resources = weakResourceRef.get();
            if (resources != null &&
                    Objects.equals(resources.getClassLoader(), classLoader) &&
                    resources.getImpl() == impl) {
                if (DEBUG) {
                    Slog.d(TAG, "- using existing ref=" + resources);
                }
                return resources;
            }
        }

        // Create a new Resources reference and use the existing ResourcesImpl object.
        Resources resources = new Resources(classLoader);
        resources.setImpl(impl);
        mResourceReferences.add(new WeakReference<>(resources));
        if (DEBUG) {
            Slog.d(TAG, "- creating new ref=" + resources);
            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
        }
        return resources;
    }

首先调用createResourcesImpl,创建ResourcesImpl,我们看下这个方法内部创建了AssetManager assets = new AssetManager();,然后调用assets.addAssetPath添加资源地址,最后返回final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);,最后查看是否有缓存,如果有则返回缓存的resources,如果没有就重新构建Resources,然后返回
平时使用

 Resources resources = getResources();
 resources.getString();
 resources.getAssets();
 resources.getColor();
 resources.getDrawable()

这个就是我们平时使用的代码,通过resources获取资源,其中getResources方法返回的就是上方ContextImpl创建的mResources变量,然后我们分析一下getString方法的实现

## Resources.java

 public String getString(@StringRes int id) throws NotFoundException {
        return getText(id).toString();
    }

  @NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
        CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
        if (res != null) {
            return res;
        }
        throw new NotFoundException("String resource ID #0x"
                + Integer.toHexString(id));
    }

最后交给了mResourcesImpl.getAssets().getResourceText(id);,我们继续看下这个方法

## ResourcesImpl.java

public AssetManager getAssets() {
        return mAssets;
    }

 public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
            @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
        mAssets = assets;
        mMetrics.setToDefaults();
        mDisplayAdjustments = displayAdjustments;
        mConfiguration.setToDefaults();
        updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
    }

最后交给了mAssets变量处理,mAssets变量就是创建ResourcesImpl时传入的AssetsManager,其实最后就是委托给了AssetsManager去处理

我们发现Apk的资源是通过AssetManager.addAssetPath方法来完成加载,那么我们就可以通过反射构建自己的AssetManager对象,然后把调用addAssetPath加载自己的资源,然后把自己构建的AssetManager通过反射设置给mAssets变量,这样下次加载资源就是用的我们AssetManager,也就是用的我们更换后的资源,这个就是热修复的资源修复的原理

作者:renxhui
链接:https://juejin.im/post/5de9f98f518825127d107638


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK