Android动态加载jar、apk的实现

前段时间到阿里巴巴参加支付宝技术分享沙龙,看到支付宝在Android使用插件化的技术,挺好奇的。正好这几天看到了农民伯伯的相关文章,因此简单整理了下,有什么错误希望大神指正。

        

       核心类

      1.1      DexClassLoader类
   可以加载jar/apk/dex,可以从SD卡中加载为安装的apk。
   1.2      PathClassLoader类  
   只能加载已经安装到Android系统中的apk文件。


    一、正文

       1.1 动态加载jar

    类似于eclipse的插件化实现, 首先定义好接口, 用户实现接口功能后即可通过动态加载的方式载入jar文件, 以实现具体功能。注意, 这里的jar包需要经过android dx工具的处理 , 否则不能使用。

首先我们定义如下接口 :

[java]  view plain copy
  1. package com.example.interf;    
  2. /**  
  3.  * @Title: ILoader.java  
  4.  * @Package com.example.loadjardemo  
  5.  * @Description:  通用接口, 需要用户实现 
  6.  * @version V1.0  
  7.  */  
  8. public interface ILoader {  
  9.      public String sayHi();  
  10. }  
  11.     
用户需实现,该接口, 并且将工程导出为jar包的形式。

示例如下 :

[java]  view plain copy
  1. public class JarLoader implements ILoader {  
  2.   
  3.     public JarLoader() {  
  4.     }  
  5.   
  6.     @Override  
  7.     public String sayHi() {  
  8.         return "I am jar loader.";  
  9.     }  
  10.   
  11. }  

最后, 实现功能的代码打包成jar包 :

首先选中工程, 右键后选择“导出”, 然后选择“java”-----“jar文件”, 然后将你的具体功能实现类导出为jar,文件名为loader.jar,如下图所示 : 

Android动态加载jar、apk的实现_第1张图片

将打包好的jar拷贝到SDK安装目录android-sdk-windows\platform-tools下,DOS进入这个目录,执行如下命令:

[java]  view plain copy
  1. dx --dex --output=loader_dex.jar loader.jar  

然后将loader_dex.jar放到android手机中, 这里我们放到SD卡根目录下。


动态加载代码 : 

[java]  view plain copy
  1. /** 
  2.  *  
  3.  * @Title: loadJar  
  4.  * @Description: 项目工程中必须定义接口, 而被引入的第三方jar包实现这些接口,然后进行动态加载 。 
  5.  *          相当于第三方按照接口协议来开发, 使得第三方应用可以以插件的形式动态加载到应用平台中。 
  6.  * @return void     
  7.  * @throws 
  8.  */  
  9. private void loadJar(){  
  10.        final File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().toString()  
  11.                + File.separator + "loader_dex.jar");  
  12.              
  13.            BaseDexClassLoader cl = new BaseDexClassLoader(Environment.getExternalStorageDirectory().toString(),  
  14.                 optimizedDexOutputPath, optimizedDexOutputPath.getAbsolutePath(), getClassLoader());  
  15.            Class libProviderClazz = null;  
  16.              
  17.            try {  
  18.                // 载入JarLoader类, 并且通过反射构建JarLoader对象, 然后调用sayHi方法  
  19.                libProviderClazz = cl.loadClass("com.example.interf.JarLoader");  
  20.                ILoader loader = (ILoader)libProviderClazz.newInstance();  
  21.                Toast.makeText(MainActivity.this, loader.sayHi() , Toast.LENGTH_SHORT).show();  
  22.            } catch (Exception exception) {  
  23.                // Handle exception gracefully here.  
  24.                exception.printStackTrace();  
  25.            }  
  26. }  

效果如下图所示 : 
Android动态加载jar、apk的实现_第2张图片



1.2 加载未安装的apk

     首先新建一个Android项目, 定义如下接口 : 

    

[java]  view plain copy
  1. public interface ISayHello {  
  2.     public String sayHello()  ;  
  3. }  
    

定义一个Activity实现该接口, 如下:

[java]  view plain copy
  1. package com.example.loaduninstallapkdemo;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.Menu;  
  6. import android.view.View;  
  7. import android.view.View.OnClickListener;  
  8. import android.widget.Toast;  
  9.   
  10. /** 
  11.  *  
  12.  * @ClassName: UninstallApkActivity  
  13.  * @Description: 这是被动态加载的Activity类 
  14.  * 
  15.  */  
  16. public class UninstallApkActivity extends Activity implements ISayHello{  
  17.   
  18.     private View mShowView = null ;  
  19.       
  20.     @Override  
  21.     protected void onCreate(Bundle savedInstanceState) {  
  22.         super.onCreate(savedInstanceState);  
  23.         setContentView(R.layout.activity_main);  
  24.           
  25.         mShowView = findViewById(R.id.show) ;  
  26.         mShowView.setOnClickListener(new OnClickListener() {  
  27.               
  28.             @Override  
  29.             public void onClick(View v) {  
  30.                 Toast.makeText(UninstallApkActivity.this"这是已安装的apk被动态加载了", Toast.LENGTH_SHORT).show();  
  31.             }  
  32.         }) ;  
  33.     }  
  34.   
  35.     @Override  
  36.     public boolean onCreateOptionsMenu(Menu menu) {  
  37.         // Inflate the menu; this adds items to the action bar if it is present.  
  38.         getMenuInflater().inflate(R.menu.activity_main, menu);  
  39.         return true;  
  40.     }  
  41.   
  42.     @Override  
  43.     public String sayHello(){  
  44.         return "Hello, this apk is not installed";  
  45.     }  
  46. }  
