動態換膚(一) 一、系統實例化View的流程 二、Factory 的使用 三、AssetManager 創建流程 四、資源加載流程

Android知識總結

一、系統實例化View的流程

我們進入AppCompatActivity中的onCreate方法:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        //創建 AppCompatDelegate 
        final AppCompatDelegate delegate = getDelegate();
        //初始化 Factory 
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }

1.1、創建 AppCompatDelegate

    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

1.2、初始化 AppCompatDelegate

在抽象類AppCompatDelegate的實現類AppCompatDelegateImpl中實現

    public void installViewFactory() {
        //構建 LayoutInflater
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            //當 Factory == null 時,設置 Factory2
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else {
            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }

執行 LayoutInflaterCompat#setFactory2 構建 Factory2

    public static void setFactory2(
            @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
        //設置 factory
        inflater.setFactory2(factory);
        //sdk 版本小於 21 執行 下面代碼
        if (Build.VERSION.SDK_INT < 21) {
            final LayoutInflater.Factory f = inflater.getFactory();
            if (f instanceof LayoutInflater.Factory2) {
                forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
            } else {
                forceSetFactory2(inflater, factory);
            }
        }
    }

LayoutInflater#setFactory2 中執行

    public void setFactory2(Factory2 factory) {
        // Factory2 只能創建一次,當創建後再執行拋出異常
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        //標誌位,使 m  只能夠創建一次
        mFactorySet = true;
        if (mFactory == null) {
            //設置 mFactory
            mFactory = mFactory2 = factory;
        } else {
            //當我們 HOOK 時執行,前提利用反射把標誌位 mFactorySet 至 false
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }

我們用的動態換膚,就是利用HOOK技術。有兩種設置方法:

  • 1)、在 super.onCreate(savedInstanceState) 方法之前執行 setFactory2 重新設置我們自己 Factory2 添加到裏面。
  • 2)、利用反射技術把標誌位mFactorySet置爲false,然後setFactory2我們自己的 Factory。這樣再次設置 Factory2 就不會拋異常了。
    private static class FactoryMerger implements Factory2 {
        //再次設至的 Factory
        private final Factory mF1, mF2;
        //第一次設至的 Factory
        private final Factory2 mF12, mF22;
        FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
            mF1 = f1;
            mF2 = f2;
            mF12 = f12;
            mF22 = f22;
        }

        @Nullable
        public View onCreateView(@NonNull String name, @NonNull Context context,
                @NonNull AttributeSet attrs) {
            View v = mF1.onCreateView(name, context, attrs);
            if (v != null) return v;
            return mF2.onCreateView(name, context, attrs);
        }

        @Nullable
        public View onCreateView(@Nullable View parent, @NonNull String name,
                @NonNull Context context, @NonNull AttributeSet attrs) {
            View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
                    : mF1.onCreateView(name, context, attrs);
            if (v != null) return v;
            return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
                    : mF2.onCreateView(name, context, attrs);
        }
    }

1.3、Factory 類

    public interface Factory2 extends Factory {
        @Nullable
        View onCreateView(@Nullable View parent, @NonNull String name,
                @NonNull Context context, @NonNull AttributeSet attrs);
    }
    public interface Factory {
        @Nullable
        View onCreateView(@NonNull String name, @NonNull Context context,
                @NonNull AttributeSet attrs);
    }

上面的代碼我們可以看到 Factory2是繼承Factory的接口。

1.4、初始化 xml 怎麼利用 Factory2

setContentView()的過程就不跟蹤了,我們直接看createViewFromTag 方法。

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        //解析view標籤
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
        //如果需要該標籤與主題相關,需要對context進行包裝,
        //將主題信息加入context包裝類ContextWrapper
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }
        //設置Factory,來對View做額外的拓展,這塊屬於可定製的內容
        try {
            View view = tryCreateView(parent, name, context, attrs);
            //如果此時不存在Factory,不管Factory還是Factory2,
            //還是mPrivateFactory都不存在,那麼會直接對name直接進行解析
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    //如果name中包含.即爲自定義View,
                    //否則爲原生SDK的View控件
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            //返回創建的 View
            return view;
        } 
    }

