电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

Android換膚原理和Android-Skin-Loader框架解析

瀏覽:63日期:2022-09-27 10:11:38

Android換膚技術已經是很久之前就已經被成熟使用的技術了,然而我最近才在學習和接觸熱修復的時候才看到。在看了一些換膚的方法之后,并且對市面上比較認可的Android-Skin-Loader換膚框架的源碼進行了分析總結。再次記錄一下祭奠自己逝去的時間。

換膚介紹

換膚本質上是對資源的一中替換包括、字體、顏色、背景、圖片、大小等等。當然這些我們都有成熟的api可以通過控制代碼邏輯做到。比如View的修改背景顏色 setBackgroundColor ,TextView的 setTextSize 修改字體等等。但是作為程序員我們怎么能忍受對每個頁面的每個元素一個行行代碼做換膚處理呢?我們需要用最少的代碼實現(xiàn)最容易維護和使用效果完美(動態(tài)切換,及時生效)的換膚框架。

換膚方式一:切換使用主題Theme

使用相同的資源id,但在不同的Theme下邊自定義不同的資源。我們通過主動切換到不同的Theme從而切換界面元素創(chuàng)建時使用的資源。這種方案的代碼量不多發(fā),而且有個很明顯的缺點不支持已經創(chuàng)建界面的換膚,必須重新加載界面元素。 GitHub Demo

換膚方式二:加載資源包

加載資源包是各種應用程序都在使用的換膚方法,例如我們最常用的輸入法皮膚、瀏覽器皮膚等等。我們可以將皮膚的資源文件放入安裝包內部,也可以進行下載緩存到磁盤上。Android的應用程序可以使用這種方式進行換膚。GitHub上面有一個start非常高的換膚框架 Android-Skin-Loader 就是通過加載資源包對app進行換膚。對這個框架的分析這個也是這篇文章主要的講述內容。

對比一下發(fā)現(xiàn)切換Theme可以進行小幅度的換膚設置(比如某個自定義組件的主題),而如果我們想要對整個app做主題切換那么通過加載資源包的這種方式目前應該說是比較好的了。

Android換膚知識點 換膚相應的API

我們先來看一下Android提供的一些基本的api,通過使用這些api可以在App內部進行資源對象的替換。

