当前位置:   article > 正文

Android换肤实现原理_getresourceentryname getidentifier

getresourceentryname getidentifier

流程分析

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

  1. /**
  2. * 1.通过原始app中的resId(R.color.XX)获取到自己的 名字
  3. * 2.根据名字和类型获取皮肤包中的ID
  4. */
  5. public int getIdentifier(int resId){
  6. if(isDefaultSkin){
  7. return resId;
  8. }
  9. String resName=mAppResources.getResourceEntryName(resId);
  10. String resType=mAppResources.getResourceTypeName(resId);
  11. int skinId=mSkinResources.getIdentifier(resName,resType,mSkinPkgName);
  12. return skinId;
  13. }

4.对view更换新的资源 

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

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

  1. //创建ActivityThread
  2. ActivityThread thread = new ActivityThread();
  3. thread.attach(true, 0);
  4. //查看attach
  5. final IActivityManager mgr = ActivityManager.getService();
  6. //通过AMS的代理对象,会执行AMS的attachApplication()
  7. mgr.attachApplication(mAppThread, startSeq);
  8. //来到AMS中,通过ApplicationThread的代理类,执行thread.bindApplication
  9. //查看ApplicationThread的bindApplication()方法,通过handler执行handleBindApplication()
  10. //handleBindApplication()中会makeApplication(data.restrictedBackupMode, null);
  11. //makeApplication 中ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
  1. static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
  2. if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
  3. ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
  4. null);
  5. //获取Resources
  6. context.setResources(packageInfo.getResources());
  7. return context;
  8. }

 

  1. //LoadApk
  2. public Resources getResources() {
  3. if (mResources == null) {
  4. final String[] splitPaths;
  5. try {
  6. splitPaths = getSplitPaths(null);
  7. } catch (NameNotFoundException e) {
  8. // This should never fail.
  9. throw new AssertionError("null split not found");
  10. }
  11. mResources = ResourcesManager.getInstance().getResources(null, mResDir,
  12. splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
  13. Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
  14. getClassLoader());
  15. }
  16. return mResources;
  17. }

 

  1. private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
  2. @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
  3. ...
  4. ResourcesImpl resourcesImpl = createResourcesImpl(key);
  5. if (resourcesImpl == null) {
  6. return null;
  7. }
  8. // Add this ResourcesImpl to the cache.
  9. mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
  10. final Resources resources;
  11. if (activityToken != null) {
  12. resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
  13. resourcesImpl, key.mCompatInfo);
  14. } else {
  15. resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
  16. }
  17. return resources;
  18. }
  19. }

重点重点,创建我们的Resource 

  1. private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
  2. final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
  3. daj.setCompatibilityInfo(key.mCompatInfo);
  4. //关键代码
  5. final AssetManager assets = createAssetManager(key);
  6. if (assets == null) {
  7. return null;
  8. }
  9. final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
  10. final Configuration config = generateConfig(key, dm);
  11. final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
  12. if (DEBUG) {
  13. Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
  14. }
  15. return impl;
  16. }

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

  1. //宿主app的 resources;
  2. Resources appResource = mContext.getResources();
  3. //
  4. //反射创建AssetManager 与 Resource
  5. AssetManager assetManager = AssetManager.class.newInstance();
  6. //资源路径设置 目录或压缩包
  7. Method addAssetPath = assetManager.getClass().getMethod("addAssetPath",
  8. String.class);
  9. addAssetPath.invoke(assetManager, skinPath);
  10. //根据当前的设备显示器信息 与 配置(横竖屏、语言等) 创建Resources
  11. Resources skinResource = new Resources(assetManager, appResource.getDisplayMetrics
  12. (), appResource.getConfiguration());

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

activity中setContentView(layoutResID);
调用到getWindow().setContentView(layoutResID);Window的实现类是PhoneWindow
  1. public void setContentView(int layoutResID) {
  2. // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
  3. // decor, when theme attributes and the like are crystalized. Do not check the feature
  4. // before this happens.
  5. if (mContentParent == null) {
  6. installDecor();
  7. } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  8. mContentParent.removeAllViews();
  9. }
  10. if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  11. final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
  12. getContext());
  13. transitionTo(newScene);
  14. } else {
  15. //重点看inflate工程
  16. mLayoutInflater.inflate(layoutResID, mContentParent);
  17. }
  18. mContentParent.requestApplyInsets();
  19. final Callback cb = getCallback();
  20. if (cb != null && !isDestroyed()) {
  21. cb.onContentChanged();
  22. }
  23. mContentParentExplicitlySet = true;
  24. }

