安卓插件化之插件式换肤(1)
管理类的搭建
1 皮肤管理类 SkinManager
2 皮肤资源 SkinResource
3 皮肤支持类 SkinSupport
回到我们的BaseActivity
@Nullable
@Override
public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
// 1.创建View
View view = createView(parent, name, context, attrs);
// 2.获取皮肤资源
if (view != null) {
Log.e("TAG", "onCreateView: --》"+view );
//2.1 重支持类中加载皮肤
List<SkinAttr> mSkinAttrs = SkinSupport.getSkinAttrs(context, attrs);
// 创建皮肤View
SkinView skinView = new SkinView(view, mSkinAttrs);
// 3交给SkinManager管理
managerSkinView(skinView);
// 4 判断要不要换肤
SkinManager.getInstance().checkSkinView(skinView);
}
return view;
}
所以我们先去SkinSupport 里面完善
package com.li.skinlibrary.skin;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import com.li.skinlibrary.skin.attribute.SkinAttr;
import com.li.skinlibrary.skin.attribute.SkinType;
import java.util.ArrayList;
import java.util.List;
/**
* @author li
* 版本:1.0
* 创建日期:2020/6/30 09
* 描述:皮肤解析支持类
*/
public class SkinSupport {
public static List<SkinAttr> getSkinAttrs(Context context, AttributeSet attrs) {
//1.创建List
List<SkinAttr> skinAttrs = new ArrayList<>();
//2 创建skinAttr
for (int i = 0; i < attrs.getAttributeCount(); i++) {
String AttrName = attrs.getAttributeName(i);
String Value = attrs.getAttributeValue(i);
// Log.e("TAG", "getSkinAttrs: -->" + AttrName + "..." + Value);
// textColor...@2130968616
//获取我们需要的skinType 不是我们需要的不需要换肤
SkinType mSkinType = getSkinTypes(AttrName);
if (mSkinType!=null){
String ResName = getSkinResName(context,Value);
if (TextUtils.isEmpty(ResName)){
continue;
}
SkinAttr skinAttr = new SkinAttr(ResName,mSkinType);
skinAttrs.add(skinAttr);
}
}
return skinAttrs;
}
// textColor...@2130968616我们得到的是地址,需要找到我们需要的文件名
private static String getSkinResName(Context context, String value) {
if (value.startsWith("@")){
value = value.substring(1);
String resName = context.getResources().getResourceEntryName(Integer.parseInt(value));
// Log.e("TAG", "getSkinResName: -->"+resName );
return resName;
}
return null;
}
private static SkinType getSkinTypes(String resName) {
SkinType [] skinTypes = SkinType.values();
for (SkinType skinType : skinTypes) {
if (skinType.getResName().equals(resName)){
return skinType;
}
}
return null;
}
}
皮肤支持类基本上完成目的就是找到View里面我们需要修改的控件和属性
接下来我们需要管理我们的皮肤,皮肤管理类的完善
private void managerSkinView(SkinView skinView) {
//在管理类里获取到需要的view的列表
List<SkinView> skinViews = SkinManager.getInstance().getSkinViews(this);
if (skinViews == null) {
//是空的话创建
skinViews = new ArrayList<>();
SkinManager.getInstance().registerSkinView(this, skinViews);
}
//添加到List里面
skinViews.add(skinView);
}
到我们的SkinManager了 在写管理类之前,考虑到对于一些自定义view或者一些有特殊设置的控件来说,我门能做到的有限,但是如果不管这些东西的话,又缺失一些功能,所以在完成管理类的时候我们暴露一个接口,在换肤的时候回调过去,这样我们就能够适配大多数控件的同时,增加我们的延展性
package com.li.skinlibrary.skin.call;
import com.li.skinlibrary.skin.SkinResource;
/**
* @author li
* 版本:1.0
* 创建日期:2020/6/30 20
* 描述:换肤回调接口--------完成一些无法切换的类
*/
public interface SkinChangeCallBack {
void skinChange(SkinResource skinResource);
}
所以在基础Activity中添加接口回调以便自定义View等其他属性的view换肤使用
public abstract class BaseActivity extends AppCompatActivity implements SkinChangeCallBack {
//在baseActivity中实现这个接口
/**
* @description 切换皮肤回调
* @param
*/
@Override
public void skinChange(SkinResource skinResource) {
}
}
SkinManager
/**
* @author li
* 版本:1.0
* 创建日期:2020/6/30 09
* 描述:
*/
public class SkinManager {
private static volatile SkinManager mInstance;
private Context mContext;
private SkinResource mSkinResource;
private Map<SkinChangeCallBack,List<SkinView>> mViewMap = new HashMap<>();
private SkinManager() {
}
public static SkinManager getInstance() {
if (mInstance == null) {
synchronized (SkinManager.class) {
if (mInstance == null) {
mInstance = new SkinManager();
}
}
}
return mInstance;
}
//皮肤资源初始化,后面有介绍
public void init(Context context) {
this.mContext = context.getApplicationContext();
//打开应用会立刻初始化如果有皮肤加载需要初始化SkinResource
SkinLoadUtils.getInstance().init(context);
String currentSkinPath = SkinLoadUtils.getInstance().getLoadPath();
File file = new File(currentSkinPath);
if (!file.exists()){
SkinLoadUtils.getInstance().cleanLoadPath();
return ;
}
String mPackageName = context.getPackageManager().getPackageArchiveInfo(currentSkinPath, PackageManager.GET_ACTIVITIES).packageName;
if (TextUtils.isEmpty(mPackageName)){
SkinLoadUtils.getInstance().cleanLoadPath();
return ;
}
mSkinResource = new SkinResource(mContext, currentSkinPath);
}
public void loadResource(String path) {
// 判断 path 文件是否存在
File file = new File(path);
if (!file.isFile()){
SkinLoadUtils.getInstance().cleanLoadPath();
return;
}
// 判断是否是APK文件,包名是否存在
String mPackageName = mContext.getPackageManager().getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES).packageName;
if (TextUtils.isEmpty(mPackageName)){
SkinLoadUtils.getInstance().cleanLoadPath();
return ;
}
// 判断是否是已经载的的地址
String currentSkinPath = SkinLoadUtils.getInstance().getLoadPath();
if (!TextUtils.isEmpty(currentSkinPath)){
if (currentSkinPath.equals(path)){
return;
}
}
//初始化资源
mSkinResource = new SkinResource(mContext, path);
// 加载皮肤
loadActivitySkin();
// 保存皮肤
saveSkinLoadPath(path);
}
private void loadActivitySkin() {
Set<SkinChangeCallBack> skinChangeCallBacks =mViewMap.keySet();
for (SkinChangeCallBack callBack : skinChangeCallBacks) {
List<SkinView> skinViews = mViewMap.get(callBack);
if (skinViews!=null){
for (SkinView skinView : skinViews) {
skinView.loadSkin();
}
callBack.skinChange(mSkinResource);
}
}
}
private void saveSkinLoadPath(String path) {
SkinLoadUtils.getInstance().saveLoadPath(path);
}
public List<SkinView> getSkinViews(SkinChangeCallBack callBack) {
return mViewMap.get(callBack);
}
public void registerSkinView(SkinChangeCallBack skinChangeCallBack ,List<SkinView> skinViews) {
mViewMap.put(skinChangeCallBack,skinViews);
}
public SkinResource getSkinResource() {
return mSkinResource;
}
/**
* @description 恢复默认
* @param
*/
public void recoverDefault() {
String currentSkinPath = SkinLoadUtils.getInstance().getLoadPath();
if (TextUtils.isEmpty(currentSkinPath)){
return;
}
String mPackageResourcePath = mContext.getPackageResourcePath();
SkinLoadUtils.getInstance().cleanLoadPath();
mSkinResource = new SkinResource(mContext,mPackageResourcePath);
loadActivitySkin();
}
/**
* @description 检测是否需要换肤
* @param
*/
public void checkSkinView(SkinView skinView) {
//保存的皮肤就换肤
if (!TextUtils.isEmpty(SkinLoadUtils.getInstance().getLoadPath())) {
// Log.e("TAG", "checkSkinView: -->"+SkinLoadUtils.getInstance().getLoadPath());
skinView.loadSkin();
}
}
/**
* @description 解绑
* @param
*/
public void unBinderSkinActivity(SkinChangeCallBack skinChangeCallBack) {
mViewMap.remove(skinChangeCallBack);
}
}
SkinResource就需要用到之前源码的一点知识了通过反射拿到皮肤资源
AssetManager里面 addAssetPath 的方法在拿到资源
/**
* @author li
* 版本:1.0
* 创建日期:2020/6/30 09
* 描述:皮肤资源
*/
public class SkinResource {
private Resources mResource;
private String mPackageName;
public SkinResource(Context context ,String path) {
// 获取本地资源
try {
Resources superRes = context.getResources();
AssetManager asset =AssetManager.class.newInstance();
Method method = AssetManager.class.getDeclaredMethod("addAssetPath",String.class);
method.setAccessible(true);
method.invoke(asset,path);
mResource = new Resources(asset,superRes.getDisplayMetrics(),superRes.getConfiguration());
mPackageName = context.getPackageManager().getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES).packageName;
} catch ( Exception e) {
e.printStackTrace();
}
}
public ColorStateList getColorByName(String resName) {
int resId = mResource.getIdentifier(resName,"color",mPackageName);
ColorStateList colorStateList = null;
try {
colorStateList = mResource.getColorStateList(resId);
} catch (Exception e) {
e.printStackTrace();
}
return colorStateList;
}
public Drawable getDrawableByName(String resName) {
int resId = mResource.getIdentifier(resName,"drawable",mPackageName);
Drawable drawable = null;
try {
drawable = mResource.getDrawable(resId);
} catch (Exception e) {
e.printStackTrace();
}
return drawable;
}
public String getTextRes(String resName) {
int resId = mResource.getIdentifier(resName,"string",mPackageName);
String text = null;
try {
text = mResource.getString(resId);
} catch (Exception e) {
e.printStackTrace();
}
return text;
}
}
初始化与保存
在BaseApplication 初始化SkinManager
public class BaseApplication extends Application {
public static Context getContext() {
return getContext().getApplicationContext();
}
@Override
public void onCreate() {
super.onCreate();
SkinManager.getInstance().init(this);
}
}
上面我将SkinManager的代码完全粘了出来在这里在详细的解释一下
因为皮肤的保存需要下次登录自动更换,所以我门需要将SkinManager在一登录就初始化 同时还需要将SkinResource 初始化,因为如果不初始化如需要换肤会出现空指针的情况 同时我们加入了皮肤保存加载Utils ——SkinLoadUtils 来处理皮肤的保存和清空
/**
* @author li
* 版本:1.0
* 创建日期:2020/6/30 18
* 描述:皮肤保存加载Utils
*/
public class SkinLoadUtils {
private static volatile SkinLoadUtils mInstance;
private Context mContext;
public SkinLoadUtils() {
}
public static SkinLoadUtils getInstance() {
if (mInstance==null){
synchronized (SkinLoadUtils.class){
if (mInstance==null){
mInstance = new SkinLoadUtils();
}
}
}
return mInstance;
}
public void init(Context context){
this.mContext = context;
}
/**
* @description 保存路径
* @param
*/
public void saveLoadPath(String path){
mContext.getSharedPreferences(SkinConfig.SKIN_CONFIG_PATH,Context.MODE_PRIVATE).edit()
.putString(SkinConfig.SKIN_LOAD_PATH,path).commit();
}
/**
* @description 返回当前皮肤路径
* @param
*/
public String getLoadPath(){
return mContext.getSharedPreferences(SkinConfig.SKIN_CONFIG_PATH,Context.MODE_PRIVATE)
.getString(SkinConfig.SKIN_LOAD_PATH,"");
}
/**
* @description 清除路径
* @param
*/
public void cleanLoadPath (){
mContext.getSharedPreferences(SkinConfig.SKIN_CONFIG_PATH,Context.MODE_PRIVATE).edit()
.putString(SkinConfig.SKIN_LOAD_PATH,"").commit();
}
}
SKinConfig的加入只是为了使结构更严谨 方便以后添加config
/**
* @author li
* 版本:1.0
* 创建日期:2020/6/30 18
* 描述:
*/
public class SkinConfig {
/**保存SharedPreferences的名称*/
public static String SKIN_CONFIG_PATH = "skinConfigPath";
/**皮肤路径*/
public static String SKIN_LOAD_PATH = "skinLoadPath";
}
最后内存泄露问题
因为我们是单例模式引用Activity里面的View 如果在退出的时候不讲View解绑会照成activity一直被单例所引用而无法被GC回收掉,所以在BaseActivity的ondestroy声明周期我们加入
@Override
protected void onDestroy() {
SkinManager.getInstance().unBinderSkinActivity(this);
super.onDestroy();
}
来进行解绑,避免因无法回收造成的内存泄露
源码