美文网首页
Android 源码分析Resource加载,实现换肤

Android 源码分析Resource加载,实现换肤

作者: 青果果 | 来源:发表于2018-04-08 22:03 被阅读0次

    android中资源有图片,颜色,string,styles等等...
    那是如何加载出来的呢?

    通过资源id,在activity中调用getResources()方法
    看源码,其实这个方法来自于context

    public class ContextThemeWrapper extends ContextWrapper {
    ...
      @Override
        public Resources getResources() {
            return getResourcesInternal();
        }
    
        private Resources getResourcesInternal() {
            if (mResources == null) {
                if (mOverrideConfiguration == null) {
                    mResources = super.getResources();
                } else {
                    final Context resContext = createConfigurationContext(mOverrideConfiguration);
                    mResources = resContext.getResources();
                }
            }
            return mResources;
        }
    ...
    

    Context是抽象类,最终的实现类是ContextImpl
    在ContextImpl构造方法中创建Resources
    packageInfo.getResources(mainThread)

     private ContextImpl(ContextImpl container, ActivityThread mainThread,
                LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
                Display display, Configuration overrideConfiguration, int createDisplayWithId) {
            mOuterContext = this;
            ......
            mDisplayAdjustments.setCompatibilityInfo(compatInfo);
            mDisplayAdjustments.setConfiguration(overrideConfiguration);
    
            mDisplay = (createDisplayWithId == Display.INVALID_DISPLAY) ? display
                    : ResourcesManager.getInstance().getAdjustedDisplay(displayId, mDisplayAdjustments);
            // packageInfo类型是LoadedApk 
            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;
    

    LoadedApk类中getResources()方法

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

    Resources实际是在ResourcesManager中直接new出来的

       @Deprecated
        public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
            this(null);
            mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
        }
    
        /**
         * Creates a new Resources object with CompatibilityInfo.
         *
         * @param classLoader class loader for the package used to load custom
         *                    resource classes, may be {@code null} to use system
         *                    class loader
         * @hide
         */
        public Resources(@Nullable ClassLoader classLoader) {
            mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
        }
    

    那一样的我们可以打包自己的资源,仿照源码的方式来加载资源
    自己创建Resources

     Resources resources = context.getResources();
            Class<AssetManager> assetManagerClass = AssetManager.class;
            AssetManager assetManager = null;
            try {
                //创建AssetManager
                assetManager = assetManagerClass.newInstance();
                //反射执行addAssetPath方法,添加资源所在路径
                Method addAssetPath = assetManagerClass.getDeclaredMethod("addAssetPath", String.class);
                addAssetPath.setAccessible(true);
                addAssetPath.invoke(assetManager, skinPath);
                mSkinResource = new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
                mSkinPageName = context.getPackageManager()
                        .getPackageArchiveInfo(skinPath, PackageManager.GET_RECEIVERS).packageName;
            } catch (Exception e) {
                e.printStackTrace();
            }
    

    AssetManager 初始化调用的jni方法 init()底层会创建AssetManager
    并添加默认的系统资源路径“framework/framwork-res.apk”+“assets”,所以我们可以使用系统的资源
    然后加载resources.arsc


    image.png

    apk打包会生成R.java文件, resources.arsc(资源映射信息)就是App的资源索引表

    有个注意点:添加资源路径,底层会判断有没有注册清单文件,如果没有不会加载

         // 检查路径是否有androidmanifest . xml
        Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
                kAndroidManifest, Asset::ACCESS_BUFFER, ap);
        if (manifestAsset == NULL) {
            delete manifestAsset;
            return false;
        }
    

    资源加载通过Resources,而我们可以做到自己构建Resources
    来加载其他apk里的相同名称的资源
    从而实现替换资源,达到换肤的目的还需要拦截view的创建

    //拦截View的创建 LayoutInflater是单例,由系统创建的服务,存在static hashMap中
    LayoutInflater layoutInflater = LayoutInflater.from(this);
    LayoutInflaterCompat.setFactory2(layoutInflater, this);
    

    源码已经告诉我们了Hook you can supply that is called when inflating from a LayoutInflater

    public interface Factory {
            /**
             * Hook you can supply that is called when inflating from a LayoutInflater.
             * You can use this to customize the tag names available in your XML
             * layout files.
             *
             * <p>
             * Note that it is good practice to prefix these custom names with your
             * package (i.e., com.coolcompany.apps) to avoid conflicts with system
             * names.
             *
             * @param name Tag name to be inflated.
             * @param context The context the view is being created in.
             * @param attrs Inflation attributes as specified in XML file.
             *
             * @return View Newly created view. Return null for the default
             *         behavior.
             */
            public View onCreateView(String name, Context context, AttributeSet attrs);
        }
    

    view的创建过程中,mFactory判断是否为null,不是null会回调factory的方法,可以创建view

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                boolean ignoreThemeAttr) {
            if (name.equals("view")) {
                name = attrs.getAttributeValue(null, "class");
            }
    
            // Apply a theme wrapper, if allowed and one is specified.
            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();
            }
    
            if (name.equals(TAG_1995)) {
                // Let's party like it's 1995!
                return new BlinkLayout(context, attrs);
            }
    
            try {
                View view;
                if (mFactory2 != null) {
                    view = mFactory2.onCreateView(parent, name, context, attrs);
                } else if (mFactory != null) {
                    view = mFactory.onCreateView(name, context, attrs);
                } else {
                    view = null;
                }
    
                if (view == null && mPrivateFactory != null) {
                    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
                }
    
                if (view == null) {
                    final Object lastContext = mConstructorArgs[0];
                    mConstructorArgs[0] = context;
                    try {
                        if (-1 == name.indexOf('.')) {
                            view = onCreateView(parent, name, attrs);
                        } else {
                            view = createView(name, null, attrs);
                        }
                    } finally {
                        mConstructorArgs[0] = lastContext;
                    }
                }
    

    通过LayoutInflaterCompat.setFactory2(layoutInflater, this),拦截View的创建
    加载其他资源包的资源就能够实现换肤

    相关文章

      网友评论

          本文标题:Android 源码分析Resource加载,实现换肤

          本文链接:https://www.haomeiwen.com/subject/eugmhftx.html