利用定義的 Factory 創建 View

    public final View tryCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context,
        @NonNull AttributeSet attrs) {
        if (name.equals(TAG_1995)) {
            //BlinkLayout是一種閃爍的FrameLayout,它包裹的內容會一直閃爍,類似QQ提示消息那種。
            return new BlinkLayout(context, attrs);
        }

        View view;
        if (mFactory2 != null) {
            //利用 Factory2 接口創建
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            //利用 Factory 接口創建
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        //利用自定義的  Factory
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }
        return view;
    }

然後執行 AppCompatDelegateImpl 裏面重載的 onCreateView 方法。

     //Factory2 裏面的
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        return createView(parent, name, context, attrs);
    }

    //Factory 裏面的
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return onCreateView(null, name, context, attrs);
    }
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            //獲取屬性
            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
            String viewInflaterClassName =
                    a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
            if (viewInflaterClassName == null) {
                mAppCompatViewInflater = new AppCompatViewInflater();
            } else {
                try {
                    Class<?> viewInflaterClass = Class.forName(viewInflaterClassName);
                    mAppCompatViewInflater =
                            (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
                                    .newInstance();
                } catch (Throwable t) {
                    Log.i(TAG, "Failed to instantiate custom view inflater "
                            + viewInflaterClassName + ". Falling back to default.", t);
                    //創建 AppCompatViewInflater
                    mAppCompatViewInflater = new AppCompatViewInflater();
                }
            }
        }
        boolean inheritContext = false;
      //把 xx 控件轉換成  AppCompatxx 控件
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }

然後在AppCompatViewInflatercreateView進行轉換

    final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;

        // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
        // by using the parent's context
        if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            // We then apply the theme on the context, if specified
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }

        View view = null;

        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Spinner":
                view = createSpinner(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ToggleButton":
                view = createToggleButton(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                // The fallback that allows extending class to take over view inflation
                // for other tags. Note that we don't check that the result is not-null.
                // That allows the custom inflater path to fall back on the default one
                // later in this method.
                view = createView(context, name, attrs);
        }

        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            // If we have created a view, check its android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
    }

二、Factory 的使用

    override fun onCreate(savedInstanceState: Bundle?) {
        LayoutInflater.from(this).factory2 = object : LayoutInflater.Factory2{
            override fun onCreateView(
                parent: View?,
                name: String,
                context: Context,
                attrs: AttributeSet
            ): View? {
                if (TextUtils.equals(name, "TextView")) {
                    val btn = Button(this@MyActivity)
                    btn.text = "我是一個按鈕"
                    return btn
                }
                return null
            }

            override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
                return null
            }

        }
        super.onCreate(savedInstanceState)
        setContentView(R.layout.my_lactivity)
    }

上面的代碼就是 HOOK 技術的簡單運用,我們利用自己創建的Factory2,把控件 TextView 轉換爲控件 Button 。

2.1、在生命週期中創建

我們也可以onActivityCreated中創建 Factory2。動態換膚就是利用這一點。
我們看Application內部的ActivityLifecycleCallbacks的源碼:

    public interface ActivityLifecycleCallbacks {
        default void onActivityPreCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { }
        //監聽 Activity 的創建
        void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);
        default void onActivityPostCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {}
        default void onActivityPreStarted(@NonNull Activity activity) { }
        void onActivityStarted(@NonNull Activity activity);
        default void onActivityPostStarted(@NonNull Activity activity) {}
        default void onActivityPreResumed(@NonNull Activity activity) { }
        void onActivityResumed(@NonNull Activity activity);
        default void onActivityPostResumed(@NonNull Activity activity) {  }
        default void onActivityPrePaused(@NonNull Activity activity) { }
        void onActivityPaused(@NonNull Activity activity);
        default void onActivityPostPaused(@NonNull Activity activity) { }
        default void onActivityPreStopped(@NonNull Activity activity) {  }
        void onActivityStopped(@NonNull Activity activity);
        default void onActivityPostStopped(@NonNull Activity activity) { }
        default void onActivityPreSaveInstanceState(@NonNull Activity activity,@NonNull Bundle outState) { }
        void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState);
        default void onActivityPostSaveInstanceState(@NonNull Activity activity,@NonNull Bundle outState) { }
        default void onActivityPreDestroyed(@NonNull Activity activity) {}
        void onActivityDestroyed(@NonNull Activity activity);
        default void onActivityPostDestroyed(@NonNull Activity activity{}
    }

