前言


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



声明


 欢迎转载,但请保留文章原始出处:)


正文


 一、 基本概念和注意点


   1.1  首先需要了解一点:在Android中可以动态加载,但无法像Java中那样方便动态加载jar


     原因:Android的虚拟机(Dalvik VM)是不认识Java打出jar的byte code,需要通过dx工具来优化转换成Dalvik byte code才行。这一点在咱们Android项目打包的apk中可以看出:引入其他Jar的内容都被打包进了classes.dex。

     所以这条路不通,请大家注意。



   1.2  当前哪些API可用于动态加载


     1.2.1  DexClassLoader


       这个可以加载jar/apk/dex,也可以从SD卡中加载,也是本文的重点。


     1.2.3  PathClassLoader  


       只能加载已经安装到Android系统中的apk文件。



 二、 准备


   本文主要参考"四、参考文章"中第一篇文章,补充细节和实践过程。


   2.1  下载开源项目


     http://code.google.com/p/goodev-demo


     将项目导入工程,工程报错的话应该是少了gen文件夹,手动添加即可。注意这个例子是从网上下载优化好的jar(已经优化成dex然后再打包成的jar)到本地文件系统,然后再从本地文件系统加载并调用的。本文则直接改成从SD卡加载。



 三、实践


   3.1  编写接口和实现


     3.1.1  接口IDynamic


package  com.dynamic;

public  interface  IDynamic  {
        public  String  helloWorld();
}
       3.1.2  实现类DynamicTest


package  com.dynamic;

public  class  DynamicTest  implements  IDynamic  {

        @Override
        public  String  helloWorld()  {
                return  "Hello  World!";
        }
}


   3.2  打包并转成dex


     3.2.1  选中工程,常规流程导出即可,如图:




     注意:在实践中发现,自己新建一个Java工程然后导出jar是无法使用的,这一点大家可以根据文章一来了解相关原因,也是本文的重点之一。这里打包导出为dynamic.jar


     (后期修复:打包请不要把接口文件打进来,参见文章末尾后续维护!)


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


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


   3.3  修改调用例子


     修改MainActivity,如下:



     

@Override
        public  void  onCreate(Bundle  savedInstanceState)  {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);
                mToastButton  =  (Button)  findViewById(R.id.toast_button);
               
                //  Before the secondary dex file can be processed by the DexClassLoader,
        //  it has to be first copied from asset resource to a storage location.
//         final File dexInternalStoragePath = new File(getDir("dex", Context.MODE_PRIVATE),SECONDARY_DEX_NAME);
//         if (!dexInternalStoragePath.exists()) {
//             mProgressDialog = ProgressDialog.show(this,
//                     getResources().getString(R.string.diag_title), 
//                     getResources().getString(R.string.diag_message), true, false);
//             //  Perform the file copying in an AsyncTask.
//             //  从网络下载需要的dex文件
//             (new PrepareDexTask()).execute(dexInternalStoragePath);
//         } else {
//             mToastButton.setEnabled(true);
//         }
               
                mToastButton.setOnClickListener( new  View.OnClickListener()  {
                        public  void  onClick(View  view)  {
                                //  Internal storage where the DexClassLoader writes the optimized dex file to.
                // final File optimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);
                                final  File  optimizedDexOutputPath  =  new  File(Environment.getExternalStorageDirect ory().toString()
                                        +  File.separator  +  "test.jar");
                                //  Initialize the class loader with the secondary dex file.
//                 DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
//                         optimizedDexOutputPath.getAbsolutePath(),
//                         null,
//                         getClassLoader());
                                DexClassLoader  cl  =  new  DexClassLoader(optimizedDexOutputPath.getAbsolutePath(),
                                        Environment.getExternalStorageDirect ory().toString(),  null,  getClassLoader());
                                Class  libProviderClazz  =  null;
                               
                                try  {
                                        //  Load the library class from the class loader.
                    //  载入从网络上下载的类
//                     libProviderClazz = cl.loadClass("com.example.dex.lib.LibraryProvider");
                                        libProviderClazz  =  cl.loadClass("com.dynamic.DynamicTest");
                                       
                                        //  Cast the return object to the library interface so that the
                    //  caller can directly invoke methods in the interface.
                    //  Alternatively, the caller can invoke methods through reflection,
                    //  which is more verbose and slow.
                    // LibraryInterface lib = (LibraryInterface) libProviderClazz.newInstance();
                                        IDynamic  lib  =  (IDynamic)libProviderClazz.newInstance();
                                       
                                        //  Display the toast!
                    // lib.showAwesomeToast(view.getContext(), "hello 世界!");
                                        Toast.makeText(MainActivity. this,  lib.helloWorld(),  Toast.LENGTH_SHORT).show();
                                }  catch  (Exception  exception)  {
                                        //  Handle exception gracefully here.
                                        exception.printStackTrace();
                                }
                        }
                });
        }


   3.4  执行结果


   



 四、参考文章


   [推荐]在Android中动态载入自定义类


   Android app中加载jar插件


   关于Android的ClassLoader探索


   Android App 如何动态加载类



 五、补充


   大家可以看看DexClassLoader的API文档,里面不提倡从SD卡加载,不安全。此外,我也正在组织翻译组尽快把这个命名空间下的几个类都翻译出来,以供大家参考。


   工程下载:这里,Dex文件下载:这里。大家可以直接把Dex文件拷贝到SD卡,然后运行例子。


 六、后期维护


   6.1  2011-12-1  修复本文错误


     感谢网友ppp250和liuzhaocn的反馈,基本按照评论2来修改:


     6.1.1  不需要在本工程里面导出jar,自己新建一个Java工程然后导出来也行。


     6.1.2  导出jar时不能带接口文件,否则会报以下错:


        java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation


     6.1.3  将jar优化时应该重新成jar(jar->dex->jar),如果如下命令:


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


   6.2  2012-3-29  本文升级版:


     Android应用开发提高系列(4)——Android动态加载(上)——加载未安装APK中的类


     请大家参照最新的文章来做动态加载!