日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

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

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

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

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

換膚介紹

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

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

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

換膚方式二:加載資源包

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

對比一下發現切換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這三個參數就可以在AssetManager中尋找相應的PackageName中有沒有Type類型并且id值都能與參數對應上的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的構造函數來看有 {@hide} 的朱姐,所以在其他類里面是直接創建AssetManager實例。但是不要忘記Java中還有反射機制可以創建類對象。

AssetManager assetManager = AssetManager.class.newInstance();

讓創建的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());} 使用資源包中的資源換膚

我們將上述所有的代碼組合在一起就可以實現,使用資源包中的資源對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創建的時候就直接使用皮膚資源包中的資源文件,那么這無疑就使換膚更加的簡單已維護。

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對象在創建的時候設置Factory,那么在Window中的View進行創建的時候就會先通過自己設置的Factory進行創建。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);} 定義基類 換膚頁面的基類的通用代碼實現基本換膚功能。

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的創建并對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;}//創建ViewView view = createView(context, name, attrs);if (view == null){ return null;}//如果View創建成功,對View進行換膚parseSkinAttr(context, attrs, view);return view; } //創建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(下載、監聽回調、應用、取消、異常處理、擴展模塊等等)。

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

標簽: Android
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
亚洲资源av| 午夜在线视频观看日韩17c| 日韩在线黄色| 免费看黄色91| 亚洲资源网站| 美女91精品| 午夜电影一区| 国产精品一区二区三区www| 免费一级欧美片在线观看网站 | 日本亚洲最大的色成网站www| 免费观看在线综合色| 亚洲精一区二区三区| 久久精品99久久久| 美女精品一区二区| 在线一区av| 婷婷成人在线| 亚洲乱码视频| 国产精品九九| 在线观看精品| 亚洲欧美日韩在线观看a三区| 亚洲视频国产精品| 久久国产日韩欧美精品| 国产精品成人3p一区二区三区| 国产第一亚洲| 日韩免费福利视频| 99久久99视频只有精品| 国产一级久久| 国产亚洲观看| 日韩电影免费在线观看| 精品1区2区3区4区| 日韩欧美激情电影| 久久av一区| 蜜桃精品在线| 在线亚洲激情| 亚洲一区二区成人| 日本一区二区三区视频在线看 | 国产模特精品视频久久久久| 日本一区免费网站| 久久精品理论片| 久久中文字幕av| 亚洲精品影视| 国产精品毛片一区二区在线看| 国产韩日影视精品| 欧美在线首页| 91精品国产调教在线观看 | 国产欧美日韩| 91精品啪在线观看国产18| 综合国产精品| 高清av不卡| 免费观看在线色综合| 成人污污视频| 伊人成人在线视频| 国产精品对白| 国产精品美女久久久浪潮软件| 精品美女久久| 日韩在线一区二区| 精品亚洲二区| 一区二区三区网站| www.51av欧美视频| 日本不卡不码高清免费观看| 伊人网在线播放| 日韩精品高清不卡| 久久一区二区三区电影| 国产精品天堂蜜av在线播放| 好看的av在线不卡观看| 久久中文字幕一区二区三区| 一区免费视频| 精品国产欧美日韩一区二区三区| 性欧美精品高清| 高清不卡亚洲| 国产亚洲一卡2卡3卡4卡新区| 亚洲激情中文| 精品视频一区二区三区四区五区 | 高清一区二区三区| 亚洲精品裸体| 欧美精选视频一区二区| 亚洲bt欧美bt精品777| 秋霞国产精品| 国产精品观看| 亚洲三级网站| 成人av二区| 欧美国产一级| 欧美精品影院| 亚洲一区二区免费在线观看| 久久视频国产| 97精品国产一区二区三区| 欧美一级网址| 视频一区二区三区入口| 亚洲a在线视频| 国产伦久视频在线观看| 欧美在线观看天堂一区二区三区| 亚洲一区区二区| 91精品国产乱码久久久久久久| 精品国产一级| 免费视频一区二区三区在线观看| 日韩高清成人在线| 蜜臀va亚洲va欧美va天堂| 欧美日韩国产一区二区三区不卡 | 国产亚洲在线观看| 亚洲二区在线| 日韩精品麻豆| 精品视频自拍| 老色鬼精品视频在线观看播放| 亚洲精品四区| 六月婷婷一区| 午夜在线观看免费一区| 国产综合精品| 欧美成a人免费观看久久| yellow在线观看网址| 精品免费在线| 日韩不卡一区| 中文字幕成在线观看| 日韩av二区| 另类专区亚洲| 日韩中文字幕高清在线观看| 欧美天堂视频| av日韩中文| 免费福利视频一区二区三区| 国产传媒在线观看| 日韩精品dvd| 久久久久久久久久久妇女| 久久久久中文| 欧美精品一区二区久久| 欧美特黄一区| 麻豆成人在线| 亚洲精品综合| 一区二区不卡| 婷婷视频一区二区三区| 日本成人在线不卡视频| 日韩精品社区| 青草av.久久免费一区| 亚洲精品麻豆| 亚洲美女久久| 国产精品亚洲人成在99www| 国产精品网站在线看| 你懂的国产精品| 欧美韩日一区| 婷婷综合五月| 亚洲一区av| 国产精品第一| 日韩欧美一区免费| 激情婷婷欧美| 蜜桃视频在线观看一区| 亚洲资源网站| 国产精品一区毛片| 精品美女视频| av中文字幕在线观看第一页| 久久久久美女| 鲁大师影院一区二区三区| 日韩中文av| 欧美国产视频| 成人午夜国产| 香蕉久久夜色精品国产| 91精品日本| 美女国产精品久久久| 久久影院午夜精品| 婷婷国产精品| 日韩欧美中文在线观看| 里番精品3d一二三区| 性感美女一区二区在线观看| 99亚洲视频| 日韩高清在线不卡| 国产欧美日韩在线一区二区 | 久久三级视频| 99视频+国产日韩欧美| 天堂va在线高清一区| 麻豆国产一区| 日韩毛片在线| 亚洲久久一区| 国产成人精选| 欧美高清一区| 国产欧美在线观看免费| 精品国产一区二区三区噜噜噜| 99热精品久久| 日韩高清二区| 久久精品国内一区二区三区水蜜桃| 首页亚洲欧美制服丝腿| 国产精品大片免费观看| 99久久亚洲精品| 91成人小视频| 美女少妇全过程你懂的久久| 日本午夜精品一区二区三区电影| 国产在线看片免费视频在线观看| 午夜在线精品偷拍| 久久精品国产网站| 91久久视频| 激情久久一区二区| 欧美亚洲激情| 国产激情综合| re久久精品视频| 国产精品免费99久久久| 日韩精品午夜| 久久不见久久见免费视频7| 欧美网站在线| 精品伊人久久| 久久av一区二区三区| 日韩av自拍| 日韩精品亚洲一区二区三区免费| 久久天堂av| 欧美日韩一区二区三区不卡视频|