我們可以實現接口 ActivityLifecycleCallbacks 來監聽Activity 的生命週期。
Activity 的創建我們在Activity#onCreate中執行監聽

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        mFragments.dispatchCreate();
        dispatchActivityCreated(savedInstanceState);
        ...
    }
    private void dispatchActivityCreated(@Nullable Bundle savedInstanceState) {
        getApplication().dispatchActivityCreated(this, savedInstanceState);
        Object[] callbacks = collectActivityLifecycleCallbacks();
        if (callbacks != null) {
            for (int i = 0; i < callbacks.length; i++) {
                // 調用 onActivityCreated,進行創建監聽
                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityCreated(this,
                        savedInstanceState);
            }
        }
    }
  • 利用 map 存儲監聽事件
   private final ArrayList<Application.ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
            new ArrayList<Application.ActivityLifecycleCallbacks>();
  • 註冊監聽
    public void registerActivityLifecycleCallbacks(
            @NonNull Application.ActivityLifecycleCallbacks callback) {
        synchronized (mActivityLifecycleCallbacks) {
            mActivityLifecycleCallbacks.add(callback);
        }
    }
  • 註銷監聽
    public void unregisterActivityLifecycleCallbacks(
            @NonNull Application.ActivityLifecycleCallbacks callback) {
        synchronized (mActivityLifecycleCallbacks) {
            mActivityLifecycleCallbacks.remove(callback);
        }
    }

三、AssetManager 創建流程

首先我們執行ActivityThread#performLaunchActivity方法

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo; 
        ...
        ComponentName component = r.intent.getComponent();
        if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }

        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        ...
        return activity;
    }

執行ActivityThread#createBaseContextForActivity方法

    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        final int displayId;
        try {
            displayId = ActivityTaskManager.getService().getDisplayId(r.token);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        //創建 ContextImpl
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

        // The rotation adjustments must be applied before creating the activity, so the activity
        // can get the adjusted display info during creation.
        if (r.mPendingFixedRotationAdjustments != null) {
            handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments,
                    r.overrideConfig);
            r.mPendingFixedRotationAdjustments = null;
        }

        final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
        // For debugging purposes, if the activity's package name contains the value of
        // the "debug.use-second-display" system property as a substring, then show
        // its content on a secondary display if there is one.
        String pkgName = SystemProperties.get("debug.second-display.pkg");
        if (pkgName != null && !pkgName.isEmpty()
                && r.packageInfo.mPackageName.contains(pkgName)) {
            for (int id : dm.getDisplayIds()) {
                if (id != Display.DEFAULT_DISPLAY) {
                    Display display =
                            dm.getCompatibleDisplay(id, appContext.getResources());
                    appContext = (ContextImpl) appContext.createDisplayContext(display);
                    break;
                }
            }
        }
        return appContext;
    }

執行ContextImpl#createActivityContext方法,創建 Context

    static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");

        String[] splitDirs = packageInfo.getSplitResDirs();
        ClassLoader classLoader = packageInfo.getClassLoader();

        if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");
            try {
                classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);
                splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
            } catch (NameNotFoundException e) {
                // Nothing above us can handle a NameNotFoundException, better crash.
                throw new RuntimeException(e);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
            }
        }
        //創建 ContextImpl, ContextImpl是 Context的實現類
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null,
                activityInfo.splitName, activityToken, null, 0, classLoader, null);
        context.mIsUiContext = true;
        context.mIsAssociatedWithDisplay = true;
        context.mIsSystemOrSystemUiContext = isSystemOrSystemUI(context);

        // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
        displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;

        final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
                ? packageInfo.getCompatibilityInfo()
                : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;

        final ResourcesManager resourcesManager = ResourcesManager.getInstance();

        // Create the base resources for which all configuration contexts for this Activity
        // 設置 Resources 資源
        context.setResources(resourcesManager.createBaseTokenResources(activityToken,
                packageInfo.getResDir(),
                splitDirs,
                packageInfo.getOverlayDirs(),
                packageInfo.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfiguration,
                compatInfo,
                classLoader,
                packageInfo.getApplication() == null ? null
                        : packageInfo.getApplication().getResources().getLoaders()));
        context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
                context.getResources());
        return context;
    }

