赞
踩
1.1. Activity实例化 PhoneWindow
- Activity:
- final void attach(Context context, ActivityThread aThread,
- Instrumentation instr, IBinder token, int ident,
- Application application, Intent intent, ActivityInfo info,
- CharSequence title, Activity parent, String id,
- NonConfigurationInstances lastNonConfigurationInstances,
- Configuration config, String referrer, IVoiceInteractor voiceInteractor,
- Window window, ActivityConfigCallback activityConfigCallback) {
- attachBaseContext(context);
-
- mFragments.attachHost(null /*parent*/);
- // Activity 中通过 PhoneWindow 加载布局,PhoneWindow AndrodStudio源码无法查看
- // 上这里去看 http://androidxref.com/
- mWindow = new PhoneWindow(this, window, activityConfigCallback);
- mWindow.setWindowControllerCallback(this);
- mWindow.setCallback(this);
- mWindow.setOnWindowDismissedCallback(this);
- mWindow.getLayoutInflater().setPrivateFactory(this);
- if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
- mWindow.setSoftInputMode(info.softInputMode);
- }
- if (info.uiOptions != 0) {
- mWindow.setUiOptions(info.uiOptions);
- }
- mUiThread = Thread.currentThread();
-
- mMainThread = aThread;
- mInstrumentation = instr;
- mToken = token;
- mIdent = ident;
- mApplication = application;
- mIntent = intent;
- mReferrer = referrer;
- mComponent = intent.getComponent();
- mActivityInfo = info;
- mTitle = title;
- mParent = parent;
- mEmbeddedID = id;
- mLastNonConfigurationInstances = lastNonConfigurationInstances;
- if (voiceInteractor != null) {
- if (lastNonConfigurationInstances != null) {
- mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
- } else {
- mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
- Looper.myLooper());
- }
- }
-
- mWindow.setWindowManager(
- (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
- mToken, mComponent.flattenToString(),
- (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
- if (mParent != null) {
- mWindow.setContainer(mParent.getWindow());
- }
- mWindowManager = mWindow.getWindowManager();
- mCurrentConfig = config;
-
- mWindow.setColorMode(info.colorMode);
- }
1.2. PhoneWindow 中 内部 DecorView
- private DecorView mDecor;
- //
- int layoutResource;
- // 做一系列判断,去加载系统的 layout资源文件
- if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
- layoutResource = R.layout.screen_swipe_dismiss;
- setCloseOnSwipeEnabled(true);
- }
- ......
- // 把系统布局加入到 DecorView 中
- // 系统布局 是一个 FrameLayout的 ViewGroup
- // id 是 android.R.id.content 叫做 mContentParent
- mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
- // 自己调用setContenView 布局位置,自己的布局 Activity
内部结构:
// 继承Activity,那么 返回 ImageView ,继承 AppCompatActivity 返回 AppCompatImageView
为什么? 在 AppCompatImageView 中 ImageView被替换成 AppCompatImageView, 看源码
2.1 .AppCompatActivity 源码 getDelegate
- @Override
- public void setContentView(@LayoutRes int layoutResID) {
- getDelegate().setContentView(layoutResID);
- }
- @NonNull
- public AppCompatDelegate getDelegate() {
- if (mDelegate == null) {
- mDelegate = AppCompatDelegate.create(this, this);
- }
- return mDelegate;
- }
2.2. AppCompatDelegate 最终实例化的是 AppCompatDelegateImplV9
- private static AppCompatDelegate create(Context context, Window window,
- AppCompatCallback callback) {
- final int sdk = Build.VERSION.SDK_INT;
- if (BuildCompat.isAtLeastN()) {
- return new AppCompatDelegateImplN(context, window, callback);
- } else if (sdk >= 23) {
- return new AppCompatDelegateImplV23(context, window, callback);
- } else if (sdk >= 14) {
- return new AppCompatDelegateImplV14(context, window, callback);
- } else if (sdk >= 11) {
- return new AppCompatDelegateImplV11(context, window, callback);
- } else {
- return new AppCompatDelegateImplV9(context, window, callback);
- }
- }
最终看 AppCompatDelegateImplV9 的 createView
核心 LayoutInflaterCompat.setFactory2(layoutInflater, this) 那么 this 实现 factory2接口 重写factory2接口方法
- public static void setFactory2(
- @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
- IMPL.setFactory2(inflater, factory);
- }
-
- public interface Factory2 extends Factory {
- /**
- * Version of {@link #onCreateView(String, Context, AttributeSet)}
- * that also supplies the parent that the view created view will be
- * placed in.
- *
- * @param parent The parent that the created view will be placed
- * in; <em>note that this may be null</em>.
- * @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(View parent, String name, Context context, AttributeSet attrs);
- }
AppCompatDelegateImplV9 的 createView方法
- @Override
- public View createView(View parent, final String name, @NonNull Context context,
- @NonNull AttributeSet attrs) {
- if (mAppCompatViewInflater == null) {
- mAppCompatViewInflater = new AppCompatViewInflater();
- }
-
- boolean inheritContext = false;
- if (IS_PRE_LOLLIPOP) {
- inheritContext = (attrs instanceof XmlPullParser)
- // If we have a XmlPullParser, we can detect where we are in the layout
- ? ((XmlPullParser) attrs).getDepth() > 1
- // Otherwise we have to use the old heuristic
- : shouldInheritContext((ViewParent) parent);
- }
-
- 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 */
- );
-
- // LayoutInflaterCompat.setFactory2(layoutInflater, this);
- // 那么 this 就是 setFactory2(
- // @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory)
- // IMPL.setFactory2(inflater, factory) 的实现
- //
-
- @Override
- public void installViewFactory() {
- LayoutInflater layoutInflater = LayoutInflater.from(mContext);
- if (layoutInflater.getFactory() == null) {
- LayoutInflaterCompat.setFactory2(layoutInflater, this);
- } else {
- if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
- Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
- + " so we can not install AppCompat's");
- }
- }
- }
- }
AppCompatDelegateImplV9 中重写 factory2的 createView方法, 这里回调,等待实例化 factory2 调用 createView
AppCompatDelegateImplV9 的 createView方法 内部 AppCompatViewInflater 调用 createView 控件 替换 如何下
- public 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 = new AppCompatTextView(context, attrs);
- break;
- case "ImageView":
- view = new AppCompatImageView(context, attrs);
- }
- }
-
- @Override
- public void setContentView(int resId) {
- ensureSubDecor();
- ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
- contentParent.removeAllViews();
- // 这里是单例
- LayoutInflater.from(mContext).inflate(resId, contentParent);
- mOriginalWindowCallback.onContentChanged();
- }
2.4. 那么什么时候 实例化 factory2 调用 createView,AppCompatDelegateImplV9 中重写 factory2的 createView方法 什么时候调用 ?? 看 LayoutInflater 源码
创建标签的时候 mFactory2.onCreateView, 那么 在 AppCompatViewInflater中的 createView被 调用,替换创建的标签,上面是钩子
- public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
- final Resources res = getContext().getResources();
- if (DEBUG) {
- Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
- + Integer.toHexString(resource) + ")");
- }
-
- final XmlResourceParser parser = res.getLayout(resource);
- try {
- // 开始解析代码
- return inflate(parser, root, attachToRoot);
- } finally {
- parser.close();
- }
- }
-
- public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
- // 解析 标签
- rInflate(parser, root, inflaterContext, attrs, false);
- }
-
- // 核心代码区
- void rInflate(XmlPullParser parser, View parent, Context context,
- AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
- //
- final View view = createViewFromTag(parent, name, context, attrs);
- }
-
-
- // 根据反射创建标签
- View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
- boolean ignoreThemeAttr) {
- // AppCompatViewInflater设置了 mFactory!=null
- try {
- View view;
- if (mFactory2 != null) {
- // 这里调用了 mFactory2.onCreateView 内部实现是
- // AppCompatViewInflater 中 createView 方法实现 拦截,拦截View的 创建
- 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 {
- // 系统的View
- if (-1 == name.indexOf('.')) {
- view = onCreateView(parent, name, attrs);
- } else {
- // 表示自定义 View
- view = createView(name, null, attrs);
- }
- } finally {
- mConstructorArgs[0] = lastContext;
- }
- }
-
- return view;
- } catch (InflateException e) {
- throw e;
-
- }
- }
- }
-
-
- public final View createView(String name, String prefix, AttributeSet attrs)
- throws ClassNotFoundException, InflateException {
-
- Constructor<? extends View> constructor = sConstructorMap.get(name);
- if (constructor != null && !verifyClassLoader(constructor)) {
- constructor = null;
- sConstructorMap.remove(name);
- }
- Class<? extends View> clazz = null;
-
- try {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
- // 从 构造器中 获取View,如果存在,那么直接获取 ,
- // 不存在,通过反射创建 View ,存入 Map中 , 比如ImageView创建一次即可
- if (constructor == null) {
- // Class not found in the cache, see if it's real, and try to add it
- clazz = mContext.getClassLoader().loadClass(
- prefix != null ? (prefix + name) : name).asSubclass(View.class);
-
- if (mFilter != null && clazz != null) {
- boolean allowed = mFilter.onLoadClass(clazz);
- if (!allowed) {
- failNotAllowed(name, prefix, attrs);
- }
- }
- constructor = clazz.getConstructor(mConstructorSignature);
- constructor.setAccessible(true);
- sConstructorMap.put(name, constructor);
- } else {
- // If we have a filter, apply it to cached constructor
- if (mFilter != null) {
- // Have we seen this name before?
- Boolean allowedState = mFilterMap.get(name);
- if (allowedState == null) {
- // New class -- remember whether it is allowed
- clazz = mContext.getClassLoader().loadClass(
- prefix != null ? (prefix + name) : name).asSubclass(View.class);
-
- boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
- mFilterMap.put(name, allowed);
- if (!allowed) {
- failNotAllowed(name, prefix, attrs);
- }
- } else if (allowedState.equals(Boolean.FALSE)) {
- failNotAllowed(name, prefix, attrs);
- }
- }
- }
-
- }
LayoutInflater 中加载 来源, 是系统的一个服务,系统服务注册 , SystemServiceRegistry:
- // 获取系统服务的时候从 Map中获取即可
- // 系统服务 SYSTEM_SERVICE_NAMES 保存到这个Map中
-
- registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
- new CachedServiceFetcher<LayoutInflater>() {
- @Override
- public LayoutInflater createService(ContextImpl ctx) {
- return new PhoneLayoutInflater(ctx.getOuterContext());
- }});
-
-
- private static <T> void registerService(String serviceName, Class<T> serviceClass,
- ServiceFetcher<T> serviceFetcher) {
- SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
- SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
- }
动态换肤: 既然上面 AppCompatActivity 可以把ImageView 替换 AppCompatImageView,通过设置,
那么我们也可以重写factory 来拦截View 的创建, 实现动态换肤恭喜
换肤原理:
任何一个apk中,资源文件 color , mip , drawable ....
android:textColor="?android:colorPrimary"
android:textColor="@color/text_color"
android:background="@mipmap/btn"
只要是名称 res_name 相同 ,那么任何一个apk 生成的 id(索引值是相同的) ,那么我们只需要把资源放入 一个 skin1.apk中即可,和主apk res_name相同,图片名称相同,颜色 名称相同 在color.xml 下
然后读取主 apk 控件属性对应 的value 的 int值 ,去资源apk中 找到对应资源即可,替换主apk中资源
只要是名称 res_name 相同 ,那么任何一个apk 生成的 id(索引值是相同的),如何理解: 看图
主apk resourec资源文件:
皮肤skin.apk resourec资源文件:
主apk 颜色资源文件
skin 换肤 apk 颜色资源文件
然后读取主 apk 控件属性对应 的value 的 int值 ,去资源apk中 找到对应资源即可
资源文件替换代码:
1. 首先 创建一个 skin.apk ,如图
2. adb push skinp\build\outputs\apk\debug\red.apk /sdcard
测试 资源替换代码 如何 :
- /**
- * 测试皮肤替换 : adb push skinp\build\outputs\apk\debug\red.apk /sdcard
- * @param view
- */
- public void testSkin(View view) {
- try {
- // 加载系统下皮肤
- String skinPath= Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator +"red.apk";
- File file=new File(skinPath);
- if(!file.exists()){
- Log.e(Tag,"文件不存在");
- return;
- }
- AssetManager asset = AssetManager.class.newInstance();
- // 读取 本地一个 .skin 里面资源
- Resources superRes = getResources();
- // 添加本地下载好的资源皮肤 Native 层 c和 c++ 怎么搞得
- Method method = AssetManager.class.getDeclaredMethod("addAssetPath",String.class);
- method.setAccessible(true);
- // 执行反射方法
- method.invoke(asset,skinPath);
- mSkinResources=new Resources(asset,superRes.getDisplayMetrics(),superRes.getConfiguration());
- packageName= getPackageManager().getPackageArchiveInfo(skinPath,PackageManager.GET_ACTIVITIES).packageName ;
- Log.e(Tag, "packageName:"+packageName);
-
- // 根据 包名, 资源文件名称 , 在 drawabel目录下 , 获取id 值 对应的 0x7f06000054
- int drawableId= mSkinResources.getIdentifier("btn", "drawable",packageName);
-
- // 获取 颜色的 id值
- int colorId= mSkinResources.getIdentifier("text_color","color",packageName);
-
- Log.e(Tag, "packageName:"+packageName + " drawable:"+ drawableId);
- // 根据id值 获取到 skin.apk 中 drawable对象
- // Drawable drawable=ContextCompat.getDrawable(this,drawableId); // 获取自己目录下
-
- Drawable drawable=mSkinResources.getDrawable(drawableId);
-
- // 根据id 值获取到颜色
- ColorStateList colorStateList = mSkinResources.getColorStateList(colorId);
-
-
- // 修改控件
- showImg.setBackground(drawable);
- showText.setTextColor(colorStateList);
- } catch (InstantiationException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- }
-
- }
综上 换肤框架实现 源码:
-
- package mk.denganzhi.com.zhiwenku;
- import android.Manifest;
- import android.app.Activity;
- import android.content.Context;
- import android.content.pm.PackageManager;
- import android.content.res.AssetManager;
- import android.content.res.ColorStateList;
- import android.content.res.Resources;
- import android.graphics.drawable.Drawable;
- import android.os.Environment;
- import android.support.v4.app.ActivityCompat;
- import android.support.v4.content.ContextCompat;
- import android.support.v4.view.LayoutInflaterCompat;
- import android.support.v4.view.LayoutInflaterFactory;
- import android.support.v7.app.AppCompatActivity;
- import android.os.Bundle;
- import android.support.v7.widget.AppCompatImageView;
- import android.text.TextUtils;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.widget.ImageView;
- import android.widget.TextView;
-
- import java.io.File;
- import java.lang.reflect.Constructor;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
-
- public class MainActivity extends AppCompatActivity {
-
-
- String Tag="denganzhi";
-
-
- private static final List<String> mAttributes = new ArrayList<>();
- static {
- mAttributes.add("background");
- mAttributes.add("src");
-
- mAttributes.add("textColor");
- mAttributes.add("drawableLeft");
- mAttributes.add("drawableTop");
- mAttributes.add("drawableRight");
- mAttributes.add("drawableBottom");
-
- mAttributes.add("skinTypeface");
- }
-
- List<SkinView> skinViews=new ArrayList<>();
- ImageView showImg=null;
- TextView showText=null;
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
-
- /**
- * LayoutInflater LayoutInflater =
- (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- LayoutInflater 是一个系统服务, 单例
- */
- LayoutInflater layoutInflater= LayoutInflater.from(this);
-
-
- // // setFactory
- // LayoutInflaterCompat.setFactory(layoutInflater, new LayoutInflaterFactory() {
- // @Override
- // public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
- // Log.e("denganzhi1","创建View被拦截:"+name);
- //
- //
- // // 1. 创建View
- //
- // // 2. 解析属性
- //
- // // 3. 同意交给SkinManager管理
- //
- // if(name.equals("ImageView")){
- // TextView textView=new TextView(MainActivity.this);
- // textView.setText("拦截");
- // return textView;
- // }
- // return null;
- // }
- // });
-
-
-
-
-
- // setFactory2
- LayoutInflaterCompat.setFactory2(layoutInflater, new LayoutInflater.Factory2() {
- @Override
- public View onCreateView(String name, Context context, AttributeSet attrs) {
-
- Log.e("denganzhi1","创建View被拦截1:"+name);
- return null;
- }
-
- @Override
- public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
- // 反射 classLoader
- View view = createViewFromTag(name, context, attrs);
- // 自定义View
- if(null == view){
- view = createView(name, context, attrs);
- }
-
-
- SkinView skinView=new SkinView(); // 控件集合
- skinView.setView(view);
-
- List<SkinAttr> skinAttrs=new ArrayList<>(); // 每一个控件存放属性的集合
- int attrLength= attrs.getAttributeCount();
- for (int index=0;index<attrLength; index++){
- // 获取名称,值
- String attrName = attrs.getAttributeName(index);
- // 不需要换肤 属性值
- if (!mAttributes.contains(attrName)) {
- continue;
- }
- // 不符合 换肤条件
- String attrValue = attrs.getAttributeValue(index);
- if (attrValue.startsWith("#")) {
- continue;
- }
- /**
- *
- * android:background="?android:colorPrimary" 使用系统颜色值 ?16843827
- android:background="#000000" 不符合换肤条件
- android:background="@mipmap/ic_launcher" 使用之定义 @2131361793
- 所有的 value 都会别转化为 int 值
- */
- // attrName:background attValue:@2131361792
-
- int resId = 0 ;
- if (attrValue.startsWith("@") || attrValue.startsWith("?")) {
- attrValue = attrValue.substring(1);
- resId= Integer.parseInt(attrValue);
- }
-
- if(resId==0){ //不符合条件
- continue;
- }
-
-
- SkinAttr skinAttr =new SkinAttr();
- skinAttr.setSkyType(attrName);
- // skinAttr.setmResName(resName);
- skinAttr.setmResId(resId);
- skinAttrs.add(skinAttr);
- }
- if(skinAttrs.size()>0 ){
- skinView.setViewName(name);
- skinView.setmAttrs(skinAttrs); // 添加属性集合
- skinViews.add(skinView); // 添加View
- Log.e(Tag,"添加的View:"+ name + " skinView: "+ skinView );
- }
-
-
- // if(name.equals("ImageView")){
- // TextView textView=new TextView(MainActivity.this);
- // textView.setText("拦截");
- // return textView;
- // }
-
- return view;
- }
- });
-
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- showImg = findViewById(R.id.showImg);
- showText = findViewById(R.id.showText);
-
-
- if (ContextCompat.checkSelfPermission(this,
- Manifest.permission.READ_EXTERNAL_STORAGE)
- != PackageManager.PERMISSION_GRANTED)
- {
-
- ActivityCompat.requestPermissions(this,
- new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
- 110);
- } else
- {
-
- }
-
- // myImageView = findViewById(R.id.myImageView);
- // Log.e("denganzhi",myImageView.getClass().toString());
- }
-
-
- // 比如根据 @123456 获取 颜色图片 名称
- public String getResName(String attrValue) {
- // 如果 android:background="#ffffff" 那么过滤掉,不换皮肤
- if (attrValue.startsWith("@") || attrValue.startsWith("?")) {
- attrValue = attrValue.substring(1);
- // 根据id 去插件包中找 资源
- int resId = Integer.parseInt(attrValue);
-
- // 获取资源类型
- String resourceTypeName = getResources().getResourceTypeName(resId);
- Log.e(Tag,"resourceTypeName: " +resourceTypeName);
-
- // 通过id 获取资源名字
-
- return getResources().getResourceEntryName(resId);
-
- }
-
- return null;
-
- }
-
-
- // 比如根据 @123456 获取 颜色图片 名称 btn_bg
- public String getResNamebyResId(int resId) {
- // 如果 android:background="#ffffff" 那么过滤掉,不换皮肤
- // 通过id 获取资源名字
- return getResources().getResourceEntryName(resId);
- }
- // 类型 mip 、color、drawable、layout.....
- public String getResTypebyResId(int resId){
- // 获取资源类型
- String resourceTypeName = getResources().getResourceTypeName(resId);
- return resourceTypeName;
- }
-
-
-
- Resources mSkinResources =null;
- String packageName=null;
- public void changeSkin(View view) {
-
- Log.e(Tag,"换肤List:"+skinViews);
-
- try {
- // 加载系统下皮肤
- String skinPath= Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator +"red.apk";
- File file=new File(skinPath);
- if(!file.exists()){
- Log.e(Tag,"文件不存在");
- return;
- }
- AssetManager asset = AssetManager.class.newInstance();
- // 读取 本地一个 .skin 里面资源
- Resources superRes = getResources();
- // 添加本地下载好的资源皮肤 Native 层 c和 c++ 怎么搞得
- Method method = AssetManager.class.getDeclaredMethod("addAssetPath",String.class);
- method.setAccessible(true);
- // 执行反射方法
- method.invoke(asset,skinPath);
- mSkinResources=new Resources(asset,superRes.getDisplayMetrics(),superRes.getConfiguration());
- // 获取资源文件的包名
- packageName= getPackageManager().getPackageArchiveInfo(skinPath,PackageManager.GET_ACTIVITIES).packageName ;
-
-
-
- if(!TextUtils.isEmpty(packageName) && mSkinResources!=null ){
- for(int i=0;i<skinViews.size();i++){
- SkinView skinView= skinViews.get(i);
- View viewSkin= skinView.getView();
- List<SkinAttr> skinAttrs= skinView.getmAttrs();
- for (int j=0;j<skinAttrs.size();j++){
-
- SkinAttr skinAttr= skinAttrs.get(j);
- String skyType= skinAttr.getSkyType();
- int mResId= skinAttr.getmResId();
-
- String resName= getResNamebyResId(mResId);
- String resType = getResTypebyResId(mResId);
-
-
- if(TextUtils.isEmpty(resName)){ // 没有找到
- continue;
- }
- // attrName:textColor attValue:@2130968660 resName:text_color
- // attrName:textColor attValue:?16843827 resName:colorPrimary
-
-
- Log.e(Tag, "packageName:"+packageName+" resName:"+resName + " resType:"+ resType);
-
- // 需要换肤的背景 background
- if(skyType.equals("background")){
-
- // 背景可以是图片 也可能是颜色
- if(resType.equals("color")){
- int background= mSkinResources.getColor(mResId);
- viewSkin.setBackgroundColor((Integer) background);
- }else{
- Drawable drawable= getDrawableByName(resName);
- ImageView imageView= (ImageView) viewSkin;
- imageView.setBackground(drawable);
- }
-
- // 需要换肤的 textcolor
- }else if(skyType.equals("textColor")){
-
- // int colorId= mSkinResources.getIdentifier("text_color","color",packageName);
- // ColorStateList colorStateList= mSkinResources.getColorStateList(resId);
- ColorStateList colorStateList= getColorByName(resName);
- TextView textView= (TextView)viewSkin;
- textView.setTextColor(colorStateList);
- }else if(skyType.equals("src")){
-
- }else if(skyType.equals("skinTypeface")){
-
- // 如何修改字体,思路
-
- // 可以判断这里是 viewSkin 是TextView Button 然后设置 , 第二个 参数传递不同路径即可
- // textView.setTypeface( Typeface.createFromAsset(this.getAssets(), "fonts/CodeBold.ttf"));
- }
- }
- }
- }
-
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (InstantiationException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- }
-
- }
-
-
-
- public ColorStateList getColorByName(String resName){
- int resId= mSkinResources.getIdentifier(resName,"color",packageName);
- ColorStateList colorStateList= mSkinResources.getColorStateList(resId);
- Log.e(Tag,"colorId:"+resId);
- return colorStateList;
- }
-
- public void restoreSkin(View view) {
-
-
- }
-
-
- private static final String[] mClassPrefixlist = {
- "android.widget.",
- "android.view.",
- "android.webkit."
- };
- private View createViewFromTag(String name, Context context, AttributeSet attrs) {
- //包含自定义控件
- if (-1 != name.indexOf(".")) {
- return null;
- }
- //
- View view = null;
- for (int i = 0; i < mClassPrefixlist.length; i++) {
- view = createView(mClassPrefixlist[i] + name, context, attrs);
- if(null != view){
- break;
- }
- }
- return view;
- }
- private static final Class[] mConstructorSignature =
- new Class[]{Context.class, AttributeSet.class};
- private static final HashMap<String, Constructor<? extends View>> mConstructor =
- new HashMap<String, Constructor<? extends View>>();
- private View createView(String name, Context context, AttributeSet attrs) {
- Constructor<? extends View> constructor = mConstructor.get(name);
- if (constructor == null) {
- try {
- //通过全类名获取class
- Class<? extends View> aClass = context.getClassLoader().loadClass(name).asSubclass(View.class);
- //获取构造方法
- constructor = aClass.getConstructor(mConstructorSignature);
- mConstructor.put(name, constructor);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- if (null != constructor) {
- try {
- return constructor.newInstance(context, attrs);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- return null;
- }
-
-
- }
SkinView.java
- package mk.denganzhi.com.zhiwenku;
-
- import android.view.View;
-
- import java.util.List;
-
- /**
- * Created by Administrator on 2020/5/24.
- */
-
- public class SkinView {
-
- private View view;
- private String viewName;
-
- private List<SkinAttr> mAttrs;
-
- public View getView() {
- return view;
- }
-
- public void setView(View view) {
- this.view = view;
- }
-
- public List<SkinAttr> getmAttrs() {
- return mAttrs;
- }
-
- public String getViewName() {
- return viewName;
- }
-
- public void setViewName(String viewName) {
- this.viewName = viewName;
- }
-
- public void setmAttrs(List<SkinAttr> mAttrs) {
- this.mAttrs = mAttrs;
- }
-
- @Override
- public String toString() {
- return "SkinView{" +
- "view=" + view +
- ", viewName='" + viewName + '\'' +
- ", mAttrs=" + mAttrs +
- '}';
- }
- }
SkinAttr.java
-
- public class SkinAttr {
-
- private String mResName;
- private String skyType;
- private int mResId;
-
-
-
-
- public String getmResName() {
- return mResName;
- }
-
- public void setmResName(String mResName) {
- this.mResName = mResName;
- }
-
- public String getSkyType() {
- return skyType;
- }
-
- public void setSkyType(String skyType) {
- this.skyType = skyType;
- }
-
- public int getmResId() {
- return mResId;
- }
-
- public void setmResId(int mResId) {
- this.mResId = mResId;
- }
-
- @Override
- public String toString() {
- return "SkinAttr{" +
- "mResName='" + mResName + '\'' +
- ", skyType='" + skyType + '\'' +
- ", mResId=" + mResId +
- '}';
- }
- }
源码地址:https://download.csdn.net/download/dreams_deng/12454900
2023.6.17重新理解;
1. 看源码,
【大概原理:Android动态换肤框架-换肤原理 - 简书】
- -- 从 setContentView(R.layout.activity_main); 点入:
- AppCompatDelegateImpl.java
-
- public void setContentView(int resId) {
- LayoutInflater.from(mContext).inflate(resId, contentParent);
- }
-
- LayoutInflater.java
- public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
- //Resources 可以理解为xml解析器
- final Resources res = getContext().getResources();
- }
-
- public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
- //获取节点name
- final String name = parser.getName();
- //创建View通过工厂或者
- final View temp = createViewFromTag(root, name, inflaterContext, attrs);
- root.addView(temp, params);
- }
-
- View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
- boolean ignoreThemeAttr) {
- //如果有 mFactory2 工厂, 通过 mFactory2 工厂创建
- if (mFactory2 != null) {
- view = mFactory2.onCreateView(parent, name, context, attrs);
- // //如果有 mFactory 工厂, 通过 mFactory 工厂创建
- } else if (mFactory != null) {
- view = mFactory.onCreateView(name, context, attrs);
- } else {
- view = null;
- }
- //如果没有工厂
- 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;
- }
- }
- }
-
-
-
- public final View createView(String name, String prefix, AttributeSet attrs)
- throws ClassNotFoundException, InflateException {
- //获取控件的.class类
- clazz = mContext.getClassLoader().loadClass(
- prefix != null ? (prefix + name) : name).asSubclass(View.class);
- //通过放射获取构造函数
- constructor = clazz.getConstructor(mConstructorSignature);
- constructor.setAccessible(true);
- //创建对象!
- final View view = constructor.newInstance(args);
- }
-
-
-
- public final View createView(String name, String prefix, AttributeSet attrs)
- throws ClassNotFoundException, InflateException {
- //获取控件的.class类
- clazz = mContext.getClassLoader().loadClass(
- prefix != null ? (prefix + name) : name).asSubclass(View.class);
- //通过放射获取构造函数
- constructor = clazz.getConstructor(mConstructorSignature);
- constructor.setAccessible(true);
- //创建对象!
- final View view = constructor.newInstance(args);
- }
大概原理就是系统 解析xml控件,createViewFromTag()的时候, 如果mFactory2不能与空,使用mFactory2 来创建控件,那么在app的onCreate中可以重写 Factory2,
创建系统控件,自定义控件, 拦截获取控件的属性保存到一个集合中
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- LayoutInflater layoutInflater= LayoutInflater.from(this);
- // setFactory2
- LayoutInflaterCompat.setFactory2(layoutInflater, new LayoutInflater.Factory2() {
- @Override
- public View onCreateView(String name, Context context, AttributeSet attrs) {
-
- Log.e(Tag ,"创建View被拦截1:"+name);
- return null;
- }
-
- @Override
- public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
- // 反射 classLoader
- View view = createViewFromTag(name, context, attrs);
- // 自定义View
- if(null == view){
- view = createView(name, context, attrs);
- }
-
- Log.e(Tag ,"onCreateView:"+name);
-
-
- // SkinView skinView=new SkinView(); // 控件集合
- // skinView.setView(view);
-
- // List<SkinAttr> skinAttrs=new ArrayList<>(); // 每一个控件存放属性的集合
- int attrLength= attrs.getAttributeCount();
- for (int index=0;index<attrLength; index++){
- // 获取名称,值
- String attrName = attrs.getAttributeName(index);
- // 不需要换肤 属性值
- if (!mAttributes.contains(attrName)) {
- continue;
- }
- // 不符合 换肤条件
- String attrValue = attrs.getAttributeValue(index);
- if (attrValue.startsWith("#")) {
- continue;
- }
- /**
- *
- * android:background="?android:colorPrimary" 使用系统颜色值 ?16843827
- android:background="#000000" 不符合换肤条件
- android:background="@mipmap/ic_launcher" 使用之定义 @2131361793
- 所有的 value 都会别转化为 int 值
- */
- // attrName:background attValue:@2131361792
-
- int resId = 0 ;
- if (attrValue.startsWith("@") || attrValue.startsWith("?")) {
- Log.e("TAG", attrValue);
- String newattrValue = attrValue.substring(1);
- resId= Integer.parseInt(newattrValue);
- //这个值就是 android.R.color.colorPrimary 的id
-
- // int color= getResources().getColor(resId);
-
- Log.e("TAG",attrName+" "+ attrValue +" "+ Integer.toHexString(resId) +" " );
-
- // <attr name="colorPrimary" format="color" />
- //
-
- if(attrValue.startsWith("@")){
- // ff6200ee 直接获取默认颜色值
- Log.e("TAG", Integer.toHexString(getResources().getColor(resId)) +" " );
- // type: color name: black + 包名,获取插件中的颜色
- String resourceTypeName =getResources().getResourceTypeName(resId);
- String resourceEntryName = getResources().getResourceEntryName(resId);
- Log.e("TAG","@: "+resourceTypeName+ " "+ resourceEntryName);
- }
-
-
- // 需要通过如下方式获取颜色值:
- // android:textColor="?android:colorPrimary"
- // resId=android:colorPrimary attr.xml 的id
- // val值是:ff6200ee
- if(attrValue.startsWith("?")){
- int[] attrs1=new int[]{resId};
- int[] resIds = new int[attrs1.length];
- TypedArray a = context.obtainStyledAttributes(attrs1);
- for (int i = 0; i < attrs1.length; i++) {
- resIds[i] = a.getResourceId(i, 0);
- Log.e("TAG", " " + Integer.toHexString(getResources().getColor( resIds[i])) + " " );
- }
- a.recycle();
-
- String resourceTypeName =getResources().getResourceTypeName( resIds[0] );
- String resourceEntryName = getResources().getResourceEntryName(resIds[0]);
- // ?: color purple_500
- Log.e("TAG","?: "+resourceTypeName+ " "+ resourceEntryName);
- }
-
-
- if(attrName.equals("textColor")){
- // int colorId= getResources().getColor(resId);
- // Log.e("TAG", "hex:" + Integer.toHexString(colorId));
- }else{
-
- }
- }
-
- if(resId==0){ //不符合条件
- continue;
- }
-
-
- // SkinAttr skinAttr =new SkinAttr();
- // skinAttr.setSkyType(attrName);
- // // skinAttr.setmResName(resName);
- // skinAttr.setmResId(resId);
- // skinAttrs.add(skinAttr);
- }
- // if(skinAttrs.size()>0 ){
- // skinView.setViewName(name);
- // skinView.setmAttrs(skinAttrs); // 添加属性集合
- // skinViews.add(skinView); // 添加View
- // Log.e(Tag,"添加的View:"+ name + " skinView: "+ skinView );
- // }
-
-
- // if(name.equals("ImageView")){
- // TextView textView=new TextView(MainActivity.this);
- // textView.setText("拦截");
- // return textView;
- // }
-
- return view;
- }
- });
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- imageView = findViewById(R.id.imageView);
- textView = findViewById(R.id.textView);
- test_color2 = findViewById(R.id.test_color2);
-
- test_color2.setTextColor(getResources().getColor(R.color.test_color));
- }
-
- }
通过上面代码采集完毕,保存起来
- 1. setFactory2 设置,采集系统控件需要换肤属性
-
- 装起来:
- key:控件
- kev-value: 属性-app属性值!
-
- private List<SkinView> skinViews = new ArrayList<>();
-
- View view; 比如textView
- List<SkinPain> skinPains;
-
- static class SkinPain {
- String attributeName; //比如textcolor,
- int resId; // value值
-
- public SkinPain(String attributeName, int resId) {
- this.attributeName = attributeName;
- this.resId = resId;
- }
- }
有一个东西:可以读取apk皮肤包的 drawable, string, 给app用
博客:(2条消息) Android应用程序插件化研究之AssertManager_weixin_34159110的博客-CSDN博客
上面已经获取到了每个控件属性值和 控件属性名
根据值,获取对应的
resourceTypeName、resourceEntryName
-
- /**
- *
- * android:background="?android:colorPrimary" 使用系统颜色值 ?16843827
- android:background="#000000" 不符合换肤条件
- android:background="@mipmap/ic_launcher" 使用之定义 @2131361793
- 所有的 value 都会别转化为 int 值
- */
- // attrName:background attValue:@2131361792
-
- int resId = 0 ;
- if (attrValue.startsWith("@") || attrValue.startsWith("?")) {
- Log.e("TAG", attrValue);
- String newattrValue = attrValue.substring(1);
- resId= Integer.parseInt(newattrValue);
- //这个值就是 android.R.color.colorPrimary 的id
-
- // int color= getResources().getColor(resId);
-
- Log.e("TAG",attrName+" "+ attrValue +" "+ Integer.toHexString(resId) +" " );
-
- // <attr name="colorPrimary" format="color" />
- //
-
-
- if(attrValue.startsWith("@")){
- // ff6200ee 直接获取默认颜色值
- Log.e("TAG", Integer.toHexString(getResources().getColor(resId)) +" " );
- // type: color name: black + 包名,获取插件中的颜色
- String resourceTypeName =getResources().getResourceTypeName(resId);
- String resourceEntryName = getResources().getResourceEntryName(resId);
- Log.e("TAG","@: "+resourceTypeName+ " "+ resourceEntryName);
- }
-
-
- // 需要通过如下方式获取颜色值:
- // android:textColor="?android:colorPrimary"
- // resId=android:colorPrimary attr.xml 的id
- // val值是:ff6200ee
- if(attrValue.startsWith("?")){
- int[] attrs1=new int[]{resId};
- int[] resIds = new int[attrs1.length];
- TypedArray a = context.obtainStyledAttributes(attrs1);
- for (int i = 0; i < attrs1.length; i++) {
- resIds[i] = a.getResourceId(i, 0);
- Log.e("TAG", " " + Integer.toHexString(getResources().getColor( resIds[i])) + " " );
- }
- a.recycle();
-
- String resourceTypeName =getResources().getResourceTypeName( resIds[0] );
- String resourceEntryName = getResources().getResourceEntryName(resIds[0]);
- // ?: color purple_500
- Log.e("TAG","?: "+resourceTypeName+ " "+ resourceEntryName);
- }
根据上一篇
- ------------------------------------------------------------------------------------------------------
- 1. 通过分析上面源码,自己写factory2, 创建view, 把View和需要换肤的属性保存起来
-
- ①. 如果有多个activity如何解决: application.registerActivityLifecycleCallbacks(skinActivityLifecycle);
- 所有的actiivty创建以后都会走这里个方法
-
- 走这个方法的时候,设置factory2,那么就可以获取到所有属性了
-
-
- ②.
- try {
- //Android 布局加载器 使用 mFactorySet 标记是否设置过Factory
- //如设置过抛出一次
- //设置 mFactorySet 标签为false
- Field field = LayoutInflater.class.getDeclaredField("mFactorySet");
- field.setAccessible(true);
- field.setBoolean(layoutInflater, false);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
-
-
-
- 2. 设置上面属性以后,获取到所有需要替换控件的 集合,然后走, 使用在 factory2中采集所有控件属性可以替换的
- setContentView(R.layout.activity_main); 方法!
-
- ***** 总结:
-
-
- 替换东西如下;
-
- ====0. background(颜色,图片),src,drawableLeft,drawableTop,drawableRight,drawableBottom,textColor
-
- 如何替换:
- 主apk和资源apk
- 根据 android:background="@mipmap/btn"
- drawable/bnt找到图片id , 根据id getResources获取图片,
-
-
- 写的博客; https://blog.csdn.net/dreams_deng/article/details/106320048
-
-
-
- ====1. 状态栏实现逻辑 : SkinThemeUtils.updateStatusBarColor
-
- 状态栏颜色;
- 如果配置 R.attr.colorPrimaryDark 用这个
- 没有配置用系统的: android.R.attr.statusBarColor, android.R.attr
- .navigationBarColor
-
- 可以自己设置: getWindow().setStatusBarColor(SkinResources.getInstance().getColor(statusBarColorResId[0]));
-
-
- android:textColor="?android:colorPrimary"
- //对应三个值:
- //resName: btn
- // resType: drawable
- // resId: R.id.btn对用的R文件的值
- // value值是 R.android.colorPrimary的R 值,
- // 然后在通过这个id获取颜色,
- // ColorStateList colorStateList = mSkinResources.getColorStateList(colorId);
- //
-
-
- ====2. 字体换肤
-
- 如何实现:
- app中: <string name="typeface"/>
- 皮肤包中:<string name="typeface">font/global.ttf</string>
- 读取皮肤包的val,
- 根据:Typeface.createFromAsset(mSkinResources.getAssets(), skinTypefacePath);
- skinTypefacePath 就是 font/global.ttf
- 如果是app包中:Typeface.createFromAsset(mAppResources.getAssets(), skinTypefacePath);
- 设置给textview即可
-
-
-
- 颜色:
- mSkinResources.getColor(skinId)
- 多个颜色:
- mSkinResources.getColorStateList(skinId);
- 图片:
- mSkinResources.getDrawable(skinId);
- 字体:
- Typeface.createFromAsset(mSkinResources.getAssets(), skinTypefacePath);
-
-
- 1. setFactory2 设置,采集系统控件需要换肤属性
-
- 装起来:
- key:控件
- kev-value: 属性-app属性值!
-
- private List<SkinView> skinViews = new ArrayList<>();
-
- View view; 比如textView
- List<SkinPain> skinPains;
-
- static class SkinPain {
- String attributeName; //比如textcolor,
- int resId; // value值
-
- public SkinPain(String attributeName, int resId) {
- this.attributeName = attributeName;
- this.resId = resId;
- }
- }
- 2. skinViews 获取完毕,下载皮肤apk
-
- 3. 开始替换皮肤
-
-
-
- ==== 3. recycleview 中的item 替换字体
-
- === 4. 自定义view实现 ,自定义属性
- 定义一个接口, public interface SkinViewSupport , 自定义控件实现, 换肤的时候调用一下方法,在apk包读取对应的属性,替换即可
-
-
-
- 上面缺点:setFactory2如果android改源码了,无法实现了!!
-
- 如何实现:
- 1.把皮肤app包放在assert下,直接去读取app皮肤包,替换!!
-
-
- ************************************************************************************************************************************************************************************
-
-
-
- Android9.0 直接hide 调了一些api,不是所有,屏蔽放射,
- 把 api 放入黑名单中,不可以放射!!
-
-
-
- /****
- * 夜间模式和日间模式实现:
- * 夜间读取: values-night 颜色值
- * 日间: 读取values 中的值
- */
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。