Android实现插件化换肤(二)

Android源码时序图如下所示:

App源码如下所示:

package com.study.skin

import android.app.Application
import com.study.skinlib.SkinManager

class App : Application() {
    
    

    override fun onCreate() {
    
    
        super.onCreate()
        SkinManager.init(this)
    }
}

SkinManager源码如下所示,其源码如下所示:

package com.study.skinlib

import android.app.Application
import android.content.pm.PackageManager
import android.content.res.AssetManager
import android.content.res.Resources
import android.text.TextUtils
import com.study.skinlib.utils.SkinResource
import java.util.*

/**
 * 皮肤管理类
 */
class SkinManager : Observable {
    
    
    private lateinit var mApplication: Application


    private constructor(application: Application) {
    
    
        mApplication = application
        //初始化皮肤路径首选项
        SkinPreference.init(application)
        //资源管理类 用于从app/皮肤中加载资源
        SkinResource.init(application)
        //给Application注册Activity生命周期监听类
        application.registerActivityLifecycleCallbacks(ActivityLifecycleCallbacksImpl(this))
        //加载上次使用保存的皮肤
        SkinPreference.getInstance()?.getSkin()?.apply {
    
    
            loadSkin(this)
        }
    }

    /**
     * 加载皮肤
     */
    fun loadSkin(skinPath: String?) {
    
    
        if (TextUtils.isEmpty(skinPath)) {
    
    
            //还原默认皮肤
            SkinPreference.getInstance()?.reset()
            SkinResource.getInstance()?.reset()
        } else {
    
    
            try {
    
    
                //宿主app的 resources;
                val appResource = mApplication.getResources()
                //
                //反射创建AssetManager 与 Resource
                val assetManager = AssetManager::class.java.newInstance()
                //资源路径设置 目录或压缩包
                val addAssetPath = assetManager.javaClass.getMethod(
                        "addAssetPath",
                        String::class.java
                )
                addAssetPath.invoke(assetManager, skinPath)

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

                //获取外部Apk(皮肤包) 包名
                val mPm = mApplication.getPackageManager()
                val info = mPm.getPackageArchiveInfo(
                        skinPath, PackageManager
                        .GET_ACTIVITIES
                )
                val packageName = info!!.packageName
                SkinResource.getInstance()?.applySkin(skinResource, packageName)

                //记录
                skinPath?.apply {
    
    
                    SkinPreference.getInstance()?.setSkin(this)
                }


            } catch (e: Exception) {
    
    
                e.printStackTrace()
            }

        }
        //通知采集的View 更新皮肤
        //被观察者改变 通知所有观察者
        setChanged()
        notifyObservers(null)
    }

    companion object {
    
    
        @Volatile
        private var instance: SkinManager? = null

        /**
         * 初始化操作
         */
        fun init(mApplication: Application) {
    
    
            if (instance == null) {
    
    
                synchronized(this) {
    
    
                    if (instance == null) {
    
    
                        instance = SkinManager(mApplication)
                    }
                }
            }
        }

        fun getInstance() = instance
    }
}

SkinPreference源码如下所示:

package com.study.skinlib

import android.app.Application
import android.content.Context
import android.content.SharedPreferences

/**
 * 皮肤路径首选项设置
 */
class SkinPreference {
    
    
    private lateinit var mPref: SharedPreferences


    private constructor(context: Context) {
    
    
        mPref = context.getSharedPreferences(SKIN_SHARED, Context.MODE_PRIVATE)
    }

    /**
     * 设置皮肤资源路径
     */
    fun setSkin(skinPath: String) = mPref.edit().putString(KEY_SKIN_PATH, skinPath).apply()


    /**
     * 重置皮肤路径
     */
    fun reset() = mPref.edit().remove(KEY_SKIN_PATH).apply()


    /**
     * 获取皮肤路径
     */
    fun getSkin(): String? = mPref.getString(KEY_SKIN_PATH, null)

    companion object {
    
    
        val SKIN_SHARED = "skins"
        val KEY_SKIN_PATH = "skin-path"


        @Volatile
        private var instance: SkinPreference? = null

        fun init(context: Context) {
    
    
            if (instance == null) {
    
    
                synchronized(this) {
    
    
                    if (instance == null) {
    
    
                        instance = SkinPreference(context)
                    }
                }
            }
        }

        fun getInstance() = instance
    }
}

SkinResource源码如下所示:

package com.study.skinlib.utils

import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.text.TextUtils

class SkinResource {
    
    

    //app原始的resource
    private lateinit var mAppResource: Resources
    //皮肤的resource
    private var mSkinResources: Resources? = null

