Learning Research of dynamic loading mechanism in Android _android

Source: Internet
Author: User
Tags reflection

In the current software and hardware environment, Native app and web app have obvious advantages in user experience, but in actual project some will upgrade clients frequently because of frequent changes in the business, resulting in poor user experience, which is the advantage of web App. This paper combs and practices the information of the dynamic loading jar on the online Android to share with you here, trying to improve the malady of frequent escalation.

Android application development in general, conventional development methods and code architecture can meet our common needs. But there are special problems that often lead us to further contemplation. We generate insights from meditation and thus produce new forms of technology.

How do I develop an Android application that can customize controls? Like eclipse, you can load plug-ins dynamically; How do you let Android apps execute unpredictable code on the server? How do you encrypt an Android application and only decrypt it while it is executing, preventing it from being cracked? ......

Friends who are familiar with Java technology may realize that we need to use the class loader to load executing classes flexibly. This is already a fairly mature technique in Java, but most of us are unfamiliar with Android.

Class loading mechanism

Dalvik virtual machines, like other Java virtual machines, first need to load the corresponding class into memory when running the program. In Java standard virtual machines, class loading can be read from the class file, or other forms of binary streams, so we often use this to load the class manually when the program is run, thus achieving the purpose of dynamic code load execution.

However, Dalvik virtual machines are not standard Java virtual machines, so they have the same place and different in the class loading mechanism. We must treat each other differently.

For example, when using standard Java virtual machines, we often customize class loaders that inherit from ClassLoader. The class is then loaded from a binary stream by the DefineClass method. However, this is not feasible in Android, there is no need for detours. See source code We know that the DefineClass method of ClassLoader in Android is specifically the DefineClass local static method that invokes Vmclassloader. This local method does nothing but throw a "unsupportedoperationexception", and even the return value is empty

static void Dalvik_java_lang_vmclassloader_defineclass (const u4* args,jvalue* pResult) { 
  object* loader = (object*) Args[0]; 
  stringobject* nameobj = (stringobject*) args[1]; 
  Const U1* data = (const u1*) args[2]; 
  int offset = args[3]; 
  int len = args[4]; 
  object* PD = (object*) args[5]; 
  char* name = NULL; 
  Name = Dvmcreatecstrfromstring (nameobj); 
  LOGE ("Error:defineclass (%p,%s,%p,%d,%d,%p) \ n", loader, name, data, offset, Len, PD); 
  Dvmthrowexception ("ljava/lang/unsupportedoperationexception;", "Can ' t load this type of class file"); 
  Free (name); 
  Return_void (); 
} 

Dalvik virtual machine class loading mechanism

So how do we implement a dynamic load class if the ClassLoader doesn't work in the Dalvik virtual machine? Android derives two classes from ClassLoader: Dexclassloader and Pathclassloader. What you need to specifically note is an annotated code in Pathclassloader:

/*--this doesn ' t work in current version of dalvik-- 
  if (data!= null) { 
    SYSTEM.OUT.PRINTLN ("---Found class" + Name 
      + "in zip[" + i + "] '" + mzips[i].getname () + "'"); 
    int dotindex = Name.lastindexof ('. '); 
    if (Dotindex!=-1) { 
      String packagename = name.substring (0, dotindex); 
      Synchronized (this) { 
        Package packageobj = Getpackage (packagename); 
        if (packageobj = = null) {definepackage (packagename, NULL, NULL, NULL, NULL, NULL, NULL, NULL 
              ); 
        } 
    } 
    Return defineclass (name, data, 0, data.length); 
*/ 

This, on the other hand, is a testament to the fact that the DefineClass function was castrated in the Dalvik virtual machine. In these two class loaders, which inherit from ClassLoader, are essentially overloaded with ClassLoader findclass methods. When performing loadclass, we can refer to the ClassLoader part of the source code:

Protected class<?> loadclass (String className, Boolean resolve) throws ClassNotFoundException { 
class<? > clazz = Findloadedclass (className); 
  if (clazz = = null) { 
    try { 
      clazz = Parent.loadclass (ClassName, false); 
    } catch (ClassNotFoundException e) {
   //Don ' t want to the This. 
    } 
    if (clazz = = null) { 
      clazz = Findclass (className); 
    } 
  } 
  return clazz; 
} 

Therefore, both Dexclassloader and Pathclassloader belong to class loaders that conform to the parental delegation model (because they do not have overloaded loadclass methods). That is, before loading a class, they go back and check whether their own class loaders have loaded the class. If it has already been loaded, it will be returned directly, without repeated loading.

