天天看點

android之換膚原了解讀

如下是解讀demo的連結,自行下載下傳

https://github.com/fengjundev/Android-Skin-Loader

由于是開源的,而且對于想了解換膚功能的童鞋這個demo實在是通俗易懂,原理也很清晰,是以忍不住想要記錄一下,

題外話:附上一篇換膚技術總結的部落格,這是一篇動态換膚與本地換膚(傳統的theme)換膚優劣勢的詳細比較,需要的童鞋,可以詳細拜讀,至少知道來源

http://blog.zhaiyifan.cn/2015/09/10/Android%E6%8D%A2%E8%82%A4%E6%8A%80%E6%9C%AF%E6%80%BB%E7%BB%93/

換膚功能用于公司的營運是常有的需求,畢竟皮膚對于app來說還是比較重要的;這個對于開發者來說并不關心, 我們隻關心其技術原理。

一、換膚功能:

       解讀的是一篇動态加載資源進而達到換膚 的效果,這也是換膚的一種潮流,行業上得換膚跟這個demo基本都大同小異,比較性能來說這個方案也是比較值得推薦

       既然是動态的,那就支援網絡加載的資源、本地資源應該都要支援,而且是無縫隙,高性能的,廢話不多說,先來看看設計者的思路

二、思路:

      前提:這demo隻是用本地講解 是以将要換膚的資源搞成apk,重新命名字尾為.skin,并儲存在指定的sd目錄中,友善加載,開發者也可通過網上下載下傳儲存到指定的sd目,友善支援線上換膚

       1、當點選換膚時,使用AsyncTask加載已儲存在sd目錄中的apk,并通過AssetManager反射添加資源路徑的方法将apk的資源加載進去,然後通過new Resource将Assetmanager管理器注冊,得到一個全新的資源管理者AssetManager,通過這個管理者與app原生的資源管理者作為區分,當加載資源的時候,就可以通過資源管理者的資源作為換膚,是以需要護膚時将這個新對象resource将其指派給全局的resource,通過Resource對象實作換膚;若切換回預設app的皮膚時,就将預設app生成的resource指派給resource,在其擷取資源時,通過resource來控制是取得哪一套資源,進而實作換膚。

     2、當點選換膚,擷取到resource之後,這裡通過觀察者模式去通知目前活動的頁面進行換膚,而不是放在onResume實時監測,使用觀察者就需要activity實作接口,這裡通過在BaseActivity實作統一接口ISkinUpdate,統一進行注冊,達到友善管理,友善換膚

     3、需要換膚就得知道哪些view需要換膚,通過設定inflate中的一個工廠Factory,這個工廠是用來建立一個view,有點類似hook,隻要這個Factory傳回一個view就不會再進行解析我們xml設定的view,每建立一個view之前factory都會執行一次,是以在這裡通過設定自己自定義的實作接口Factory的SkinInflateFactory,就可以在其讀取layout的xml檔案生成view之前會執行onCreateView,通過hook這個點,即生成xml檔案的view又可以滿足我們所要讀取需要換膚的view,并且判斷目前view是否需要換膚,需要則直接設定相應的color或drawable。到此基本就這個思路

三、代碼走讀

   接下來一起看看代碼走讀:我的閱讀習慣是從點選切換皮膚開始,然後一層層剝皮,需要用到的屬性在哪裡初始化,就跳轉到哪裡看看初始化的地方;讀者在讀demo的時候根據自己的習慣吧,這裡就姑且按我的思維方式走。

  換膚嘛,當然是找到點選換膚的事件咯;

  找到如下類及其點選換膚響應事件的方法

public class SettingActivity extends BaseActivity {

   /**
    * Put this skin file on the root of sdcard
    * eg:
    * /mnt/sdcard/BlackFantacy.skin
    */
   private static final String SKIN_NAME = "BlackFantacy.skin";
   private static final String SKIN_DIR = Environment
         .getExternalStorageDirectory() + File.separator + SKIN_NAME;


   private void initView() {

      setNightSkinBtn.setOnClickListener(new OnClickListener() {

         @Override
         public void onClick(View v) {
            onSkinSetClick();
         }
      });

      setOfficalSkinBtn.setOnClickListener(new OnClickListener() {

         @Override
         public void onClick(View v) {
            onSkinResetClick();
         }
      });
   }