    //皮肤包的名字
    private lateinit var mSkinPkgName: String
    //是否使用默认皮肤包
    private var isDefaultSkin = true

    private constructor(context: Context) {
    
    
        mAppResource = context.resources
    }

    fun applySkin(resources: Resources, pkgName: String) {
    
    
        mSkinResources = resources
        mSkinPkgName = pkgName
        //是否使用默认皮肤
        isDefaultSkin = TextUtils.isEmpty(pkgName) || resources == null
    }


    /**
     * 1.通过原始app中的resId(R.color.XX)获取到自己的 名字
     * 2.根据名字和类型获取皮肤包中的ID
     */
    fun getIdentifier(resId: Int): Int {
    
    
        if (isDefaultSkin) {
    
    
            return resId
        }
        //获取资源的名称和类型
        val resName = mAppResource.getResourceEntryName(resId)
        val resType = mAppResource.getResourceTypeName(resId)
        //通过插件的Resources获取到插件中相同名称和类型的id
        return mSkinResources?.getIdentifier(resName, resType, mSkinPkgName) ?: 0
    }

    /**
     * 输入主APP的ID,到皮肤APK文件中去找到对应ID的颜色值
     *
     * @param resId
     * @return
     */
    fun getColor(resId: Int): Int {
    
    
        if (isDefaultSkin) {
    
    
            return mAppResource.getColor(resId)
        }
        val skinId = getIdentifier(resId)
        return if (skinId == 0) {
    
    
            mAppResource.getColor(resId)
        } else mSkinResources?.getColor(skinId) ?: 0
    }

    fun getColorStateList(resId: Int): ColorStateList? {
    
    
        if (isDefaultSkin) {
    
    
            return mAppResource.getColorStateList(resId)
        }
        val skinId = getIdentifier(resId)
        return if (skinId == 0) {
    
    
            mAppResource.getColorStateList(resId)
        } else mSkinResources?.getColorStateList(skinId)
    }

    fun getDrawable(resId: Int): Drawable? {
    
    
        if (isDefaultSkin) {
    
    
            return mAppResource.getDrawable(resId)
        }
        //通过 app的resource 获取id 对应的 资源名 与 资源类型
        //找到 皮肤包 匹配 的 资源名资源类型 的 皮肤包的 资源 ID
        val skinId = getIdentifier(resId)
        return if (skinId == 0) {
    
    
            mAppResource.getDrawable(resId)
        } else mSkinResources?.getDrawable(skinId)
    }


    /**
     * 可能是Color 也可能是drawable
     *
     * @return
     */
    fun getBackground(resId: Int): Any? {
    
    
        //根据资源id获取资源类型
        val resourceTypeName = mAppResource.getResourceTypeName(resId)

        return if ("color" == resourceTypeName) {
    
    
            getColor(resId)
        } else {
    
    
            // drawable
            getDrawable(resId)
        }
    }


    /**
     * 重置资源
     */
    fun reset() {
    
    
        mSkinResources = null
        mSkinPkgName = ""
        isDefaultSkin = true
    }

    companion object {
    
    
        @Volatile
        private var instance: SkinResource? = null

        /**
         * 初始化操作
         */
        fun init(context: Context) {
    
    
            if (instance == null) {
    
    
                synchronized(this) {
    
    
                    if (instance == null) {
    
    
                        instance = SkinResource(context)
                    }
                }
            }
        }

        fun getInstance() = instance
    }
}

ActivityLifecycleCallbacksImpl源码如下所示:

package com.study.skinlib

import android.app.Activity
import android.app.Application
import android.os.Bundle
import android.util.ArrayMap
import android.view.LayoutInflater
import androidx.core.view.LayoutInflaterCompat
import com.study.skinlib.utils.SkinThemeUtils
import java.util.*

/**
 * Activity生命周期回调管理类
 */
class ActivityLifecycleCallbacksImpl : Application.ActivityLifecycleCallbacks {
    
    

    private var mObserable: Observable
    private val mLayoutInflaterFactories = ArrayMap<Activity, SkinLayoutInflaterFactory>()

    public constructor(observable: Observable){
    
    
        mObserable = observable
    }

    /**
     * Activity的onCreate方法中dispatchActivityCreated(savedInstanceState);分发事件
     * setContentView方法之前调用
     */
    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
    
    
        /**
         *  更新状态栏
         */
        SkinThemeUtils.updateStatusBarColor(activity)

        /**
         *  更新布局视图
         */
        //获得Activity的布局加载器
        val layoutInflater = activity.layoutInflater

