Easy Android skin replacement (subject)
Currently, many apps have skin replacement functions. You can customize your own interfaces based on your preferences, such as Sina Weibo and Netease news. Today, I want to introduce a mechanism for app skin replacement.
I have found several skin-changing apps. Basically, the icons, background images, and background colors of the interface have been replaced. In fact, the layout can be changed, however, it is unnecessary. Therefore, this article explains how to change the skin by changing the icon, background image, and other resources.
By searching through the Internet, I found that the online skin replacement mechanism is basically as follows:
1. directly put the skin package into the apk. This solution is very simple, but not flexible enough, and it also makes the apk bigger.
2. Make the skin into an independent apk file, share a shareUsedId with the project, and have the same signature. This solution is more flexible than the first one. The disadvantage is that you need to install it. Sina Weibo currently uses this solution.
The solution I want to introduce today is similar to the second one, but I do not want to install the resource package. After all, users are generally willing to install some messy applications.
Before learning this article, I 'd better learn my previous article "Android Resource Management Mechanism Analysis", because skin management is actually the management of resources. Let's start learning how to skin it.
Token)
#E61ABD
#38F709
#000000
#FFFFFF
2. Package the resource into an apk file and put it into the SD card (you can download the actual project from my network)
3. Implement ISkinUpdate for the Activity that requires skin replacement (this can be customized by yourself) Interface
public class MainActivity extends Activity implements ISkinUpdate,OnClickListener{private Button btn_main;private View main_view;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); this.setContentView(R.layout.activity_main);SkinApplication.getInstance().mActivitys.add(this);btn_main=(Button)this.findViewById(R.id.btn_main); btn_main.setOnClickListener(this);main_view=this.findViewById(R.id.main_view);} @Override protected void onResume() { super.onResume(); if(SkinPackageManager.getInstance(this).mResources!=null) { updateTheme(); Log.d("yzy", "onResume-->updateTheme"); } }@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {int id = item.getItemId();if (id == R.id.action_settings) {//Toast.makeText(this, "change skin", 1000).show();File dir=new File(Environment.getExternalStorageDirectory(),"plugins");File skin=new File(dir,"SkinPlugin.apk");if(skin.exists()){ SkinPackageManager.getInstance(MainActivity.this).loadSkinAsync(skin.getAbsolutePath(), new loadSkinCallBack() { @Override public void startloadSkin() { Log.d("yzy", "startloadSkin"); } @Override public void loadSkinSuccess() { Log.d("yzy", "loadSkinSuccess"); MainActivity.this.sendBroadcast(new Intent(SkinBroadCastReceiver.SKIN_ACTION)); } @Override public void loadSkinFail() { Log.d("yzy", "loadSkinFail"); } });}return true;}return super.onOptionsItemSelected(item);}@Overridepublic void updateTheme() {// TODO Auto-generated method stubif(btn_main!=null){try {Resources mResource=SkinPackageManager.getInstance(this).mResources;Log.d("yzy", "start and mResource is null-->"+(mResource==null));int id1=mResource.getIdentifier("main_btn_color", "color", "com.skin.plugin");btn_main.setBackgroundColor(mResource.getColor(id1));int id2=mResource.getIdentifier("main_background", "color","com.skin.plugin");main_view.setBackgroundColor(mResource.getColor(id2));//img_skin.setImageDrawable(mResource.getDrawable(mResource.getIdentifier("skin", "drawable","com.skin.plugin")));} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}}@Overrideprotected void onDestroy() {// TODO Auto-generated method stubSkinApplication.getInstance().mActivitys.remove(this);super.onDestroy();}@Overridepublic void onClick(View v) {// TODO Auto-generated method stubif(v.getId()==R.id.btn_main){Intent intent=new Intent(this,SecondActivity.class);this.startActivity(intent);}}}
This code mainly looks at onOptionsItemSelected. In this method, the Resources object corresponding to the resource apk is obtained through the resource apk path. Let's take a look at what SkinPacakgeManager has done.
/*** Resolve the skin resource package * com. skin. demo. SkinPackageManager * @ author yuanzeyao
* Create at 3:24:16, January 1, January 3, 2015 */public class SkinPackageManager {private static SkinPackageManager mInstance; private Context mContext;/*** current resource package name */public String mPackageName; /*** Skin Resources */public Resources mResources; private SkinPackageManager (Context mContext) {this. mContext = mContext;} public static SkinPackageManager getInstance (Context mContext) {if (mInstance = null) {mInstance = new SkinPackageManager (mContext);} return mInstance ;} /*** asynchronously load Skin Resources * @ param dexPath * The Skin Resources to be loaded * @ param callback * callback interface */public void loadSkinAsync (String dexPath, final loadSkinCallBack callback) {new AsyncTask
() {Protected void onPreExecute () {if (callback! = Null) {callback. startloadSkin () ;}}; @ Override protected Resources doInBackground (String... params) {try {if (params. length = 1) {String dexPath_tmp = params [0]; PackageManager mPm = mContext. getPackageManager (); PackageInfo mInfo = mPm. getPackageArchiveInfo (dexPath_tmp, PackageManager. GET_ACTIVITIES); mPackageName = mInfo. packageName; AssetManager assetManager = AssetManager. class. newInstance (); Method AddAssetPath = assetManager. getClass (). getMethod ("addAssetPath", String. class); addAssetPath. invoke (assetManager, dexPath_tmp); Resources superRes = mContext. getResources (); Resources skinResource = new Resources (assetManager, superRes. getDisplayMetrics (), superRes. getConfiguration (); SkinConfig. getInstance (mContext ). setSkinResourcePath (dexPath_tmp); return skinResource;} return null;} catch (Ex Ception e) {return null ;}}; protected void onPostExecute (Resources result) {mResources = result; if (callback! = Null) {if (mResources! = Null) {callback. loadSkinSuccess ();} else {callback. loadSkinFail () ;}}; cmd.exe cute (dexPath) ;}/ *** callback interface for loading resources * com. skin. demo. loadSkinCallBack * @ author yuanzeyao
* Create at January 4, 2015 1:45:48 */public static interface loadSkinCallBack {public void startloadSkin (); public void loadSkinSuccess (); public void loadSkinFail ();}}
After loadSkinAsync is called, if it succeeds, a skin change broadcast will be sent and the path of the current skin apk will be saved to the sp, so that the next time the app starts, the skin resources will be loaded directly.
The acceptance of skin change broadcast is registered in SkinApplication. After receiving the broadcast, call the updateTheme method of all started activities that require skin replacement to enable skin replacement.
Public class SkinApplication extends Application {private static SkinApplication mInstance = null; public ArrayList
MActivitys = new ArrayList
(); @ Overridepublic void onCreate () {// TODO Auto-generated method stubsuper. onCreate (); mInstance = this; String skinPath = SkinConfig. getInstance (this ). getSkinResourcePath (); if (! TextUtils. isEmpty (skinPath) {// If the skin has been changed, the SkinPackageManager skin needs to be loaded the second time it comes in. getInstance (this ). loadSkinAsync (skinPath, null);} SkinBroadCastReceiver. registerBroadCastReceiver (this);} public static SkinApplication getInstance () {return mInstance ;}@ Overridepublic void onTerminate () {// TODO Auto-generated method stubSkinBroadCastReceiver. unregisterBroadCastReceiver (this); super. onTerminate ();} public void changeSkin () {for (ISkinUpdate skin: mActivitys) {skin. updateTheme ();}}}
As the skin change here is only for changing the icon, background color, and so on, it is relatively simple. If you want to change the layout file, it will be a little more complicated and will not be introduced here, if you are interested, you can study it yourself ..