Plug-in development in Android----application of skin-changing principle analysis

Source: Internet
Author: User

First, preface

Today again to the weekend, feel the time is too fast. This week wife is angry, so can not and her happy, that can only write blog. So today, let's look at the application of the principle of skin-changing analysis. In a previous blog post I spoke about the basics of plugin development in Android: Knowledge of the ClassLoader. Students who have not seen can go to:

http://blog.csdn.net/jiangwei0910410003/article/details/41384667


second, the principle of introduction

Now there are many applications on the market have the function of skin, is to be able to provide users with some skins package, and then download, replace. And some of the skin is charged. For this function, there is no technical difficulty, but he contains a very fire at this stage of a technology: dynamic loading

Well, since the dynamic loading , then if there are unfamiliar classmates, you can go to see another blog:

http://blog.csdn.net/jiangwei0910410003/article/details/17679823


Let's take a look at one app on the market with an example of a skin-changing function: QQ space

Click on my Space + personalization + original theme + Select Download Theme


Download the theme and then you can replace it. Now, let's see where this theme bag is. Since the download must have been stored.

Two places can be put: one is the SD card, one is the application of the data directory

Let's take a look at the application's directory ( the ADB command is configured ):

First step: Get the package name of QQ space:

Open the QQ space app and don't quit. Then execute the command:adb shell dumpsys activity top


This command is still useful, you can quickly get an app's package name

We see the package name of the QQ space: Com.qzone

The second step: Enter the QQ application directory, view the corresponding resources


We found the Theme.xml file in his shared_prefs, and looking at the file, we can find the location of the corresponding skin:

/data/user/0/com.qzone/files/cache/qz_external_resource/theme_res/38

We go to this directory:


See the red circle above the directory structure and file name is not very familiar. Yes, this is what we get after extracting a normal apk. Then we can conclude that the QQ space skin package is actually an apk, and then dynamically load the APK, take the corresponding resources and then replace.


third, how to design a skin-changing plug-in

Well, since the above we read the skin-changing function of QQ space, but also know its general principle, the following we have to make our own DIY theme package.

About the dynamic loading of the relevant technology is not described in detail here, see my previous mention of the two related articles of introduction.

We need to build three projects here:

Host Program (main program): Resourceloader

Project of Theme Pack 1: RESOURCELOADERAPK1

Project of Theme Pack 2: RESOURCELOADERAPK2

In the host program we need to write code that is dynamically loaded:

Here's a look at the specific code:

Mainactivity.java