執行ContextImpl#createBaseTokenResources方法

    public @Nullable Resources createBaseTokenResources(@NonNull IBinder token,
            @Nullable String resDir,
            @Nullable String[] splitResDirs,
            @Nullable String[] overlayDirs,
            @Nullable String[] libDirs,
            int displayId,
            @Nullable Configuration overrideConfig,
            @NonNull CompatibilityInfo compatInfo,
            @Nullable ClassLoader classLoader,
            @Nullable List<ResourcesLoader> loaders) {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                    "ResourcesManager#createBaseActivityResources");
            final ResourcesKey key = new ResourcesKey(
                    resDir,
                    splitResDirs,
                    overlayDirs,
                    libDirs,
                    displayId,
                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                    compatInfo,
                    loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();

            if (DEBUG) {
                Slog.d(TAG, "createBaseActivityResources activity=" + token
                        + " with key=" + key);
            }

            synchronized (this) {
                // Force the creation of an ActivityResourcesStruct.
                getOrCreateActivityResourcesStructLocked(token);
            }

            // Update any existing Activity Resources references.
            updateResourcesForActivity(token, overrideConfig, displayId,
                    false /* movedToDifferentDisplay */);

            rebaseKeyForActivity(token, key);

            synchronized (this) {
                Resources resources = findResourcesForActivityLocked(token, key,
                        classLoader);
                if (resources != null) {
                    return resources;
                }
            }

            // Now request an actual Resources object.
            return createResources(token, key, classLoader);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }

執行ContextImpl#createResources方法創建Resources

    private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
            int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo,
            List<ResourcesLoader> resourcesLoader) {
        final String[] splitResDirs;
        final ClassLoader classLoader;
        try {
            splitResDirs = pi.getSplitPaths(splitName);
            classLoader = pi.getSplitClassLoader(splitName);
        } catch (NameNotFoundException e) {
            throw new RuntimeException(e);
        }
        //利用單例模式獲取 Resources
        return ResourcesManager.getInstance().getResources(activityToken,
                pi.getResDir(),
                splitResDirs,
                pi.getOverlayDirs(),
                pi.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfig,
                compatInfo,
                classLoader,
                resourcesLoader);
    }

我們看 ResourcesManager裏面的方法

    //單例,懶漢式
    public static ResourcesManager getInstance() {
        synchronized (ResourcesManager.class) {
            if (sResourcesManager == null) {
                sResourcesManager = new ResourcesManager();
            }
            return sResourcesManager;
        }
    }
    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,
            @Nullable List<ResourcesLoader> loaders) {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
            //創建 key,根據key 獲取 ResourcesImpl
            final ResourcesKey key = new ResourcesKey(
                    resDir,
                    splitResDirs,
                    overlayDirs,
                    libDirs,
                    displayId,
                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                    compatInfo,
                    loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();

            if (activityToken != null) {
                rebaseKeyForActivity(activityToken, key);
            }
            //創建資源 Resources
            return createResources(activityToken, key, classLoader);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }

獲取 Resources 資源

    private @Nullable Resources createResources(@Nullable IBinder activityToken,
            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
        synchronized (this) {
            if (DEBUG) {
                Throwable here = new Throwable();
                here.fillInStackTrace();
                Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
            }
            //根據 key 獲取 ResourcesImpl
            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);
            if (resourcesImpl == null) {
                return null;
            }
            //創建 Resources
            if (activityToken != null) {
                return createResourcesForActivityLocked(activityToken, classLoader,
                        resourcesImpl, key.mCompatInfo);
            } else {
                return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
            }
        }
    }

執行ResourcesManager#findOrCreateResourcesImplForKeyLocked方法

    private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
            new ArrayMap<>();

    private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
            @NonNull ResourcesKey key) {
        //從緩存中獲取 ResourcesImpl
        ResourcesImpl impl = findResourcesImplForKeyLocked(key);
        if (impl == null) {
            //創建 ResourcesImpl
            impl = createResourcesImpl(key);
            if (impl != null) {
                //加入緩存
                mResourceImpls.put(key, new WeakReference<>(impl));
            }
        }
        return impl;
    }

