• Android中的动态加载机制


    在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势。本文对网上Android动态加载jar的资料进行梳理和实践在这里与大家一起分享,试图改善频繁升级这一弊病。

    Android应用开发在一般情况下,常规的开发方式和代码架构就能满足我们的普通需求。但是有些特殊问题,常常引发我们进一步的沉思。我们从沉思中产生顿悟,从而产生新的技术形式。
    如何开发一个可以自定义控件的Android应用?就像eclipse一样,可以动态加载插件;如何让Android应用执行服务器上的不可预知的代码?如何对Android应用加密,而只在执行时自解密,从而防止被破解?……
    熟悉Java技术的朋友,可能意识到,我们需要使用类加载器灵活的加载执行的类。这在Java里已经算是一项比较成熟的技术了,但是在Android中,我们大多数人都还非常陌生。

    实际操作

    使用到的工具都比较常规:javac、dx、eclipse等其中dx工具最好是指明--no-strict,因为class文件的路径可能不匹配
    加载好类后,通常我们可以通过Java反射机制来使用这个类但是这样效率相对不高,而且老用反射代码也比较复杂凌乱。更好的做法是定义一个interface,并将这个interface写进容器端。待加载的类,继承自这个interface,并且有一个参数为空的构造函数,以使我们能够通过Class的newInstance方法产生对象然后将对象强制转换为interface对象,于是就可以直接调用成员方法了,下面是具体的实现步骤了:
    第一步:

    编写好动态代码类:

    1. package com.dynamic.interfaces;  
    2. import android.app.Activity;  
    3. /** 
    4.  * 动态加载类的接口 
    5.  */  
    6. public interface IDynamic {  
    7.     /**初始化方法*/  
    8.     public void init(Activity activity);  
    9.     /**自定义方法*/  
    10.     public void showBanner();  
    11.     public void showDialog();  
    12.     public void showFullScreen();  
    13.     public void showAppWall();  
    14.     /**销毁方法*/  
    15.     public void destory();  
    16. }  

    实现类代码如下:

     
    1. package com.dynamic.impl;  
    2.   
    3. import android.app.Activity;  
    4. import android.widget.Toast;  
    5.   
    6. import com.dynamic.interfaces.IDynamic;  
    7.   
    8. /** 
    9.  * 动态类的实现 
    10.  */  
    11. public class Dynamic implements IDynamic{  
    12.   
    13.     private Activity mActivity;  
    14.       
    15.     @Override  
    16.     public void init(Activity activity) {  
    17.         mActivity = activity;  
    18.     }  
    19.       
    20.     @Override  
    21.     public void showBanner() {  
    22.         Toast.makeText(mActivity, "我是ShowBannber方法", 1500).show();  
    23.     }  
    24.   
    25.     @Override  
    26.     public void showDialog() {  
    27.         Toast.makeText(mActivity, "我是ShowDialog方法", 1500).show();  
    28.     }  
    29.   
    30.     @Override  
    31.     public void showFullScreen() {  
    32.         Toast.makeText(mActivity, "我是ShowFullScreen方法", 1500).show();  
    33.     }  
    34.   
    35.     @Override  
    36.     public void showAppWall() {  
    37.         Toast.makeText(mActivity, "我是ShowAppWall方法", 1500).show();  
    38.     }  
    39.   
    40.     @Override  
    41.     public void destory() {  
    42.     }  
    43.   
    44. }  

    这样动态类就开发好了

    第二步:

    将上面开发好的动态类打包成.jar,这里要注意的是只打包实现类Dynamic.java,不打包接口类IDynamic.java,

    然后将打包好的jar文件拷贝到android的安装目录中的platform-tools目录下,使用dx命令:(我的jar文件是dynamic.jar)

    dx --dex --output=dynamic_temp.jar dynamic.jar

    这样就生成了dynamic_temp.jar,这个jar和dynamic.jar有什么区别呢?

    其实这条命令主要做的工作是:首先将dynamic.jar编译成dynamic.dex文件(Android虚拟机认识的字节码文件),然后再将dynamic.dex文件压缩成dynamic_temp.jar,当然你也可以压缩成.zip格式的,或者直接编译成.apk文件都可以的,这个后面会说到。

    到这里还不算完事,因为你想想用什么来连接动态类和目标类呢?那就是动态类的接口了,所以这时候还要打个.jar包,这时候只需要打接口类IDynamic.java了

    然后将这个.jar文件引用到目标类中,下面来看一下目标类的实现:

     
    1. package com.jiangwei.demo;  
    2.   
    3. import java.io.File;  
    4. import java.util.List;  
    5.   
    6. import android.app.Activity;  
    7. import android.content.Intent;  
    8. import android.content.pm.ActivityInfo;  
    9. import android.content.pm.PackageManager;  
    10. import android.content.pm.ResolveInfo;  
    11. import android.os.Bundle;  
    12. import android.os.Environment;  
    13. import android.view.View;  
    14. import android.widget.Button;  
    15. import android.widget.Toast;  
    16.   
    17. import com.dynamic.interfaces.IDynamic;  
    18.   
    19. import dalvik.system.DexClassLoader;  
    20. import dalvik.system.PathClassLoader;  
    21.   
    22. public class AndroidDynamicLoadClassActivity extends Activity {  
    23.       
    24.     //动态类加载接口  
    25.     private IDynamic lib;  
    26.       
    27.     @Override  
    28.     public void onCreate(Bundle savedInstanceState) {  
    29.         super.onCreate(savedInstanceState);  
    30.         setContentView(R.layout.main);  
    31.         //初始化组件  
    32.         Button showBannerBtn = (Button) findViewById(R.id.show_banner_btn);  
    33.         Button showDialogBtn = (Button) findViewById(R.id.show_dialog_btn);  
    34.         Button showFullScreenBtn = (Button) findViewById(R.id.show_fullscreen_btn);  
    35.         Button showAppWallBtn = (Button) findViewById(R.id.show_appwall_btn);  
    36.         /**使用DexClassLoader方式加载类*/  
    37.         //dex压缩文件的路径(可以是apk,jar,zip格式)  
    38.         String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Dynamic.apk";  
    39.         //dex解压释放后的目录  
    40.         //String dexOutputDir = getApplicationInfo().dataDir;  
    41.         String dexOutputDirs = Environment.getExternalStorageDirectory().toString();  
    42.         //定义DexClassLoader  
    43.         //第一个参数:是dex压缩文件的路径  
    44.         //第二个参数:是dex解压缩后存放的目录  
    45.         //第三个参数:是C/C++依赖的本地库文件目录,可以为null  
    46.         //第四个参数:是上一级的类加载器  
    47.         DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());  
    48.           
    49.         /**使用PathClassLoader方法加载类*/  
    50.         //创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>    
    51.         Intent intent = new Intent("com.dynamic.impl", null);    
    52.         //获得包管理器    
    53.         PackageManager pm = getPackageManager();    
    54.         List<ResolveInfo> resolveinfoes =  pm.queryIntentActivities(intent, 0);    
    55.         //获得指定的activity的信息    
    56.         ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;    
    57.         //获得apk的目录或者jar的目录    
    58.         String apkPath = actInfo.applicationInfo.sourceDir;    
    59.         //native代码的目录    
    60.         String libPath = actInfo.applicationInfo.nativeLibraryDir;    
    61.         //创建类加载器,把dex加载到虚拟机中    
    62.         //第一个参数:是指定apk安装的路径,这个路径要注意只能是通过actInfo.applicationInfo.sourceDir来获取  
    63.         //第二个参数:是C/C++依赖的本地库文件目录,可以为null  
    64.         //第三个参数:是上一级的类加载器  
    65.         PathClassLoader pcl = new PathClassLoader(apkPath,libPath,this.getClassLoader());  
    66.         //加载类  
    67.         try {  
    68.             //com.dynamic.impl.Dynamic是动态类名  
    69.             //使用DexClassLoader加载类  
    70.             //Class libProviderClazz = cl.loadClass("com.dynamic.impl.Dynamic");  
    71.             //使用PathClassLoader加载类  
    72.             Class libProviderClazz = pcl.loadClass("com.dynamic.impl.Dynamic");  
    73.             lib = (IDynamic)libProviderClazz.newInstance();  
    74.             if(lib != null){  
    75.                 lib.init(AndroidDynamicLoadClassActivity.this);  
    76.             }  
    77.         } catch (Exception exception) {  
    78.             exception.printStackTrace();  
    79.         }  
    80.         /**下面分别调用动态类中的方法*/  
    81.         showBannerBtn.setOnClickListener(new View.OnClickListener() {  
    82.             public void onClick(View view) {  
    83.                if(lib != null){  
    84.                    lib.showBanner();  
    85.                }else{  
    86.                    Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();  
    87.                }  
    88.             }  
    89.         });  
    90.         showDialogBtn.setOnClickListener(new View.OnClickListener() {  
    91.             public void onClick(View view) {  
    92.                if(lib != null){  
    93.                    lib.showDialog();  
    94.                }else{  
    95.                    Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();  
    96.                }  
    97.             }  
    98.         });  
    99.         showFullScreenBtn.setOnClickListener(new View.OnClickListener() {  
    100.             public void onClick(View view) {  
    101.                if(lib != null){  
    102.                    lib.showFullScreen();  
    103.                }else{  
    104.                    Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();  
    105.                }  
    106.             }  
    107.         });  
    108.         showAppWallBtn.setOnClickListener(new View.OnClickListener() {  
    109.             public void onClick(View view) {  
    110.                if(lib != null){  
    111.                    lib.showAppWall();  
    112.                }else{  
    113.                    Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();  
    114.                }  
    115.             }  
    116.         });  
    117.     }  
    118. }  

    这里面定义了一个IDynamic接口变量,同时使用了DexClassLoader和PathClassLoader来加载类,这里面先来说一说DexClassLoader方式加载:

     
    1. //定义DexClassLoader  
    2. //第一个参数:是dex压缩文件的路径  
    3. //第二个参数:是dex解压缩后存放的目录  
    4. //第三个参数:是C/C++依赖的本地库文件目录,可以为null  
    5. //第四个参数:是上一级的类加载器  
    6. DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());  

    上面已经说了,DexClassLoader是继承ClassLoader类的,这里面的参数说明:

    第一个参数是:dex压缩文件的路径:这个就是我们将上面编译后的dynamic_temp.jar存放的目录,当然也可以是.zip和.apk格式的

    第二个参数是:dex解压后存放的目录:这个就是将.jar,.zip,.apk文件解压出的dex文件存放的目录,这个就和PathClassLoader方法有区别了,同时你也可以看到PathClassLoader方法中没有这个参数,这个也真是这两个类的区别:

    PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在手机的data/dalvik目录中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。

    然而我们可以通过DexClassLoader方法指定解压后的dex文件的存放目录,但是我们一般不这么做,因为这样做无疑的暴露了dex文件,所以我们一般不会将.jar/.zip/.apk压缩文件存放到用户可以察觉到的位置,同时解压dex的目录也是不能让用户看到的。

    第三个参数和第四个参数用到的不是很多,所以这里就不做太多的解释了。

    这里还要注意一点就是PathClassLoader方法的时候,第一个参数是dex存放的路径,这里传递的是:

     
    1. //获得apk的目录或者jar的目录    
    2. String apkPath = actInfo.applicationInfo.sourceDir;    

    指定的apk安装路径,这个值只能这样获取,不然会加载类失败的

    第三步:

    运行目标类:

    要做的工作是:

    如果用的是DexClassLoader方式加载类:这时候需要将.jar或者.zip或者.apk文件放到指定的目录中,我这里为了方便就放到sd卡的根目录中

    如果用的是PathClassLoader方法加载类:这时候需要先将Dynamic.apk安装到手机中,不然找不到这个activity,同时要注意的是:

     
    1. //创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>    
    2. Intent intent = new Intent("com.dynamic.impl", null);    

    这里的com.dynamic.impl是一个action需要在指定的apk中定义,这个名称是动态apk和目标apk之间约定好的

    运行结果

    点击showBanner显示一个Toast,成功的运行了动态类中的代码!

    其实更好的办法就是将动态的.jar.zip.apk文件从网络上获取,安全可靠,同时本地的目标项目不需要改动代码就可以执行不同的逻辑了

  • 相关阅读:
    Saltstack module acl 详解
    Saltstack python client
    Saltstack简单使用
    P5488 差分与前缀和 NTT Lucas定理 多项式
    CF613D Kingdom and its Cities 虚树 树形dp 贪心
    7.1 NOI模拟赛 凸包套凸包 floyd 计算几何
    luogu P5633 最小度限制生成树 wqs二分
    7.1 NOI模拟赛 dp floyd
    springboot和springcloud
    springboot集成mybatis
  • 原文地址:https://www.cnblogs.com/chenxibobo/p/6075011.html
Copyright © 2020-2023  润新知