   private void onSkinSetClick() {
      if(!isOfficalSelected) return;

      File skin = new File(SKIN_DIR);

      if(skin == null || !skin.exists()){
         Toast.makeText(getApplicationContext(), "請檢查" + SKIN_DIR + "是否存在", Toast.LENGTH_SHORT).show();
         return;
      }

      SkinManager.getInstance().load(skin.getAbsolutePath(),
            new ILoaderListener() {
               @Override
               public void onStart() {
                  L.e("startloadSkin");
               }

               @Override
               public void onSuccess() {
                  L.e("loadSkinSuccess");
                  Toast.makeText(getApplicationContext(), "切換成功", Toast.LENGTH_SHORT).show();
                  setNightSkinBtn.setText("黑色幻想(目前)");
                  setOfficalSkinBtn.setText("官方預設");
                  isOfficalSelected = false;
               }

               @Override
               public void onFailed() {
                  L.e("loadSkinFail");
                  Toast.makeText(getApplicationContext(), "切換失敗", Toast.LENGTH_SHORT).show();
               }
            });
   }
}      

然後在這個設定類裡邊,根據響應事件,我們看到了加載皮膚的調用處:

SkinManager.getInstance().load(skin.getAbsolutePath(),      

當然路徑是如下:先不管,将demo的BlackFanTancy.skin放到sd卡就行

private static final String SKIN_NAME = "BlackFantacy.skin";
   private static final String SKIN_DIR = Environment
         .getExternalStorageDirectory() + File.separator + SKIN_NAME;      

接下來看看SkinManager這個單例皮膚管理類:其中load的方法,進入如下:

public void load(String skinPackagePath, final ILoaderListener callback) {

		new AsyncTask<String, Void, Resources>() {

			protected void onPreExecute() {
				if (callback != null) {
					callback.onStart();
				}
			};

			@Override
			protected Resources doInBackground(String... params) {
				try {
					if (params.length == 1) {//加載皮膚包,并且将包的路徑加到資料總管中AssetManager
						String skinPkgPath = params[0];

						File file = new File(skinPkgPath);
						if (file == null || !file.exists()) {//
							Log.d(TAG, "!file.exists() skinPkgPath= " + skinPkgPath);
							Log.d(TAG, "!file.exists() = " + file.exists());
							return null;
						}
						Log.d(TAG,"skinPkgPath = "+skinPkgPath);
						PackageManager mPm = context.getPackageManager();
						PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);
						skinPackageName = mInfo.packageName;

						AssetManager assetManager = AssetManager.class.newInstance();
						Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
						addAssetPath.invoke(assetManager, skinPkgPath);

						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);
	}
           

這個方法傳入了一個回調接口ILoadListner,以及皮膚絕對路徑,随後通過AsyncTask異步執行加載路徑上的skin資源

接下來看看其中三個方法

首先、onStart():沒做什麼工作,就是在加載之前判斷callBack是否為空做一些初始化,我們這裡沒做什麼初始化,隻是列印而已

再次:doInBackground:重點的都在這個方法裡邊了:這個方法異步加載了資源,通過新建立Assetmanager與Resource建立對新加載的資源skin的管理,完成後通過SkinConfig.saveSkinPath();儲存目前皮膚路徑,以備下次再次打開app時預設加載皮膚還是上一次選中的。

最後:onPostExecut回到主線程處理更新皮膚;這裡将新建立的resource對象儲存到全局,由于callBack不為null,然後通過回調接口callBack.onSuccess()修改ui,以及調用notifySkinUpdate();猜測這個方法就是進行皮膚更新的方法。

到了這裡這個線路基本完事兒;

細心好奇的你疑問肯定有兩點:

1)、context是在哪兒指派的

2)、notifySkinUpdate()到底做了什麼工作

先來看看context到底在哪兒指派的,當然是在目前類找了:你會發現下邊這個方法

public void init(Context ctx){
   context = ctx.getApplicationContext();
}      

這個很自然的看看其在哪兒調用的,快捷鍵ctrl+alt+H,隻有一處方法調用,那就是Application,這是應用啟動就初始化的如下

public class SkinApplication extends Application {
   
   public void onCreate() {
      super.onCreate();
      
      initSkinLoader();
   }

   /**
    * Must call init first
    */
   private void initSkinLoader() {
      SkinManager.getInstance().init(this);
      SkinManager.getInstance().load();
   }
}      

看到這裡,你會意外發現,咦,這裡也有個load()那我們就會疑問這個load()是幹啥用的,初始化的時候為何要調用它,這個load是在我們進入app時調用的,那就有理由猜想如下:

 1)、 上次登入app時我們還沒切換過皮膚,還是預設皮膚,這個load是怎麼工作的

 2)、上次登入app時我們切換皮膚了,那麼這個load又是怎麼工作的

帶着這兩個疑問,我們進入load()方法一探究竟呗

public void load(){
   String skin = SkinConfig.getCustomSkinPath(context);
   Log.d(TAG, "skin = " + skin);
   load(skin, null);
}      

