Android换肤实现原理

流程分析

1.创建skip.apk,里面只有要修改的资源文件,没有代码
2.将skip.apk放在data/data目录下
3.通过原apk中的资源id获取到资源的name和type,然后通过mSkinResources.getIdentifier获取到皮肤包中的资源(mSkinResources是skip.apk资源包中获取的Resources,下面会讲怎么获取skip.apk中的资源)

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

4.对view更换新的资源 

1.Resource是怎么加载进内存的

当我们的app进程创建出来的时候,回执行我们app的入口函数ActivityThread.main()方法

//创建ActivityThread
 ActivityThread thread = new ActivityThread();
 thread.attach(true, 0);
//查看attach
final IActivityManager mgr = ActivityManager.getService();
      //通过AMS的代理对象,会执行AMS的attachApplication()
      mgr.attachApplication(mAppThread, startSeq);
//来到AMS中,通过ApplicationThread的代理类,执行thread.bindApplication
//查看ApplicationThread的bindApplication()方法,通过handler执行handleBindApplication()
//handleBindApplication()中会makeApplication(data.restrictedBackupMode, null);
//makeApplication 中ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
       
    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);
        //获取Resources
        context.setResources(packageInfo.getResources());
        return context;
    }

 

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

 

    private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
        ...
            ResourcesImpl resourcesImpl = createResourcesImpl(key);
            if (resourcesImpl == null) {
                return null;
            }

            // Add this ResourcesImpl to the cache.
            mResourceImpls.put(key, new WeakReference<>(resourcesImpl));

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

重点重点,创建我们的Resource 

    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);
        //关键代码

        final AssetManager assets = createAssetManager(key);
        if (assets == null) {
            return null;
        }

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

 到这里,我们将新的资源加载进去了,demo代码

 //宿主app的 resources;
                Resources appResource = mContext.getResources();
//
                //反射创建AssetManager 与 Resource
                AssetManager assetManager = AssetManager.class.newInstance();
                //资源路径设置 目录或压缩包
                Method addAssetPath = assetManager.getClass().getMethod("addAssetPath",
                        String.class);
                addAssetPath.invoke(assetManager, skinPath);

                //根据当前的设备显示器信息 与 配置(横竖屏、语言等) 创建Resources
                Resources skinResource = new Resources(assetManager, appResource.getDisplayMetrics
                        (), appResource.getConfiguration());

1.从setContentView()流程查看View是怎么创建出来的

activity中setContentView(layoutResID);
调用到getWindow().setContentView(layoutResID);Window的实现类是PhoneWindow
public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //重点看inflate工程
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

一直跟进去,到LayoutInflater中

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
           ...
                    // Temp is the root view that was found in the xml
                    //创建view
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ... 
        }
    }
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {

        ...
        try {
            View view;
            //重点来了
            //默认mFactory2为空,这个是Google默认给我们留的一个入口,我们重写mFactory2后就可以修改view, 换肤我们就从这儿开始,LayoutInflaterCompat.setFactory2(layoutInflater, skinLayoutInflaterFactory);
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                //默认是用的mFactory
                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 {
                    //上面创建失败,创建view的工程,我们会在mFactory2中抄袭这块儿代码
                    //普通view
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                    //a.b.c.View一般是自定义view
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        }
...
    }

2.分析如何重写Factory2中创建view的方法

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

2.1创建SkinLayoutInflaterFactory.java实现Factory2接口,重写onCreateView()创建我们的View
抄写LayoutInflater中创建view的方式
 

public class SkinLayoutInflaterFactory implements LayoutInflater.Factory2 {

    private static final String[] mClassPrefixList = {
            "android.widget.",
            "android.webkit.",
            "android.app.",
            "android.view."
    };

    //记录对应VIEW的构造函数
    private static final Class<?>[] mConstructorSignature = new Class[] {
            Context.class, AttributeSet.class};

    private static final HashMap<String, Constructor<? extends View>> mConstructorMap =
            new HashMap<String, Constructor<? extends View>>();

    
    private Activity activity;


    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        //换肤就是在需要时候替换 View的属性(src、background等)
        //所以这里创建 View,从而修改View属性
        View view = createSDKView(name, context, attrs);
        if (null == view) {
            view = createView(name, context, attrs);
        }
//拿到view,就可以对其更换资源
       
        return view;
    }


    private View createSDKView(String name, Context context, AttributeSet
            attrs) {
        //如果包含 . 则不是SDK中的view 可能是自定义view包括support库中的View
        if (-1 != name.indexOf('.')) {
            return null;
        }
        //不包含就要在解析的 节点 name前,拼上: android.widget. 等尝试去反射
        for (int i = 0; i < mClassPrefixList.length; i++) {
            View view = createView(mClassPrefixList[i] + name, context, attrs);
            if(view!=null){
                return view;
            }
        }
        return null;
    }

    private View createView(String name, Context context, AttributeSet
            attrs) {
        Constructor<? extends View> constructor = findConstructor(context, name);
        try {
            return constructor.newInstance(context, attrs);
        } catch (Exception e) {
        }
        return null;
    }



    private Constructor<? extends View> findConstructor(Context context, String name) {
        Constructor<? extends View> constructor = mConstructorMap.get(name);
        if (constructor == null) {
            try {
                Class<? extends View> clazz = context.getClassLoader().loadClass
                        (name).asSubclass(View.class);
                constructor = clazz.getConstructor(mConstructorSignature);
                mConstructorMap.put(name, constructor);
            } catch (Exception e) {
            }
        }
        return constructor;
    }




    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return null;
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值