Package Com.example.resourceloader;import Java.io.file;import Java.lang.reflect.field;import Java.lang.reflect.method;import Android.annotation.suppresslint;import Android.content.context;import Android.graphics.drawable.drawable;import Android.os.bundle;import Android.util.log;import Android.view.View; Import Android.view.view.onclicklistener;import Android.widget.imageview;import android.widget.LinearLayout; Import Android.widget.textview;public class Mainactivity extends Baseactivity {/** * need to replace the subject of the control * Here is the list of three: TextView, Imageview,linearlayout */private TextView textv;private ImageView imgv;private linearlayout layout; @Overrideprotected void OnCreate (Bundle savedinstancestate) {super.oncreate (savedinstancestate); Setcontentview (r.layout.activity_ Main); Textv = (TextView) Findviewbyid (r.id.text); IMGV = (ImageView) Findviewbyid (r.id.imageview); layout = ( LinearLayout) Findviewbyid (r.id.layout); Findviewbyid (R.ID.BTN1). Setonclicklistener (New Onclicklistener () {@ overridepublic void OnClick (View arg0) {String Filesdir = Getcachedir (). GetAbsolutePath (); String FilePath = filesdir + file.separator + "apk1.apk"; LOG.I ("Loader", "FilePath:" +filepath); LOG.I ("Loader", "Isexist:" +new File (FilePath). exists ());//loadresources (FilePath);//setcontent ();// Printresourceid (); SetContent1 ();//printrfield ();}}); Findviewbyid (R.ID.BTN2). Setonclicklistener (New Onclicklistener () {@Overridepublic void OnClick (View v) {String        Filesdir = Getcachedir (). GetAbsolutePath (); String FilePath = filesdir + file.separator + "apk2.apk";//loadresources (FilePath); SetContent ();});} /** * Dynamically loads the resources in the theme package and then replaces each control */@SuppressLint ("Newapi") private void SetContent () {Try{class clazz = Classloader.loadclass ( "Com.example.resourceloaderapk.UIUtil"); method = Clazz.getmethod ("gettextstring", Context.class); String str = (string) Method.invoke (null, this); Textv.settext (str); method = Clazz.getmethod ("Getimagedrawable", Context.class);D rawable drawable = (drawable) method.invoke (null, this); Imgv.setbackground (drawable)method = Clazz.getmethod ("GetLayout", Context.class); View view = (view) Method.invoke (null, this); Layout.addview (view);} catch (Exception e) {log.i ("Loader", "Error:" +log.getstacktracestring (e));}} /** * Another way to get */private void SetContent1 () {int stringid = Gettextstringid (); int drawableid = Getimgdrawableid (); int Layo Utid = Getlayoutid (); LOG.I ("Loader", "Stringid:" +stringid+ ", Drawableid:" +drawableid+ ", LayoutID:" +layoutid ");} @SuppressLint ("Newapi") private int Gettextstringid () {Try{class clazz = Classloader.loadclass (" Com.example.resourceloaderapk1.r$string "); Field field = Clazz.getfield ("app_name"), int resId = (int) field.get (null); return resId;} catch (Exception e) {log.i ("Loader", "Error:" +log.getstacktracestring (e));} return 0;} @SuppressLint ("Newapi") private int Getimgdrawableid () {Try{class clazz = Classloader.loadclass (" Com.example.resourceloaderapk1.r$drawable "); Field field = Clazz.getfield ("Ic_launcher"), int resId = (int) field.get (null); return resId;} catch (Exception e) {log.i ("Loader", "errOr: "+log.getstacktracestring (e));} return 0;} @SuppressLint ("Newapi") private int Getlayoutid () {Try{class clazz = Classloader.loadclass (" Com.example.resourceloaderapk1.r$layout "); Field field = Clazz.getfield ("Activity_main"), int resId = (int) field.get (null); return resId;} catch (Exception e) {log.i ("Loader", "Error:" +log.getstacktracestring (e));} return 0;} @SuppressLint ("Newapi") private void Printresourceid () {Try{class clazz = Classloader.loadclass (" Com.example.resourceloaderapk.UIUtil "); method = Clazz.getmethod ("Gettextstringid", null); Object obj = Method.invoke (null, NULL); LOG.I ("Loader", "Stringid:" +obj); LOG.I ("Loader", "NewId:" +r.string.app_name); method = Clazz.getmethod ("Getimagedrawableid", null); obj = Method.invoke (null, NULL); LOG.I ("Loader", "Drawableid:" +obj); LOG.I ("Loader", "NewId:" +r.drawable.ic_launcher); method = Clazz.getmethod ("Getlayoutid", null); obj = Method.invoke ( NULL, NULL); LOG.I ("Loader", "LayoutID:" +obj); LOG.I ("Loader", "NewId:" +r.layout.activity_main); catch (exception e) {log.i ("Loader", "Error:" +log.getstacktracestring (e));}} private void Printrfield () {Class clazz = R.id.class; field[] fields = Clazz.getfields (); for (Field field:fields) {log.i ("Loader", "Fields:" +field);} Class Clazzs = R.layout.class; field[] Fieldss = Clazzs.getfields (); for (Field Field:fieldss) {log.i ("Loader", "FIELDSS:" +field);}}}

The code here is not very difficult, it is we use the Dexclassloader class to load the APK package for each theme and then invoke the methods in the APK package using the reflected method to get the resources.

Here's a look at the topic package Engineering Code:

Uiutil.java

Package Com.example.resourceloaderapk;import Android.content.context;import android.graphics.drawable.Drawable; Import Android.view.layoutinflater;import Android.view.view;import Com.example.resourceloaderapk1.r;public class Uiutil {public static String gettextstring (Context ctx) {return ctx.getresources (). getString (R.string.app_name);} public static drawable getimagedrawable (Context ctx) {return ctx.getresources (). getdrawable (R.drawable.ic_launcher);} public static View GetLayout (Context ctx) {return Layoutinflater.from (CTX). Inflate (r.layout.activity_main, null);} public static int Gettextstringid () {return r.string.app_name;} public static int Getimagedrawableid () {return r.drawable.ic_launcher;} public static int Getlayoutid () {return r.layout.activity_main;}}
This class is to provide external access to resources, we in the host program is the reflection of the method to obtain resources, this method we provide two ways to obtain resources: one is to directly return the contents of the resource, there is a return of a resource ID.

About the project of theme Package 2 is not introduced here, the code is the same, but the resources are not the same.

We run two theme packs and get two apk

resourceloaderapk1.apk

resourceloaderapk2.apk

At this point we use the adb push command to place the two apk in the host program's cache directory.


Warm tips:

It is not possible to place the apk that needs to be loaded outside the sandbox directory of the non-host program, otherwise it will fail to load and throw an exception. The concept of the sandbox directory of the program is actually very good understanding: is the/data/data/xxx.xxx/directory, is the directory is unique to this program, the other do not share the rights of the app is not accessible (of course, in addition to root privileges), this is actually very good to understand why to do so, Google is also for security, and the Apk/dex/jar it needs to load should be protected.


Of course, it does not have to be placed in the cache directory, as long as the sandbox directory can be, create a new directory is also possible. However, the cache directory is generally used.


Project Address: http://download.csdn.net/detail/jiangwei0910410003/9008423


At this point we run the host program:


Two btn, can load different theme content, but the problem comes. After clicking Find no effect, catch exception, we print log to see:

ADB logcat-s Loader


He said he could not find the resource exception, let's analyze it.

In theme 1 projects, we call the resources in theme 1apk r.string.xxx

But we know that the time to get the resources is to use the resource class to get, for a program, a context will only hold a resource object, but when we load the APK, the theme apk does not get the corresponding context. Because dynamic loading does not want to run a program properly, each program has a global context variable, but it is not loaded. Then someone said: In the code, we use reflection to get it when the host's context variable is not passed past it?


Yes, there seems to be no problem, but this is actually the question: how the host context can load the resources in the plugin apk, we know that an app's project resource file will be insinuate to the R file, and this r file package name is the application's package name, A package name typically corresponds to a context. So now that we pass the context of the host and the package name of the corresponding host, that is, the R file that hosts the project, we cannot find the corresponding resource. In fact, the problem we have to solve is to add the plugin apk in the host apk. This is a way to use the mechanism of reflection:

By calling the Addassetpath method in Assetmanager, we can load a resource from an apk into the resources, because Addassetpath is a hidden API and we can't call it directly, so only through reflection, here's the declaration, By commenting, we can see that the path can be either a zip file or a resource directory, and the APK is a zip, so just pass the APK path to it and the resource will be loaded into Assetmanager. And then create a new resources object through Assetmanager, which is the resource in the APK that we can use.


Let's look at the code:

protected void Loadresources (String dexpath) {          try {              Assetmanager Assetmanager = AssetManager.class.newInstance ();              Method Addassetpath = Assetmanager.getclass (). GetMethod ("Addassetpath", string.class);              Addassetpath.invoke (Assetmanager, dexpath);              Massetmanager = Assetmanager;          } catch (Exception e) {              e.printstacktrace ();          }          Resources superres = Super.getresources ();          Superres.getdisplaymetrics ();          Superres.getconfiguration ();          Mresources = new Resources (Massetmanager, Superres.getdisplaymetrics (), superres.getconfiguration ());          Mtheme = Mresources.newtheme ();          Mtheme.setto (Super.gettheme ());    }
The parameter is the path to the package that needs to load the resource.

Of course we also need to rewrite the context of the three methods:

@Override public  Assetmanager getassets () {  return massetmanager = = null? Super.getassets (): Massetmanager;
   }  @Override public  Resources getresources () {  return mresources = = null? Super.getresources (): mresources;  }  @Override public  Theme gettheme () {  return mtheme = = null? Super.gettheme (): Mtheme;  }
The three overriding methods are to let the system get the variables we loaded after the APK package.

Here, let's change the code: Add a Baseactivity class to the host project:

Baseactivity.java

Package Com.example.resourceloader;import Java.io.file;import Java.lang.reflect.method;import android.app.Activity ; Import Android.content.res.assetmanager;import Android.content.res.resources;import Android.content.res.resources.theme;import Android.os.bundle;import Dalvik.system.dexclassloader;public Class  Baseactivity extends activity{protected assetmanager massetmanager;//resource Manager protected Resources mresources;//Resource Protected Theme mtheme;//theme @Overrideprotected void OnCreate (Bundle savedinstancestate) {super.oncreate ( Savedinstancestate);} protected void Loadresources (String dexpath) {try {Assetmanager Assetmanager = Assetmanager.class.              Newinstance ();              Method Addassetpath = Assetmanager.getclass (). GetMethod ("Addassetpath", String.class);              Addassetpath.invoke (Assetmanager, Dexpath);          Massetmanager = Assetmanager;          } catch (Exception e) {e.printstacktrace (); } Resources superres = Super.gEtresources ();          Superres.getdisplaymetrics ();          Superres.getconfiguration ();          Mresources = new Resources (Massetmanager, Superres.getdisplaymetrics (), superres.getconfiguration ());          Mtheme = Mresources.newtheme ();    Mtheme.setto (Super.gettheme ());  } @Override Public Assetmanager getassets () {return Massetmanager = = null? Super.getassets (): Massetmanager;  } @Override Public Resources getresources () {return mresources = = null? Super.getresources (): mresources;  } @Override Public Theme Gettheme () {return mtheme = = null? Super.gettheme (): Mtheme; } }

Call the LoadResource method in Mainactivity:


At this point we are running the host program:


Click on Topic 1: We find that the text has become: resourceloaderapk; Because the pictures here are all used robots so it doesn't seem to change, see the bottom linearlayout load the layout XML content in the theme package.

Click on theme 2: The effect is the same, but the content is in Theme Pack 2apk.

All right. Here we have developed our own skin-changing function perfectly. But some students may think that this is the function of skin change, do not see the effect? My example is not to completely develop a skin-changing project. Just introduce the principle. But there's really no difficulty in changing the skin, we need to solve some problems:

1. For controls that need to replace a theme, you need to define it uniformly.

2, for each topic package of the project's external interface to unify (or to conform to certain specifications), such as this example must have a theme package:

Com.example.resourceloaderapk.UIUtil class, and there must be three methods in this class: Gettextstring,getimagedrawable,getlayout

So this is a specification, of course, I am not very good at the specifications here, the correct approach is to provide an interface, and then each theme package project must implement this interface, and then the theme package engineering and the host project all contain this interface, so it can be very flexible.

3, the general theme package apk is downloaded from the Internet, so we need to set up a few default theme packages in the local, if downloaded from the Internet, the theme package has a problem, we go to load the default theme. This way, there will be no abnormal conditions.


Iv. Summary of issues

In fact, this article we see above we are actually solving a problem, is how to load the theme package APK resources. In fact, some people have the idea that we will need to package all the resources (can be any format of compressed package), downloaded from the Internet, unzip the file, through the stream to read each resource file into the project, in fact, this way is feasible, but the efficiency of a lot of problems (I have not tried anyway). So this is a convenient and efficient way to do this.


v. Practical uses

The main role of this technology in this article is now on the market:

1, online replacement theme (skin), language packs, etc.

2, reduce the size of the main APK package, will not be very important resources packaged into the APK into the server.


Vi. Summary

This article mainly introduces the application of skin-changing principle, the core technology is: How to load the plug-in APK resources. Follow-up will also explain the use of plug-ins in Android: Free installation of running programs, production ...


To interrupt a message

$*********************************************************************************************$

Bo Main recommendation:

The wind is easy to water cold, "naïve" a go no more. How to find the innocence of that happiness. Small make-up recommended app: innocence without harmonic

Download method: Pea pod, app Bao, 360 mobile phone assistant, Baidu Mobile assistant, Android, 91 market search: naïve and non-harmonic

Follow us on: View Details

$*********************************************************************************************$









Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.

Plug-in development in Android----application of skin-changing principle analysis

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.