執行ResourcesManager#createResourcesImpl方法,,創建AssetManager,幷包裝到 ResourcesImpl

    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);
        //創建 AssetManager
        final AssetManager assets = createAssetManager(key);
        if (assets == null) {
            return null;
        }

        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
        final Configuration config = generateConfig(key, dm);
        //創建ResourcesImpl
        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);

        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
    }

通過Builder模式創建 AssetManager

    protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
        final AssetManager.Builder builder = new AssetManager.Builder();

        if (key.mResDir != null) {
            try {
                builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
                        false /*overlay*/));
            } catch (IOException e) {
                Log.e(TAG, "failed to add asset path " + key.mResDir);
                return null;
            }
        }

        if (key.mSplitResDirs != null) {
            for (final String splitResDir : key.mSplitResDirs) {
                try {
                    builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/,
                            false /*overlay*/));
                } catch (IOException e) {
                    Log.e(TAG, "failed to add split asset path " + splitResDir);
                    return null;
                }
            }
        }

        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.
                    try {
                        builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
                                false /*overlay*/));
                    } catch (IOException e) {
                        Log.w(TAG, "Asset path '" + libDir +
                                "' does not exist or contains no resources.");

                        // continue.
                    }
                }
            }
        }

        if (key.mOverlayDirs != null) {
            for (final String idmapPath : key.mOverlayDirs) {
                try {
                    builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
                            true /*overlay*/));
                } catch (IOException e) {
                    Log.w(TAG, "failed to add overlay path " + idmapPath);

                    // continue.
                }
            }
        }

        if (key.mLoaders != null) {
            for (final ResourcesLoader loader : key.mLoaders) {
                builder.addLoader(loader);
            }
        }
        return builder.build();
    }

執行ResourcesManager#loadApkAssets方法創建 ApkAssets包資源

    private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
            throws IOException {
        final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
        ApkAssets apkAssets = null;
        if (mLoadedApkAssets != null) {
            apkAssets = mLoadedApkAssets.get(newKey);
            if (apkAssets != null && apkAssets.isUpToDate()) {
                return apkAssets;
            }
        }

        // Optimistically check if this ApkAssets exists somewhere else.
        final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
        if (apkAssetsRef != null) {
            apkAssets = apkAssetsRef.get();
            if (apkAssets != null && apkAssets.isUpToDate()) {
                if (mLoadedApkAssets != null) {
                    mLoadedApkAssets.put(newKey, apkAssets);
                }

                return apkAssets;
            } else {
                // Clean up the reference.
                mCachedApkAssets.remove(newKey);
            }
        }

        // We must load this from disk.
        if (overlay) {
            apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path), 0 /*flags*/);
        } else {
            apkAssets = ApkAssets.loadFromPath(path, sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
        }

        if (mLoadedApkAssets != null) {
            mLoadedApkAssets.put(newKey, apkAssets);
        }

        mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
        return apkAssets;
    }

四、資源加載流程

Resources 類中根據資源 id,獲取String。

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

AssetManager中執行 getResourceText根據資源 id 獲取資源

    @Nullable CharSequence getResourceText(@StringRes int resId) {
        synchronized (this) {
            final TypedValue outValue = mValue;
            if (getResourceValue(resId, 0, outValue, true)) {
                return outValue.coerceToString();
            }
            return null;
        }
    }

從上面的資源加載流程可得,Resources 類也是通過 AssetManager 類來訪問那些被編譯過的應用程序資源文件的,不過在訪問之前,它會先根據資源 ID 查找得到對應的資源文件名。 而 AssetManager 對象既可以通過文件名訪問那些被編譯過的,也可以訪問沒有被編譯過的應用程序資源文件。

  • 根據名字獲取資源包的資源
    // app原始的resource
    private Resources mAppResources;
    // 皮膚包的resource
    private Resources mSkinResources;
    /**
     * 1.通過原始app中的resId(R.color.XX)獲取到自己的 名字
     * 2.根據名字和類型獲取皮膚包中的ID
     */
    public int getIdentifier(int resId) {
        if (isDefaultSkin) {
            return resId;
        }
        String resName = mAppResources.getResourceEntryName(resId);
        String resType = mAppResources.getResourceTypeName(resId);
        int skinId = mSkinResources.getIdentifier(resName, resType, mSkinPkgName);
        return skinId;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章