        try {
    
    
            //Android 布局加载器 使用 mFactorySet 标记是否设置过Factory
            //如设置过抛出一次
            //设置 mFactorySet 标签为false
            val field = LayoutInflater::class.java.getDeclaredField("mFactorySet")
            field.isAccessible = true
            field.setBoolean(layoutInflater, false)
        } catch (e: Exception) {
    
    
            e.printStackTrace()
        }


        //使用factory2 设置布局加载工程
        val skinLayoutInflaterFactory = SkinLayoutInflaterFactory(activity)
        //内部也是通过反射来修改layoutInflater的值
        LayoutInflaterCompat.setFactory2(layoutInflater, skinLayoutInflaterFactory)
        mLayoutInflaterFactories[activity] = skinLayoutInflaterFactory

        mObserable.addObserver(skinLayoutInflaterFactory)
    }

    override fun onActivityPaused(activity: Activity) {
    
    }

    override fun onActivityStarted(activity: Activity) {
    
    }

    override fun onActivityDestroyed(activity: Activity) {
    
    }

    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
    
    }

    override fun onActivityStopped(activity: Activity) {
    
    }


    override fun onActivityResumed(activity: Activity) {
    
    }
}

SkinLayoutInflaterFactory实现了Factory2,其源码如下所示:

package com.study.skinlib

import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import com.study.skinlib.utils.SkinThemeUtils
import java.lang.Exception
import java.lang.reflect.Constructor
import java.util.*

/**
 * 皮肤控件填充器
 */
class SkinLayoutInflaterFactory : LayoutInflater.Factory2, Observer {
    
    
    lateinit var mActivity: Activity
    lateinit var skinAttribute: SkinAttribute

    public constructor(activity: Activity) {
    
    
        mActivity = activity
        skinAttribute = SkinAttribute()
    }


    /**
     * 如果利用Factory2创建控件,那么就会通过该方法来创建
     */
    override fun onCreateView(
        parent: View?,
        name: String,
        context: Context,
        attrs: AttributeSet
    ): View? {
    
    
        var view = createSDKView(name, context, attrs)
        if (view == null) {
    
    
            view = createView(name, context, attrs)
        }

        //记录我们所创建的view
        if (view != null) {
    
    
            //加载控件属性
            //加载属性
            skinAttribute.look(view, attrs)
        }
        return view
    }

    fun createSDKView(
        name: String,
        context: Context,
        attrs: AttributeSet
    ): View? {
    
    
        //如果包含,说明是自定义控件或者Google提供的扩展view
        if (-1 != name.indexOf(".")) {
    
    
            return null
        }

        //不包含".",则需要在name前面加上包名,通过反射创建控件
        for (prefix in mClassPrefixList) {
    
    
            var view = createView(prefix + name, context, attrs)
            if (view != null) {
    
    
                return view
            }
        }
        return null
    }

    /**
     * 创建view
     */
    private fun createView(qualityName: String, context: Context, attrs: AttributeSet): View? {
    
    
        var constructor = findConstructor(context, qualityName)
        return constructor?.newInstance(context, attrs) ?: null
    }

    /**
     * 查找构造函数
     */
    private fun findConstructor(context: Context, qualityName: String): Constructor<out View>? {
    
    
        var constructor = mConstructorMap[qualityName]
        if (constructor == null) {
    
    
            try {
    
    
                val clazz = context.classLoader.loadClass(qualityName).asSubclass(View::class.java)
                constructor = clazz.getConstructor(*mConstructorSignature)
                mConstructorMap[qualityName] = constructor
            } catch (e: Exception) {
    
    
                Log.e("factory::", e.message)
            }
        }
        return constructor
    }

    /**
     * Factory2的实现类不调用该接口
     */
    override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? = null

    /**
     * 观察者接受到被观察的改变通知调用该方法
     */
    override fun update(o: Observable?, arg: Any?) {
    
    
        SkinThemeUtils.updateStatusBarColor(mActivity)
        skinAttribute.applySkin()
    }

    companion object {
    
    
        /**
         * 控件的报名
         */
        val mClassPrefixList = arrayOf(
            "android.widget.",
            "android.webkit.",
            "android.app.",
            "android.view."
        )

        /**
         * 创建view的构造函数
         */
        val mConstructorSignature = arrayOf(
            Context::class.java, AttributeSet::class.java
        )

        /**
         * view与构造函数的map
         */
        val mConstructorMap = hashMapOf<String, Constructor<out View>>()
    }
}

SkinAttribute的源码如下所示:

package com.study.skinlib

import android.util.AttributeSet
import android.view.View
import com.study.skinlib.data.SkinPair
import com.study.skinlib.utils.SkinThemeUtils

/**
 * 页面控件属性管理器
 */