一直跟进去,到LayoutInflater中

  1. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
  2. synchronized (mConstructorArgs) {
  3. ...
  4. // Temp is the root view that was found in the xml
  5. //创建view
  6. final View temp = createViewFromTag(root, name, inflaterContext, attrs);
  7. ...
  8. }
  9. }
  1. View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
  2. boolean ignoreThemeAttr) {
  3. ...
  4. try {
  5. View view;
  6. //重点来了
  7. //默认mFactory2为空,这个是Google默认给我们留的一个入口,我们重写mFactory2后就可以修改view, 换肤我们就从这儿开始,LayoutInflaterCompat.setFactory2(layoutInflater, skinLayoutInflaterFactory);
  8. if (mFactory2 != null) {
  9. view = mFactory2.onCreateView(parent, name, context, attrs);
  10. } else if (mFactory != null) {
  11. //默认是用的mFactory
  12. view = mFactory.onCreateView(name, context, attrs);
  13. } else {
  14. view = null;
  15. }
  16. if (view == null && mPrivateFactory != null) {
  17. view = mPrivateFactory.onCreateView(parent, name, context, attrs);
  18. }
  19. if (view == null) {
  20. final Object lastContext = mConstructorArgs[0];
  21. mConstructorArgs[0] = context;
  22. try {
  23. //上面创建失败,创建view的工程,我们会在mFactory2中抄袭这块儿代码
  24. //普通view
  25. if (-1 == name.indexOf('.')) {
  26. view = onCreateView(parent, name, attrs);
  27. } else {
  28. //a.b.c.View一般是自定义view
  29. view = createView(name, null, attrs);
  30. }
  31. } finally {
  32. mConstructorArgs[0] = lastContext;
  33. }
  34. }
  35. return view;
  36. }
  37. ...
  38. }

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

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

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

  1. public class SkinLayoutInflaterFactory implements LayoutInflater.Factory2 {
  2. private static final String[] mClassPrefixList = {
  3. "android.widget.",
  4. "android.webkit.",
  5. "android.app.",
  6. "android.view."
  7. };
  8. //记录对应VIEW的构造函数
  9. private static final Class<?>[] mConstructorSignature = new Class[] {
  10. Context.class, AttributeSet.class};
  11. private static final HashMap<String, Constructor<? extends View>> mConstructorMap =
  12. new HashMap<String, Constructor<? extends View>>();
  13. private Activity activity;
  14. @Override
  15. public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
  16. //换肤就是在需要时候替换 View的属性(src、background等)
  17. //所以这里创建 View,从而修改View属性
  18. View view = createSDKView(name, context, attrs);
  19. if (null == view) {
  20. view = createView(name, context, attrs);
  21. }
  22. //拿到view,就可以对其更换资源
  23. return view;
  24. }
  25. private View createSDKView(String name, Context context, AttributeSet
  26. attrs) {
  27. //如果包含 . 则不是SDK中的view 可能是自定义view包括support库中的View
  28. if (-1 != name.indexOf('.')) {
  29. return null;
  30. }
  31. //不包含就要在解析的 节点 name前,拼上: android.widget. 等尝试去反射
  32. for (int i = 0; i < mClassPrefixList.length; i++) {
  33. View view = createView(mClassPrefixList[i] + name, context, attrs);
  34. if(view!=null){
  35. return view;
  36. }
  37. }
  38. return null;
  39. }
  40. private View createView(String name, Context context, AttributeSet
  41. attrs) {
  42. Constructor<? extends View> constructor = findConstructor(context, name);
  43. try {
  44. return constructor.newInstance(context, attrs);
  45. } catch (Exception e) {
  46. }
  47. return null;
  48. }
  49. private Constructor<? extends View> findConstructor(Context context, String name) {
  50. Constructor<? extends View> constructor = mConstructorMap.get(name);
  51. if (constructor == null) {
  52. try {
  53. Class<? extends View> clazz = context.getClassLoader().loadClass
  54. (name).asSubclass(View.class);
  55. constructor = clazz.getConstructor(mConstructorSignature);
  56. mConstructorMap.put(name, constructor);
  57. } catch (Exception e) {
  58. }
  59. }
  60. return constructor;
  61. }
  62. @Override
  63. public View onCreateView(String name, Context context, AttributeSet attrs) {
  64. return null;
  65. }
  66. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/86933
推荐阅读
相关标签
  

闽ICP备14008679号