public class Resources{ public String getString(int id)throws NotFoundException {CharSequence res = mAssets.getResourceText(id);if (res != null) { return res;}throw new NotFoundException('String resource ID #0x' + Integer.toHexString(id)); } public Drawable getDrawable(int id)throws NotFoundException {/********部分代碼省略*******/ } public int getColor(int id)throws NotFoundException {{/********部分代碼省略*******/ } /********部分代碼省略*******/}

這個是我們常用的Resources類的api,我們通常可以使用在資源文件中定義的 @+id String類型,然后在編譯出的R.java中對應的資源文件生產的id(int類型),從而通過這個id(int類型)調用Resources提供的這些api獲取到對應的資源對象。這個在同一個app下沒有任何問題,但是在皮膚包中我們怎么獲取這個id值呢。

public class Resources{ /********部分代碼省略*******/ /*** 通過給的資源名稱返回一個資源的標識id。*@paramname 描述資源的名稱*@paramdefType 資源的類型*@paramdefPackage 包名**@return返回資源id,0標識未找到該資源*/ public int getIdentifier(String name, String defType, String defPackage){if (name == null) { throw new NullPointerException('name is null');}try { return Integer.parseInt(name);} catch (Exception e) { // Ignore}return mAssets.getResourceIdentifier(name, defType, defPackage); }}

Resources提供了可以通過 @+id 、Type、PackageName這三個參數(shù)就可以在AssetManager中尋找相應的PackageName中有沒有Type類型并且id值都能與參數(shù)對應上的id,進行返回。然后我們可以通過這個id再調用Resource的獲取資源的api就可以得到相應的資源。

這里我們需要注意的一點是 getIdentifier(String name, String defType, String defPackage) 方法和 getString(int id) 方法所調用Resources對象的mAssets對象必須是同一個,并且包含有PackageName這個資源包。

AssetManager構造

怎么構造一個包含特定packageName資源的AssetManager對象實例呢?

public final class AssetManagerimplements AutoCloseable{ /********部分代碼省略*******/ /*** Create a new AssetManager containing only the basic system assets.* Applications will not generally use this method, instead retrieving the* appropriate asset manager with {@linkResources#getAssets}. Not for* use by applications.* {@hide}*/ public AssetManager(){synchronized (this) { if (DEBUG_REFS) {mNumRefs = 0;incRefsLocked(this.hashCode()); } init(false); if (localLOGV) Log.v(TAG, 'New asset manager: ' + this); ensureSystemAssets();} }

從AssetManager的構造函數(shù)來看有 {@hide} 的朱姐,所以在其他類里面是直接創(chuàng)建AssetManager實例。但是不要忘記Java中還有反射機制可以創(chuàng)建類對象。

AssetManager assetManager = AssetManager.class.newInstance();

讓創(chuàng)建的assetManager包含特定的PackageName的資源信息,怎么辦?我們在AssetManager中找到相應的api可以調用。

public final class AssetManagerimplements AutoCloseable{ /********部分代碼省略*******/ /*** Add an additional set of assets to the asset manager. This can be* either a directory or ZIP file. Not for use by applications. Returns* the cookie of the added asset, or 0 on failure.* {@hide}*/ public final int addAssetPath(String path){synchronized (this) { int res = addAssetPathNative(path); if (mStringBlocks != null) {makeStringBlocks(mStringBlocks); } return res;} }}

同樣改方法也不支持外部調用,我們只能通過反射的方法來調用。

/*** apk路徑*/String apkPath = Environment.getExternalStorageDirectory()+'/skin.apk';AssetManager assetManager = null;try { AssetManager assetManager = AssetManager.class.newInstance(); AssetManager.class.getDeclaredMethod('addAssetPath', String.class).invoke(assetManager, apkPath);} catch (Throwable th) { th.printStackTrace();}

至此我們可以構造屬于自己換膚的Resources了。

換膚Resources構造

public Resources getSkinResources(Context context){ /*** 插件apk路徑*/ String apkPath = Environment.getExternalStorageDirectory()+'/skin.apk'; AssetManager assetManager = null; try {AssetManager assetManager = AssetManager.class.newInstance();AssetManager.class.getDeclaredMethod('addAssetPath', String.class).invoke(assetManager, apkPath); } catch (Throwable th) {th.printStackTrace(); } return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());} 使用資源包中的資源換膚

我們將上述所有的代碼組合在一起就可以實現(xiàn),使用資源包中的資源對app進行換膚。

public Resources getSkinResources(Context context){ /*** 插件apk路徑*/ String apkPath = Environment.getExternalStorageDirectory()+'/skin.apk'; AssetManager assetManager = null; try {AssetManager assetManager = AssetManager.class.newInstance();AssetManager.class.getDeclaredMethod('addAssetPath', String.class).invoke(assetManager, apkPath); } catch (Throwable th) {th.printStackTrace(); } return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());}@Overrideprotected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView imageView = (ImageView) findViewById(R.id.imageView); TextView textView = (TextView) findViewById(R.id.text); /*** 插件資源對象*/ Resources resources = getSkinResources(this); /*** 獲取圖片資源*/ Drawable drawable = resources.getDrawable(resources.getIdentifier('night_icon', 'drawable','com.tzx.skin')); /*** 獲取文本資源*/ int color = resources.getColor(resources.getIdentifier('night_color','color','com.tzx.skin')); imageView.setImageDrawable(drawable); textView.setText(text);}

通過上述介紹,我們可以簡單的對當前頁面進行換膚了。但是想要做出一個一個成熟換膚框架那么僅僅這些還是不夠的,提高一下我們的思維高度,如果我們在View創(chuàng)建的時候就直接使用皮膚資源包中的資源文件,那么這無疑就使換膚更加的簡單已維護。

LayoutInflater.Factory

看過我前一篇 遇見LayoutInflater&Factory 文章的這部分可以省略掉.

很幸運Android給我們在View生產的時候做修改提供了法門。

public abstract class LayoutInflater{ /***部分代碼省略****/ public interface Factory{public View onCreateView(String name, Context context, AttributeSet attrs); } public interface Factory2extends Factory{public View onCreateView(View parent, String name, Context context, AttributeSet attrs); } /***部分代碼省略****/}

我們可以給當前的頁面的Window對象在創(chuàng)建的時候設置Factory,那么在Window中的View進行創(chuàng)建的時候就會先通過自己設置的Factory進行創(chuàng)建。Factory使用方式和相關注意事項請移位到 遇見LayoutInflater&Factory ,關于Factory的相關知識點盡在其中。

Android-Skin-Loader解析 初始化 初始化換膚框架,導入需要換膚的資源包(當前為一個apk文件,其中只有資源文件)。

public class SkinApplicationextends Application{public void onCreate(){super.onCreate();initSkinLoader();}/*** Must call init first*/private void initSkinLoader(){SkinManager.getInstance().init(this);SkinManager.getInstance().load();}} 構造換膚對象 導入需要換膚的資源包,并構造換膚的Resources實例。

/*** Load resources from apk in asyc task*@paramskinPackagePath path of skin apk*@paramcallback callback to notify user*/public void load(String skinPackagePath,final ILoaderListener callback){new AsyncTask<String, Void, Resources>() {protected void onPreExecute(){if (callback != null) {callback.onStart();}};@Overrideprotected Resources doInBackground(String... params){try {if (params.length == 1) {String skinPkgPath = params[0];File file = new File(skinPkgPath); if(file == null || !file.exists()){return null;}PackageManager mPm = context.getPackageManager();//檢索程序外的一個安裝包文件PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);//獲取安裝包報名skinPackageName = mInfo.packageName; //構建換膚的AssetManager實例AssetManager assetManager = AssetManager.class.newInstance();Method addAssetPath = assetManager.getClass().getMethod('addAssetPath', String.class);addAssetPath.invoke(assetManager, skinPkgPath); //構建換膚的Resources實例Resources superRes = context.getResources();Resources skinResource = new Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());//存儲當前皮膚路徑SkinConfig.saveSkinPath(context, skinPkgPath);skinPath = skinPkgPath;isDefaultSkin = false;return skinResource;}return null;} catch (Exception e) {e.printStackTrace();return null;}};protected void onPostExecute(Resources result){mResources = result;if (mResources != null) {if (callback != null) callback.onSuccess();//更新多有可換膚的界面notifySkinUpdate();}else{isDefaultSkin = true;if (callback != null) callback.onFailed();}};}.execute(skinPackagePath);} 定義基類 換膚頁面的基類的通用代碼實現(xiàn)基本換膚功能。

public class BaseFragmentActivityextends FragmentActivityimplements ISkinUpdate,IDynamicNewView{/***部分代碼省略****///自定義LayoutInflater.Factory private SkinInflaterFactory mSkinInflaterFactory;@Override protected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState); try { //設置LayoutInflater的mFactorySet為true,表示還未設置mFactory,否則會拋出異常。 Field field = LayoutInflater.class.getDeclaredField('mFactorySet'); field.setAccessible(true); field.setBoolean(getLayoutInflater(), false); //設置LayoutInflater的MFactory mSkinInflaterFactory = new SkinInflaterFactory(); getLayoutInflater().setFactory(mSkinInflaterFactory);} catch (NoSuchFieldException e) { e.printStackTrace();} catch (IllegalArgumentException e) { e.printStackTrace();} catch (IllegalAccessException e) { e.printStackTrace();} } @Override protected void onResume(){super.onResume();//注冊皮膚管理對象SkinManager.getInstance().attach(this); }@Override protected void onDestroy(){super.onDestroy();//反注冊皮膚管理對象SkinManager.getInstance().detach(this); } /***部分代碼省略****/} SkinInflaterFactory SkinInflaterFactory進行View的創(chuàng)建并對View進行換膚。 構造View

public class SkinInflaterFactoryimplements Factory{ /***部分代碼省略****/ public View onCreateView(String name, Context context, AttributeSet attrs){//讀取View的skin:enable屬性,false為不需要換膚// if this is NOT enable to be skined , simplly skip itboolean isSkinEnable = attrs.getAttributeBooleanValue(SkinConfig.NAMESPACE, SkinConfig.ATTR_SKIN_ENABLE, false);if (!isSkinEnable){return null;}//創(chuàng)建ViewView view = createView(context, name, attrs);if (view == null){ return null;}//如果View創(chuàng)建成功,對View進行換膚parseSkinAttr(context, attrs, view);return view; } //創(chuàng)建View,類比可以查看LayoutInflater的createViewFromTag方法 private View createView(Context context, String name, AttributeSet attrs){View view = null;try { if (-1 == name.indexOf(’.’)){if ('View'.equals(name)) { view = LayoutInflater.from(context).createView(name, 'android.view.', attrs);} if (view == null) { view = LayoutInflater.from(context).createView(name, 'android.widget.', attrs);} if (view == null) { view = LayoutInflater.from(context).createView(name, 'android.webkit.', attrs);} }else {view = LayoutInflater.from(context).createView(name, null, attrs); } L.i('about to create ' + name);} catch (Exception e) { L.e('error while create 【' + name + '】 : ' + e.getMessage()); view = null;}return view; }} 對生產的View進行換膚

public class SkinInflaterFactoryimplements Factory{ //存儲當前Activity中的需要換膚的View private List<SkinItem> mSkinItems = new ArrayList<SkinItem>(); /***部分代碼省略****/ private void parseSkinAttr(Context context, AttributeSet attrs, View view){//當前View的所有屬性標簽List<SkinAttr> viewAttrs = new ArrayList<SkinAttr>();for (int i = 0; i < attrs.getAttributeCount(); i++){ String attrName = attrs.getAttributeName(i); String attrValue = attrs.getAttributeValue(i);if(!AttrFactory.isSupportedAttr(attrName)){continue; } //過濾view屬性標簽中屬性的value的值為引用類型 if(attrValue.startsWith('@')){try { int id = Integer.parseInt(attrValue.substring(1)); String entryName = context.getResources().getResourceEntryName(id); String typeName = context.getResources().getResourceTypeName(id); //構造SkinAttr實例,attrname,id,entryName,typeName //屬性的名稱(background)、屬性的id值(int類型),屬性的id值(@+id,string類型),屬性的值類型(color) SkinAttr mSkinAttr = AttrFactory.get(attrName, id, entryName, typeName); if (mSkinAttr != null) {viewAttrs.add(mSkinAttr); }} catch (NumberFormatException e) { e.printStackTrace();} catch (NotFoundException e) { e.printStackTrace();} }}//如果當前View需要換膚,那么添加在mSkinItems中if(!ListUtils.isEmpty(viewAttrs)){ SkinItem skinItem = new SkinItem(); skinItem.view = view; skinItem.attrs = viewAttrs; mSkinItems.add(skinItem); //是否是使用外部皮膚進行換膚 if(SkinManager.getInstance().isExternalSkin()){skinItem.apply(); }} }} 資源獲取

通過當前的資源id,找到對應的資源name。再從皮膚包中找到該資源name所對應的資源id。

public class SkinManagerimplements ISkinLoader{ /***部分代碼省略****/ public int getColor(int resId){int originColor = context.getResources().getColor(resId);//是否沒有下載皮膚或者當前使用默認皮膚if(mResources == null || isDefaultSkin){ return originColor;}//根據resId值獲取對應的xml的的@+id的String類型的值String resName = context.getResources().getResourceEntryName(resId);//更具resName在皮膚包的mResources中獲取對應的resIdint trueResId = mResources.getIdentifier(resName, 'color', skinPackageName);int trueColor = 0;try{ //根據resId獲取對應的資源value trueColor = mResources.getColor(trueResId);}catch(NotFoundException e){ e.printStackTrace(); trueColor = originColor;}return trueColor; } public Drawable getDrawable(int resId){...}} 其他

除此之外再增加以下對于皮膚的管理api(下載、監(jiān)聽回調、應用、取消、異常處理、擴展模塊等等)。

來自:http://dandanlove.com/2017/11/27/android-skin-changed/

標簽: Android
相關文章:
主站蜘蛛池模板: 接地电阻测试仪[厂家直销]_电缆故障测试仪[精准定位]_耐压测试仪-武汉南电至诚电力设备 | 技德应用| 卫生型双针压力表-高温防腐差压表-安徽康泰电气有限公司 | 游戏版号转让_游戏资质出售_游戏公司转让-【八九买卖网】 | 常州律师事务所_常州律所_常州律师-江苏乐天律师事务所 | 二手色谱仪器,十万分之一分析天平,蒸发光检测器,电位滴定仪-湖北捷岛科学仪器有限公司 | 仿古建筑设计-仿古建筑施工-仿古建筑公司-汉匠古建筑设计院 | 丙烷/液氧/液氮气化器,丙烷/液氧/液氮汽化器-无锡舍勒能源科技有限公司 | 创富网-B2B网站|供求信息网|b2b平台|专业电子商务网站 | 安徽成考网-安徽成人高考网| 真空泵维修保养,普发,阿尔卡特,荏原,卡西亚玛,莱宝,爱德华干式螺杆真空泵维修-东莞比其尔真空机电设备有限公司 | 艾乐贝拉细胞研究中心 | 国家组织工程种子细胞库华南分库 | 专业广州网站建设,微信小程序开发,一物一码和NFC应用开发、物联网、外贸商城、定制系统和APP开发【致茂网络】 | 电动卫生级调节阀,电动防爆球阀,电动软密封蝶阀,气动高压球阀,气动对夹蝶阀,气动V型调节球阀-上海川沪阀门有限公司 | 粒米特测控技术(上海)有限公司-测功机_减速机测试台_电机测试台 | 菲希尔X射线测厚仪-菲希尔库伦法测厚仪-无锡骏展仪器有限责任公司 | 12cr1mov无缝钢管切割-15crmog无缝钢管切割-40cr无缝钢管切割-42crmo无缝钢管切割-Q345B无缝钢管切割-45#无缝钢管切割 - 聊城宽达钢管有限公司 | 头条搜索极速版下载安装免费新版,头条搜索极速版邀请码怎么填写? - 欧远全 | 楼承板设备-楼承板成型机-免浇筑楼承板机器厂家-捡来 | 合肥仿石砖_合肥pc砖厂家_合肥PC仿石砖_安徽旭坤建材有限公司 | 3D全息投影_地面互动投影_360度立体投影_水幕灯光秀 | 广东银虎 蜂窝块状沸石分子筛-吸附脱硫分子筛-萍乡市捷龙环保科技有限公司 | 成都茶楼装修公司 - 会所设计/KTV装修 - 成都朗煜装饰公司 | 优秀的临床医学知识库,临床知识库,医疗知识库,满足电子病历四级要求,免费试用 | 开云(中国)Kaiyun·官方网站 - 登录入口| 山东臭氧发生器,臭氧发生器厂家-山东瑞华环保设备 | 有福网(yofus.com)洗照片冲印,毕业聚会纪念册相册制作个性DIY平台 | 中高频感应加热设备|高频淬火设备|超音频感应加热电源|不锈钢管光亮退火机|真空管烤消设备 - 郑州蓝硕工业炉设备有限公司 | 氢氧化钙设备_厂家-淄博工贸有限公司 | 电地暖-电采暖-发热膜-石墨烯电热膜品牌加盟-暖季地暖厂家 | 四川成都干燥设备_回转筒干燥机_脉冲除尘器_输送设备_热风炉_成都川工星科机电设备有限公司 | 气象监测系统_气象传感器_微型气象仪_气象环境监测仪-山东风途物联网 | 辐射仪|辐射检测仪|辐射巡测仪|个人剂量报警仪|表面污染检测仪|辐射报警仪|辐射防护网 | 汽液过滤网厂家_安平县银锐丝网有限公司 | 东莞螺丝|东莞螺丝厂|东莞不锈钢螺丝|东莞组合螺丝|东莞精密螺丝厂家-东莞利浩五金专业紧固件厂家 | 深圳品牌设计公司-LOGO设计公司-VI设计公司-未壳创意 | 罐体电伴热工程-消防管道电伴热带厂家-山东沃安电气 | 东莞螺丝|东莞螺丝厂|东莞不锈钢螺丝|东莞组合螺丝|东莞精密螺丝厂家-东莞利浩五金专业紧固件厂家 | 微型气象仪_气象传感器_防爆气象传感器-天合传感器大全 | 在线悬浮物浓度计-多参数水质在线检测仪-上海沃懋仪表科技有限公司 | 衡阳耐适防护科技有限公司——威仕盾焊接防护用品官网/焊工手套/焊接防护服/皮革防护手套 | 全自动面膜机_面膜折叠机价格_面膜灌装机定制_高速折棉机厂家-深圳市益豪科技有限公司 |