class SkinAttribute {
    
    
    private val mAttributes = arrayListOf<String>()

    init {
    
    
        mAttributes.add("background")
        mAttributes.add("src")
        mAttributes.add("textColor")
        mAttributes.add("drawableLeft")
        mAttributes.add("drawableTop")
        mAttributes.add("drawableRight")
        mAttributes.add("drawableBottom")
    }

    //记录页面控件中View的属性
    private val mSkinViews = arrayListOf<SkinView>()

    /**
     * 实施替换
     */
    fun look(view: View, attrs: AttributeSet) {
    
    
        var mSkinPars = arrayListOf<SkinPair>()
        //0<attrs.attributeCount
        for (i in 0 until attrs.attributeCount) {
    
    
            //获取属性名
            val attributeName = attrs.getAttributeName(i)
            if (mAttributes.contains(attributeName)) {
    
    
                //获取属性值,宿主Apk的android:background="@color/main_bg"中@color/main_bg的引用值,例如@2130968629
                val attributeValue = attrs.getAttributeValue(i)
                //例如针对color的属性的属性值写死
                if (attributeValue.startsWith("#")) {
    
    
                    continue
                }
                var resId = 0
                // 以 ?开头的表示使用 属性
                if (attributeValue.startsWith("?")) {
    
    
                    val attrId = Integer.parseInt(attributeValue.substring(1))
                    resId = SkinThemeUtils.getResId(view.context, intArrayOf(attrId))[0]
                } else {
    
    
                    // 正常以 @ 开头
                    resId = Integer.parseInt(attributeValue.substring(1))
                }

                val skinPair = SkinPair(attributeName, resId)
                mSkinPars.add(skinPair)
            }
        }

        if (!mSkinPars.isEmpty() || view is SkinViewSupport) {
    
    
            val skinView = SkinView(view, mSkinPars)
            // 如果选择过皮肤 ,调用 一次 applySkin 加载皮肤的资源
            skinView.applySkin()
            mSkinViews.add(skinView)
        }
    }

    /**
     *  对所有的view中的所有的属性进行皮肤修改
     */
    fun applySkin() {
    
    
        for (mSkinView in mSkinViews) {
    
    
            mSkinView.applySkin()
        }
    }

}

SkinView的源码如下所示:

package com.study.skinlib

import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.ViewCompat
import com.study.skinlib.data.SkinPair
import com.study.skinlib.utils.SkinResource

/**
 * 控件View及其使用的属性
 */
class SkinView(var view: View, var skinPairs: List<SkinPair>) {
    
    

    /**
     * 对控件的View修改其属性值
     */
    fun applySkin() {
    
    
        //自定义控件替换颜色
        if (view is SkinViewSupport) {
    
    
            (view as SkinViewSupport).applySkin()
        }

        var left: Drawable? = null
        var top: Drawable? = null
        var right: Drawable? = null
        var bottom: Drawable? = null

        for (skinPair in skinPairs) {
    
    
            when (skinPair.attributeName) {
    
    
                "background" -> {
    
    
                    var background = SkinResource.getInstance()?.getBackground(skinPair
                            .resId)
                    //背景可能是 @color 也可能是 @drawable
                    if (background is Int) {
    
    
                        view.setBackgroundColor(background)
                    } else {
    
    
                        ViewCompat.setBackground(view, background as Drawable)
                    }
                }
                "src" -> {
    
    
                    var background = SkinResource.getInstance()?.getBackground(skinPair
                            .resId)
                    if (background is Int) {
    
    
                        (view as ImageView).setImageDrawable(ColorDrawable(background))
                    } else {
    
    
                        (view as ImageView).setImageDrawable(background as Drawable)
                    }
                }
                "textColor" -> {
    
    
                    (view as TextView).setTextColor(SkinResource.getInstance()?.getColorStateList(skinPair.resId))
                }
                "drawableLeft" -> {
    
    
                    left = SkinResource.getInstance()?.getDrawable(skinPair.resId)
                }
                "drawableTop" -> {
    
    
                    top = SkinResource.getInstance()?.getDrawable(skinPair.resId)
                }
                "drawableRight" -> {
    
    
                    right = SkinResource.getInstance()?.getDrawable(skinPair.resId)
                }
                "drawableBottom" -> {
    
    
                    bottom = SkinResource.getInstance()?.getDrawable(skinPair.resId)
                }
                else -> {
    
    
                }
            }
        }

        if (view is TextView) {
    
    
            if (left != null || top != null || right != null || bottom != null) {
    
    
                (view as TextView).setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom)
            }
        }
    }
}

源码地址

猜你喜欢

转载自blog.csdn.net/Duckdan/article/details/108302962