Dexclassloader and Pathclassloader actually implement class loading by Dexfile this class. Here, incidentally, the Dalvik virtual machine identifies a Dex file, not a class file. Therefore, the file we are loading for the class can only be a Dex file, or a. apk or. jar file that contains a Dex file.

It may be thought that, since dexfile can load classes directly, why do we still use ClassLoader subclasses? Dexfile When you load a class, you specifically invoke the member method LoadClass or Loadclassbinaryname. Where Loadclassbinaryname needs to "." In the class name containing the package name. Convert to "/" Let's take a look at the LoadClass code to make it clear:

Public Class loadclass (string name, ClassLoader loader) { 
    string slashname = Name.replace ('. ', '/'); 
    Return Loadclassbinaryname (slashname, loader); 
} 

There is a comment in front of this code, and the key part of the interception is that: If you are not calling this from a class loader, the This is most likely does going to do what your want. Use {@link class#forname (String)} instead. That's why we need to use the ClassLoader subclass. As for how it verifies whether this method is invoked in ClassLoader, I have no research, and if you are interested you can go further.

There is a detail that may not be easy for everyone to notice. The Pathclassloader is generated by the constructor new Dexfile (path), while Dexclassloader gets the Loaddex object through its static method Outpath (path, dexfile, 0). The difference between the two is that Dexclassloader needs to provide a writable Outpath path for releasing the. apk package or the Dex file in the. jar package. In other words, Pathclassloader cannot voluntarily release Dex from the ZIP package, so it only supports the direct operation of the Dex file, or the installed APK (because the installed apk has a cached Dex file in the cache). The Dexclassloader can support. APK,. jar and. dex files, and will release the Dex file on the specified outpath path.

In addition, Pathclassloader calls the Dexfile Loadclassbinaryname when the class is loaded, and Dexclassloader calls LoadClass. Therefore, the class full name needs to be replaced with "/" when using Pathclassloader.

Actual operation

The tools used are more general: Javac, DX, eclipse, etc. where DX tools are best to indicate--no-strict because the path of the class file may not match

After loading the class, we can usually use this class through the Java reflection mechanism but this is relatively inefficient, and the old reflection code is also more complex and messy. A better approach is to define a interface and write the interface into the container end. The class to be loaded inherits from this interface, and there is a constructor with an empty argument so that we can generate the object and then cast the object to the interface object through the Newinstance method of class, so that the member method is called directly. Here is the specific implementation step:

First step:

Write a dynamic code class:


Package com.dynamic.interfaces; 
Import android.app.Activity; 
/** 
 * The interface of the dynamic loading class 
/public interface Idynamic { 
  /** initialization method * 
  /public void init (active activity); 
  /** Custom Method * 
  /public void Showbanner (); 
  public void ShowDialog (); 
  public void Showfullscreen (); 
  public void Showappwall (); 
  /** Destroy Method * * Public 
  void Destory (); 
} 

The implementation class code is as follows:

Package Com.dynamic.impl; 
 
Import android.app.Activity; 
Import Android.widget.Toast; 
 
Import com.dynamic.interfaces.IDynamic; 
 
/** 
 * Dynamic class implementation 
 * * 
 * 
/public class dynamic implements idynamic{ 
 
  private activity mactivity; 
   
  @Override public 
  void init [activity activity] { 
    mactivity = activity; 
  } 
   
  @Override public 
  void Showbanner () { 
    toast.maketext (mactivity, "I am Showbannber method", 1500). Show (); 
 
  @Override public 
  void ShowDialog () { 
    toast.maketext (mactivity, "I am ShowDialog method", 1500). Show (); 
 
  @Override public 
  void Showfullscreen () { 
    toast.maketext (mactivity, "I am Showfullscreen method", 1500). Show (); 
 
  @Override public 
  void Showappwall () { 
    toast.maketext (mactivity, "I am Showappwall method", 1500). Show (); 
 
  @Override public 
  void Destory () { 
  } 
 
} 

So the dynamic class is developed,

Step Two:

Package The dynamic classes that are developed above to the. jar, note here is only package implementation class Dynamic.java, do not package interface class Idynamic.java,

Then copy the packaged jar file to the Platform-tools directory in the Android installation directory and use the DX command: (my jar file is Dynamic.jar)

DX--dex--output=dynamic_temp.jar Dynamic.jar

This creates the Dynamic_temp.jar, what's the difference between this jar and Dynamic.jar?

The main task of this command is to first compile the Dynamic.jar into a dynamic.dex file (a bytecode file known to the Android virtual machine), and then compress the Dynamic.dex file into Dynamic_ Temp.jar, of course, you can also compress into a. zip format, or directly compiled into a. apk file, which will be said later.

It's not finished here, because what do you want to use to connect the dynamic class to the target class? That's the interface of the dynamic class, so it's time to hit a. jar package, which is just the interface class Idynamic.java.

Then reference the. jar file to the target class, and here's a look at the implementation of the target class:

Package Com.jiangwei.demo; 
Import Java.io.File; 
 
Import java.util.List; 
Import android.app.Activity; 
Import android.content.Intent; 
Import Android.content.pm.ActivityInfo; 
Import Android.content.pm.PackageManager; 
Import Android.content.pm.ResolveInfo; 
Import Android.os.Bundle; 
Import android.os.Environment; 
Import Android.view.View; 
Import Android.widget.Button; 
 
Import Android.widget.Toast; 
 
Import com.dynamic.interfaces.IDynamic; 
Import Dalvik.system.DexClassLoader; 
 
Import Dalvik.system.PathClassLoader; 
   
  public class Androiddynamicloadclassactivity extends activity {//dynamic class loading interface private Idynamic Lib; 
    @Override public void OnCreate (Bundle savedinstancestate) {super.oncreate (savedinstancestate); 
    Setcontentview (R.layout.main); 
    Initialize the component Button showbannerbtn = (button) Findviewbyid (R.ID.SHOW_BANNER_BTN); 
    Button showdialogbtn = (button) Findviewbyid (R.ID.SHOW_DIALOG_BTN); Button showfullscreenbtn = (Button) findviewbyiD (R.ID.SHOW_FULLSCREEN_BTN); 
    Button showappwallbtn = (button) Findviewbyid (R.ID.SHOW_APPWALL_BTN); /** use Dexclassloader method to load class///dex Compressed file path (can be apk,jar,zip format) String Dexpath = Environment.getexternalstoragedirecto 
    Ry (). toString () + File.separator + "dynamic.apk"; 
    Dex decompression After releasing the directory//string Dexoutputdir = Getapplicationinfo (). DataDir; 
    String dexoutputdirs = Environment.getexternalstoragedirectory (). toString (); Define Dexclassloader//First parameter: is DEX compressed file path//second parameter: is the directory that Dex unpacked//The third parameter: The local library file directory that is a/C + + dependent, can be null//fourth parameter: 
     
    is the class loader Dexclassloader cl = new Dexclassloader (Dexpath,dexoutputdirs,null,getclassloader ()); /** use the Pathclassloader method to load the class///Create an intent to find the specified apk: "Com.dynamic.impl here is defined apk in the Androidmainfest.xml file in the specified <action  
    Name= "Com.dynamic.impl"/> Intent Intent = new Intent ("Com.dynamic.impl", null);  
    Get Package Manager Packagemanager PM = Getpackagemanager (); list<resolveinfo> resolveinfoes = Pm.querYintentactivities (Intent, 0);  
    Gets the information of the specified activity activityinfo actinfo = resolveinfoes.get (0). Activityinfo;  
    Get apk directory or jar directory String Apkpath = ActInfo.applicationInfo.sourceDir;  
    The directory String libpath = ActInfo.applicationInfo.nativeLibraryDir of the native code; Create the ClassLoader and load Dex into the virtual machine. The first parameter: Specifies the path to the APK installation, which should be noted only by ActInfo.applicationInfo.sourceDir to get//The second parameter: a local Cuven that is C + + dependent 
    Catalog, can be NULL//third parameter: is the class loader at the top level pathclassloader PCL = new Pathclassloader (Apkpath,libpath,this.getclassloader ()); Load class Try {//com.dynamic.impl.dynamic is a dynamic class name//use Dexclassloader load class//class Libproviderclazz 
      = Cl.loadclass ("com.dynamic.impl.Dynamic"); 
      Use Pathclassloader to load classes class Libproviderclazz = Pcl.loadclass ("com.dynamic.impl.Dynamic"); 
      Lib = (idynamic) libproviderclazz.newinstance (); 
      If (lib!= null) {lib.init (androiddynamicloadclassactivity.this); } catch (Exception Exception) {excEption.printstacktrace (); /** the methods in the dynamic class are called separately under Showbannerbtn.setonclicklistener (new View.onclicklistener () {public void OnClick (Vi 
        EW view) {if (lib!= null) {Lib.showbanner (); 
        }else{Toast.maketext (Getapplicationcontext (), "Class load Failed", 1500). Show (); 
    } 
      } 
    }); Showdialogbtn.setonclicklistener (New View.onclicklistener () {public void OnClick (view view) {if (Lib!= n 
        ull) {lib.showdialog (); 
        }else{Toast.maketext (Getapplicationcontext (), "Class load Failed", 1500). Show (); 
    } 
      } 
    }); Showfullscreenbtn.setonclicklistener (New View.onclicklistener () {public void OnClick (view view) {if (Lib 
        != null) {Lib.showfullscreen (); 
        }else{Toast.maketext (Getapplicationcontext (), "Class load Failed", 1500). Show (); 
    } 
      } 
    }); Showappwallbtn.setonclicklistener (New View.onclicklistener () {public void oncliCK (view view) {if (lib!= null) {Lib.showappwall (); 
        }else{Toast.maketext (Getapplicationcontext (), "Class load Failed", 1500). Show (); 
  } 
      } 
    }); 
 } 
}

This defines a idynamic interface variable, and uses both Dexclassloader and Pathclassloader to load the class, which first says

Dexclassloader loading:
//define Dexclassloader 
//First parameter: is Dex compressed file path 
//second parameter: is the directory that Dex unpacked 
//The third parameter: is the c/c+ + dependent local library file directory, can be null 
//Fourth parameter: is the class loader at the top level 
dexclassloader cl = new Dexclassloader (Dexpath,dexoutputdirs,null, getClassLoader ()); 

As has been said above, Dexclassloader is inherited from the ClassLoader class, which is the parameter description:

The first parameter is the path of Dex's compressed file: This is the directory we have compiled from the Dynamic_temp.jar, and of course, the. zip and. apk format.

The second parameter is the directory that Dex has unpacked: This is the directory where the Dex file is extracted from the. jar,.zip,.apk file, which is different from the Pathclassloader method. You can also see that the Pathclassloader method does not have this parameter, which is really the difference between these two classes:

Pathclassloader cannot voluntarily release Dex from the ZIP package, so it only supports the direct operation of the DEX format file, or the installed APK (because the installed apk has a cached Dex file in the phone's Data/dalvik directory). The Dexclassloader can support. APK,. jar and. dex files, and will release the Dex file on the specified outpath path.

We can, however, specify the location of the extracted Dex file by Dexclassloader, but we do not generally do so because it exposes the Dex file, so we generally don't store the. jar/.zip/.apk compressed file where the user can perceive it, Also extracting Dex's directory is not for users to see.

The third argument and the fourth parameter are not much used, so there is no explanation for this.

One thing to note here is the Pathclassloader method, where the first parameter is the path that Dex holds, where it passes:

Get apk directory or jar directory  
String apkpath = ActInfo.applicationInfo.sourceDir;  

The specified apk installation path, this value can only be obtained, or it will load the class failed

Step Three:

To run the target class:

The work to be done is:

If you are loading a class in Dexclassloader mode: You need to place the. jar or. zip or. apk file in the specified directory, and I'll put it in the root directory of the SD card for convenience.

If you are using the Pathclassloader method to load a class: This time you need to install the dynamic.apk to the phone, or you can not find the activity, but also note that:

Creates an intent to find the specified apk: here the "Com.dynamic.impl" is the <action name= "Com.dynamic.impl" defined in the Androidmainfest.xml file in the specified apk >  
Intent Intent = new Intent ("Com.dynamic.impl", null);  

The Com.dynamic.impl here is an action that needs to be defined in the specified apk, which is a convention between dynamic APK and target apk.
Run results

Click Showbanner to display a toast, successfully run the code in the dynamic Class!

In fact, a better way is to get the dynamic. jar.zip.apk file from the network, secure and reliable, while the local target project can execute different logic without the need to change the code.

Some ideas about code encryption

It was originally conceived to encrypt the Dex file and then write the decryption code on the native layer through JNI. After decryption, the binary stream is transmitted directly, and then the class is loaded into memory by DefineClass.

You can now do this, but since you cannot use DefineClass directly, you must pass the file path to the Dalvik virtual machine kernel, so the decrypted file needs to be written to disk, increasing the risk of being cracked.

The Dalvik virtual machine kernel only supports loading classes from the Dex file is not flexible, because without a very deep research kernel, I'm not sure if the Dalvik virtual machine itself is not supported or Android was castrated when porting. But believe that Dalvik or Android open source projects are trying to be able to support raw data definition classes.

We can see in the document Google said: Jar or APK file with "Classes.dex". (May expand this to include "Raw DEX" in the future.) In the Android Dalvik source code we can also see Rawdexfile figure (but not specifically implemented)

Before rawdexfile out, we can only use this type of encryption that has a certain risk. Need to pay attention to the release of Dex file path and Rights management, in addition, after loading the class, unless for other purposes, you should immediately delete the temporary decryption file.

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.