然后将该编译生apk, 并且将该apk拷贝到SD卡根目录下。

动态加载未安装的apk

[java]  view plain copy
  1. /** 
  2.  *  
  3.  * @Title: loadUninstallApk  
  4.  * @Description: 动态加载未安装的apk 
  5.  * @return void     
  6.  * @throws 
  7.  */  
  8. private void loadUninstallApk(){  
  9.        String path = Environment.getExternalStorageDirectory() + File.separator;  
  10.        String filename = "LoadUninstallApkDemo.apk";  
  11.   
  12.        // 4.1以后不能够将optimizedDirectory设置到sd卡目录, 否则抛出异常.  
  13.        File optimizedDirectoryFile = getDir("dex"0) ;  
  14.        DexClassLoader classLoader = new DexClassLoader(path + filename, optimizedDirectoryFile.getAbsolutePath(),  
  15.                                                         null, getClassLoader());  
  16.   
  17.        try {  
  18.         // 通过反射机制调用, 包名为com.example.loaduninstallapkdemo, 类名为UninstallApkActivity  
  19.            Class mLoadClass = classLoader.loadClass("com.example.loadunstallapkdemo.UninstallApkActivity");  
  20.            Constructor constructor = mLoadClass.getConstructor(new Class[] {});  
  21.            Object testActivity = constructor.newInstance(new Object[] {});  
  22.              
  23.            // 获取sayHello方法  
  24.            Method helloMethod = mLoadClass.getMethod("sayHello"null);  
  25.            helloMethod.setAccessible(true);  
  26.            Object content = helloMethod.invoke(testActivity, null);  
  27.            Toast.makeText(MainActivity.this, content.toString(), Toast.LENGTH_LONG).show();  
  28.              
  29.        } catch (Exception e) {  
  30.            e.printStackTrace();  
  31.        }  
  32. }  


DexClassLoader 注意点 :

A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.

This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) to create such a directory:

<span class="pln" style="color: rgb(0, 0, 0); ">   </span><code style="line-height: 14px; "><span class="typ" style="color: rgb(102, 0, 102); ">File</span><span class="pln" style="color: rgb(0, 0, 0); "> dexOutputDir </span><span class="pun" style="color: rgb(102, 102, 0); ">=</span><span class="pln" style="color: rgb(0, 0, 0); "> context</span><span class="pun" style="color: rgb(102, 102, 0); ">.</span><span class="pln" style="color: rgb(0, 0, 0); ">getDir</span><span class="pun" style="color: rgb(102, 102, 0); ">(</span><span class="str" style="color: rgb(0, 136, 0); ">"dex"</span><span class="pun" style="color: rgb(102, 102, 0); ">,</span><span class="pln" style="color: rgb(0, 0, 0); "> </span><span class="lit" style="color: rgb(0, 102, 102); ">0</span><span class="pun" style="color: rgb(102, 102, 0); ">);</span><span class="pln" style="color: rgb(0, 0, 0); ">
 </span></code>

Do not cache optimized classes on external storage. External storage does not provide access controls necessary to protect your application from code injection attacks.


效果如图 : 

Android动态加载jar、apk的实现_第3张图片


1.3 加载已安装的apk

将1.2中的apk安装到手机中,我的例子中,该apk的包名为“com.example.loaduninstallapkdemo”,Activity名为"UninstallApkActivity". 加载代码如下 :

[java]  view plain copy
  1.       /** 
  2.  
  3. * @Title: loadInstalledApk  
  4. * @Description: 动态加载已安装的apk     
  5. * @return void     
  6. * @throws 
  7. */  
  8. rivate void loadInstalledApk() {  
  9. try {  
  10.     String pkgName = "com.example.loaduninstallapkdemo";  
  11.     Context context = createPackageContext(pkgName,  
  12.             Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE) ;  
  13.       
  14.     // 获取动态加载得到的资源  
  15.     Resources resources = context.getResources() ;  
  16.     // 过去该apk中的字符串资源"tips", 并且toast出来,apk换肤的实现就是这种原理  
  17.     String toast = resources.getString(resources.getIdentifier("tips""string", pkgName) ) ;  
  18.     Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show() ;  
  19.       
  20.     Class cls = context.getClassLoader().loadClass(pkgName + ".UninstallApkActivity") ;  
  21.     // 跳转到该Activity  
  22.     startActivity(new Intent(context, cls)) ;  
  23. catch (NameNotFoundException e) {  
  24.     e.printStackTrace();  
  25. }catch (ClassNotFoundException e) {  
  26.     Log.d("", e.toString()) ;  
  27. }  
  28. </span>  

你可能感兴趣的:(Android动态加载jar、apk的实现)