首先從sharePreference擷取皮膚路徑:分兩步走

 <一>、沒切換過皮膚,則skin得到的是預設

public  static final String     DEFALT_SKIN          =  "cn_feng_skin_default";      

 <二>、切換了皮膚,則擷取的是切換皮膚的路徑:

然後往下走,神奇的發現調用了load(skin,null);  這個方法不就是前邊我們分析過的嗎,接下來我們再次看看這個方法,畢竟參數不一樣了嘛:

/**
	 * Load resources from apk in asyc task
	 * @param skinPackagePath path of skin apk
	 * @param callback 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();
				}
			};

			@Override
			protected Resources doInBackground(String... params) {
				try {
					if (params.length == 1) {//加載皮膚包,并且将包的路徑加到資料總管中AssetManager
						String skinPkgPath = params[0];

						File file = new File(skinPkgPath);
						if (file == null || !file.exists()) {//
							Log.d(TAG, "!file.exists() skinPkgPath= " + skinPkgPath);
							Log.d(TAG, "!file.exists() = " + file.exists());
							return null;
						}
						Log.d(TAG,"skinPkgPath = "+skinPkgPath);
						PackageManager mPm = context.getPackageManager();
						PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);
						skinPackageName = mInfo.packageName;

						AssetManager assetManager = AssetManager.class.newInstance();
						Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
						addAssetPath.invoke(assetManager, skinPkgPath);

						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);
	}
           

當然還是分兩步走:由于callback為null,是以不在走callback回調,這裡不做分析了

   1)預設皮膚時,skin為預設的

public  static final String     DEFALT_SKIN          =  "cn_feng_skin_default";      

 由于這個是不存在的,是以當執行到doInBackground():

File file = new File(skinPkgPath);
						if (file == null || !file.exists()) {//
							Log.d(TAG, "!file.exists() skinPkgPath= " + skinPkgPath);
							Log.d(TAG, "!file.exists() = " + file.exists());
							return null;
						}
           

在這裡會直接傳回,不在加載皮膚;用的就是app中layout預設的顔色背景;到這裡這個預設的分析完畢

  2)切換了皮膚:前邊也分析儲存了皮膚在sd中的絕對路徑:是以這裡擷取的skin路徑上一次皮膚的路徑

       再往下走,除了callback不調用外,最後還是調用notifySkinUpdate()方法;

那麼這個方法到底做了什麼,肯定就是我們的換膚方法了;好奇的你肯定會進入notifySkinUpdate()方法一探究竟,那我們就一起看看:

@Override
	public void notifySkinUpdate() {
		if(skinObservers == null) return;
		for(ISkinUpdate observer : skinObservers){
			observer.onThemeUpdate();
		}
	}
           

當打開app的時候,不管曾經是否換膚,由于skinObservers為null,是以直接傳回

那麼我們就要看看這個觀察者skinObservers在哪兒初始化,哪兒訂閱的了;

我們會發現初始化的方法、訂閱的地方以及取消訂閱的地方如下:

@Override
	public void attach(ISkinUpdate observer) {
		if(skinObservers == null){
			skinObservers = new ArrayList<ISkinUpdate>();
		}
		if(!skinObservers.contains(skinObservers)){
			skinObservers.add(observer);
		}
	}

	@Override
	public void detach(ISkinUpdate observer) {
		if(skinObservers == null) return;
		if(skinObservers.contains(observer)){
			skinObservers.remove(observer);
		}
	}
           

這一看這裡都是重寫的方法,細心的你會發現notifySkinUpdat()方法也是重寫的,那麼我們就看看它實作的接口

public class SkinManager implements ISkinLoader{      

然後再看看attach(ISkinUpdate observer)和detach( ISkinUpdate observer)在哪兒調用,在檢視之前我們有理由猜想,BaseActivity肯定是作為觀察者實作了ISkinUpdate,已實時監測換膚功能;

接下來檢視attach(ISkinUpdate observer)和detach(ISkinUpdate observer)調用:發現确實是BaseActivity和BaseFragementActivity兩個類中調用:如下

@Override
protected void onResume() {
   super.onResume();
   SkinManager.getInstance().attach(this);
}

@Override
protected void onDestroy() {
   super.onDestroy();
   SkinManager.getInstance().detach(this);
   mSkinInflaterFactory.clean();
}      

傳的是this,那麼她們肯定實作了接口

public interface ISkinUpdate {
   void onThemeUpdate();
}
      

自然而然,我們就來看看onThemeUdapte做了什麼,它就能更換皮膚了?

@Override
public void onThemeUpdate() {
   if(!isResponseOnSkinChanging){
      return;
   }
   mSkinInflaterFactory.applySkin();
}      
isResponseOnSkinChanging      

這個預設是true,也沒地方改變它的預設值,我們先不管,直接跳到下面那行

mSkinInflaterFactory.applySkin();      

還是看看mSkinInflaterFactory到底是什麼鬼,在哪兒初始化的,檢視知道在oncreate方法中:

private SkinInflaterFactory mSkinInflaterFactory;

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   mSkinInflaterFactory = new SkinInflaterFactory();
   getLayoutInflater().setFactory(mSkinInflaterFactory);
}      

這到底是什麼鬼,每次進入activity都要設定這個Factory,這裡我們部隊Factory開講,下次再進行對它的源碼深究,我們隻要知道它是一個生産view的工廠類,在inflate的時候,通過每周遊一個layout的每個元件view之前都會檢測Factory是否為null,若不為null,則會調用onCreaterView();

擴充:Factory是否要生成view,如果生成view,則不會在建立layout周遊的那個元件,是以通過這個Factory也可以更改傳回顯示的view:比如layout布局其中一個元件是Imageview,而通過Factory可以生成TextView代替ImageView;

接下來看看SkinInFlaterFactory這個類的onCreateView,這個類肯定實作了Factory接口,否則setFactory()的,是以每次在inFlate時由于factory不為空,肯定都會檢測是否要調用onCreateView;是以setFactory必須設定在setContentView()方法之前;因為setContentView實際上也是調用inflate;

那就看看其oncreateView()咯:

@Override
	public View onCreateView(String name, Context context, AttributeSet attrs) {
		// if this is NOT enable to be skined , simplly skip it
		//在xml的節點中設定,設定為true表示是需要換膚的view,否則跳過這個view,因為這個view不需要換膚
		boolean isSkinEnable = attrs.getAttributeBooleanValue(SkinConfig.NAMESPACE, SkinConfig.ATTR_SKIN_ENABLE, false);
        if (!isSkinEnable){
        		return null;
        }
		
		View view = createView(context, name, attrs);
		
		if (view == null){
			return null;
		}
		
		parseSkinAttr(context, attrs, view);
		
		return view;
	}
           

首先:就來看看這個isSkinEnable是什麼:這個是我們在layout為元件設定的一個标志,标着這個元件是需要換膚的,如果不需要換膚的元件我們就不用往下走

其次:假設在元件中設定屬性skin:enable=true,則就會往下執行 ,執行createView()以及parseSkinAttr(),以下分析這連個個方法:

private View createView(Context context, String name, AttributeSet attrs) {
		View view = null;
		try {
			Log.d("SkinInflaterFactory","name = "+name);
			if (-1 == name.indexOf('.')){//-1則不是自定義的view
				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,主要是判斷:

這個元件view是否是用原生的還是自定義的:name是元件的名字:如TextView、ImageView,所示自定義的,則得到的是全名(包名+類名)

parseSkinAttr():

private void parseSkinAttr(Context context, AttributeSet attrs, 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);
			Log.d(TAG,"attrName = "+attrName);//屬性name比如layout_width或者自定義屬性name比如本次用的enable 值是true
			Log.d(TAG,"attrValue = "+attrValue);//屬性的值
			if(!AttrFactory.isSupportedAttr(attrName)){
				continue;
			}
			
		    if(attrValue.startsWith("@")){
				try {
					int id = Integer.parseInt(attrValue.substring(1));
					String entryName = context.getResources().getResourceEntryName(id);
					String typeName = context.getResources().getResourceTypeName(id);
					SkinAttr mSkinAttr = AttrFactory.get(attrName, id, entryName, typeName);
					Log.d(TAG,"id = "+id);
					Log.d(TAG,"getResourceEntryName = "+entryName);//color的name或drawable的name
					Log.d(TAG,"getResourceTypeName = "+typeName);//比如:color,drawable
					if (mSkinAttr != null) {
						viewAttrs.add(mSkinAttr);
					}
				} catch (NumberFormatException e) {
					e.printStackTrace();
				} catch (NotFoundException e) {
					e.printStackTrace();
				}
		    }
		}
		
		if(!ListUtils.isEmpty(viewAttrs)){
			SkinItem skinItem = new SkinItem();
			skinItem.view = view;
			skinItem.attrs = viewAttrs;

			mSkinItems.add(skinItem);

			if (SkinManager.getInstance().isExternalSkin()) {
				//這裡是每次進入activity後fragment的時候都要判斷是否需要換膚
				skinItem.apply();
			}
		}
	}
           

這個方法是解析元件的所有屬性, 并将得到的可以換膚的所有屬性color或drawable屬性id和屬性名 儲存到一個viewAttrs,然後将viewAttrs和view所有相關值 ,儲存skinItem,随後将SkinItem緩存到mSkinItem集合中,接下來是判斷目前是否需要換膚;假設是需要的,則我們來看看它是如何執行的

public void apply(){
		if(ListUtils.isEmpty(attrs)){
			return;
		}
		for(SkinAttr at : attrs){
			at.apply(view);
		}
	}
           

由于parseSKinAttr解析式已經将attrs設定,所不會為空,是以會執行for循環

檢視at.apply(View),那麼我們發現SkinAttr是個抽象類,抽象方法為apply(view),于是回過頭看看parseSKinAttr這個方法在哪裡調用的:驚奇的發現:

SkinAttr mSkinAttr = AttrFactory.get(attrName, id, entryName, typeName);      

用的竟讓是個工廠方式,進去一瞧咯:

public static SkinAttr get(String attrName, int attrValueRefId, String attrValueRefName, String typeName){
   
   SkinAttr mSkinAttr = null;
   
   if(BACKGROUND.equals(attrName)){ 
      mSkinAttr = new BackgroundAttr();
   }else if(TEXT_COLOR.equals(attrName)){ 
      mSkinAttr = new TextColorAttr();
   }else if(LIST_SELECTOR.equals(attrName)){ 
      mSkinAttr = new ListSelectorAttr();
   }else if(DIVIDER.equals(attrName)){ 
      mSkinAttr = new DividerAttr();
   }else{
      return null;
   }
   
   mSkinAttr.attrName = attrName;
   mSkinAttr.attrValueRefId = attrValueRefId;
   mSkinAttr.attrValueRefName = attrValueRefName;
   mSkinAttr.attrValueTypeName = typeName;
   return mSkinAttr;
}      

很容易就知道,這是生成什麼屬性,支援哪些換膚,主要有四個,是以隻拿第一個BackgroundAttr類作為分析,其它原理一樣:

public class BackgroundAttr extends SkinAttr {

   @Override
   public void apply(View view) {
      
      if(RES_TYPE_NAME_COLOR.equals(attrValueTypeName)){
         view.setBackgroundColor(SkinManager.getInstance().getColor(attrValueRefId));
         Log.i("attr", "_________________________________________________________");
         Log.i("attr", "apply as color");
      }else if(RES_TYPE_NAME_DRAWABLE.equals(attrValueTypeName)){
         Drawable bg = SkinManager.getInstance().getDrawable(attrValueRefId);
         view.setBackground(bg);
         Log.i("attr", "_________________________________________________________");
         Log.i("attr", "apply as drawable");
         Log.i("attr", "bg.toString()  " + bg.toString());
         
         Log.i("attr", this.attrValueRefName + " 是否可變換狀态? : " + bg.isStateful());
      }
   }
}      

原來換膚最終的真相在這裡:

view.setBackgroundColor(SkinManager.getInstance().getColor(attrValueRefId))      

以及

Drawable bg = SkinManager.getInstance().getDrawable(attrValueRefId);
         view.setBackground(bg);      

這裡邊的

SkinManager.getInstance().getColor(attrValueRefId)      

SkinManager.getInstance().getDrawable(attrValueRefId)      

做了什麼:進去瞧瞧就知道了:這裡選擇一個來分析吧,其它都一樣的

public int getColor(int resId){
   int originColor = context.getResources().getColor(resId);
   if(mResources == null || isDefaultSkin){
      return originColor;
   }
   //通過預設的resId擷取預設顔色的資源名,通過名字查找皮膚包一緻的名字再擷取生成的dstId
   String resName = context.getResources().getResourceEntryName(resId);

   int trueResId = mResources.getIdentifier(resName, "color", skinPackageName);
   int trueColor = 0;

   try{
      trueColor = mResources.getColor(trueResId);
   }catch(NotFoundException e){
      e.printStackTrace();
      trueColor = originColor;
   }

   return trueColor;
}      

首先:通過app的context擷取originColor,app的預設的顔色,若mResoutces=null(沒切換皮膚)或isDefaultSkin=true(顯示的是預設的),則直接傳回顯示預設color

否則:通過預設的resId擷取預設顔色的資源名,通過名字查找皮膚包一緻的名字再擷取生成的dstId,得到dstId這裡就是trueResId,這個id就是從新的Resource資源管理者擷取的,就是換膚的皮膚顔色id,這樣就能獲得了皮膚,直接傳回設定顔色就可以換膚成功了;

到這裡終于結束了!!!,了解有誤的地方,敬請指正!!!