Android动态加载jar/dex

来源:互联网 发布:有个网络歌手老头唱歌 编辑:程序博客网 时间:2024/05/17 20:22

前言

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

声明

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

    博客园:http://www.cnblogs.com

    农民伯伯: http://over140.cnblogs.com 

    Android中文翻译组:http://androidbox.sinaapp.com/

正文

  一、 基本概念和注意点

    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 DynamicTestimplements 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.getExternalStorageDirectory().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.getExternalStorageDirectory().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中的类

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

结束

  除了翻译组的工作和自己本职的工作以外,很难抽时间出来分享一些开发心得,但正所谓挤挤总是有的,欢迎交流!

农民伯伯
关注 - 3
粉丝 - 2717
评论:
#1楼2011-11-23 19:31 | 梦书
这个很赞
支持(0)反对(0)
http://pic.cnitblog.com/face/u9070.jpg 
#2楼2011-11-24 15:54 | liuzhaocn
这个非常赞,竟然是昨天的文章,今天正好查找相关资料。谢谢农民伯伯,呵呵

PS:我照着做了,但是有个问题。
如果我android 工程中有DynamicTest.java那么不把dynamic.dex拷到手机上,竟然不会有问题。
如果工程中删除了DynamicTest.java,然后把dynamic.dex文件拷到sd卡根目录,就会找不到类。

这两种情况下,接口IDynamic都是有的。

请问如何解决?

-----

问题解决了:
打包成jar,而不是dex文件,然后调用jar包,并且jar包中去掉接口。

新问题是:加载有些慢
支持(0)反对(0)
 
#3楼2011-11-24 16:04 | MudooT
木有明白哈 为何要讲dex文件改为jar?
还有我看到有些项目是直接可以加载别的apk的内容 这个怎么做额?
支持(0)反对(0)
http://pic.cnitblog.com/face/u147876.jpg 
#4楼2011-11-25 13:55 | liuzhaocn
我是二楼,我还有一个问题:

怎样打包和加载资源文件,比如我要现实一个activity,是不是只能打包成APK了。
支持(0)反对(0)
 
#5楼[楼主]2011-11-25 15:31 | 农民伯伯
@liuzhaocn
没有调用APK的经验,有时间可以研究一下,调用其他APK里的资源可能会有一个问题,资源ID与当前APK资源ID有可能相同。
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#6楼2011-11-25 15:46 | liuzhaocn
@农民伯伯
我做了一个小例子,一个android工程,我导出jar文件。而且用dx工具处理成带classes.dex文件和资源文件等的jar包。

用同样的方法加载activity类,却提示找不到类,而且jar包也没有解压。

如果单独处理一个java文件,则没有错误,jar包也会解压。

求教伯伯~
支持(0)反对(0)
 
#7楼[楼主]2011-11-25 16:02 | 农民伯伯
@liuzhaocn
 注意:在实践中发现,自己新建一个Java工程然后导出jar是无法使用的,这一点大家可以根据文章一来了解相关原因,也是本文的重点之一。这里打包导出为dynamic.jar

请仔细看文章,看看是不是这个原因。
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#8楼2011-11-30 16:58 | ppp250
@liuzhaocn
请问可以分享下只打成jar包,不打dex包的方式吗?
支持(0)反对(0)
 
#9楼2011-11-30 17:01 | ppp250
伯伯, 其实这种方式,加载的是主程序里的类,而不是dex里的类。
支持(0)反对(0)
 
#10楼[楼主]2011-11-30 17:27 | 农民伯伯
@ppp250
helloworld这个字符串主程序里面是没有的(打包完可直接把DynamicTest删除)。

有截图如何打包。
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#11楼2011-11-30 17:40 | ppp250
引用农民伯伯:
@ppp250
helloworld这个字符串主程序里面是没有的(打包完可直接把DynamicTest删除)。

有截图如何打包。


试过,如果把DynamicTest删除,则class not find
按照上述方法打的dex不行,用你提供下载的dex也一样。

ps:2楼的问题应该跟我是一样的。
支持(0)反对(0)
 
#12楼[楼主]2011-12-01 09:01 | 农民伯伯
@ppp250
感谢反馈,我这边再检查一下看看哪里出了问题,你看看这篇文章:
http://yunfeng.sinaapp.com/?p=87
本文基本上是参照这篇文章来做的。有结果了会更新文章,再次感谢!
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#13楼[楼主]2011-12-01 09:16 | 农民伯伯
@ppp250
问题确实存在,感谢反馈,已经在文章顶部醒目位置提醒后来阅读者,我在仔细研究一下,欢迎交流,再次感谢!
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#14楼[楼主]2011-12-01 10:02 | 农民伯伯
@ppp250
@liuzhaocn
感谢二位的反馈,文章已经做了修复,期待两位有更多发现并分享 :)
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#15楼2011-12-01 11:57 | ppp250
已OK。thx.
请问农伯有没做过动态加载.so,比如sdcard上的.so文件
支持(0)反对(0)
 
#16楼2011-12-01 12:44 | expipi[未注册用户]
忍不住强烈感谢一下!
 
#17楼[楼主]2011-12-02 09:43 | 农民伯伯
@ppp250
so本身使用方式就是动态加载,不过没做过在sdcard上的。
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#18楼2012-02-07 11:03 | Cmdmac
你好,我用命令行编译一个googlemap的工程可以,但运行时出现
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation错误,貌似加载类时出现了两个jar,请问知道这是什么原因吗,无比感谢!
支持(0)反对(0)
 
#19楼[楼主]2012-02-07 11:08 | 农民伯伯
@Cmdmac
IllegalAccessError 违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#20楼2012-03-20 17:57 | LPP
请问楼主,利用Eclipse打包的操作通过代码怎么实现
支持(0)反对(0)
 
#21楼[楼主]2012-03-21 09:38 | 农民伯伯
@奔跑在路上
你可以搜一下ant相关的资料。
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#22楼2012-03-21 09:42 | LPP
@农民伯伯
谢谢回复了,ant我知道,但是当我命令行去执行dx的命令时候老是出现不匹配异常,蛋疼
支持(0)反对(0)
 
#23楼[楼主]2012-03-21 09:46 | 农民伯伯
@奔跑在路上
正准备写一篇新的动态加载的文章,其实不用这么麻烦,直接新建一个android项目,打包成apk,直接把这里调用jar换成apk即可。
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#24楼2012-03-21 09:56 | LPP
@农民伯伯
既然能动态加载apk,那么能不能调用已安装程序的类和方法?
支持(0)反对(0)
 
#25楼[楼主]2012-03-21 14:01 | 农民伯伯
@奔跑在路上
更加可以,主要是ClassLoader,已安装的能获得Context,动态的无法获取这个。
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#26楼2012-03-21 14:48 | LPP
@农民伯伯
哦,谢谢了,但是我用接口接收的时候 java.lang.ClassCastException就出来了,具体用什么来接收呢?

是用context.getClassLoader()获取ClassLoader然后loader的吗?


哦,知道了,可以通过getMethod直接使用类里面的方法,呵呵,谢谢伯伯!!!
支持(0)反对(0)
 
#27楼2012-04-23 14:24 | cdiory
@农民伯伯
这篇文章写的很好, 有个问题也想请教一下, 这个dex里面可不可以再访问其他的JAR包啊,是否支持这种形式呢?
支持(0)反对(0)
 
#28楼2012-08-16 19:03 | myqhit
向楼主提问:
你的工程包中com.dynamic.DynamicTest 在主工程中删除试验下,并且这个类不可以写在主工程中,当删除后按照你目前提供的dex,还是会找不到DynamicTest类的,我做了apk的动态调用方式,接口在上层,实现在apk中,没有问题,你看下这个dex或者jar如何打包可以实现正常访问。
支持(0)反对(0)
 
#29楼[楼主]2012-08-17 00:01 | 农民伯伯
@myqhit
请参考本文 后期维护说明。
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#30楼2012-08-17 09:24 | myqhit
@农民伯伯
只是提醒后来者,参照范例的时候注意,范例不正确
如果不删除本例中DynamicTest 类,则通过反射得到的类并非从导出的jar包中加载的,而是从当前运行的程序中加载到的
楼主可以调试你的例子,DynamicTest 是从PathClassLoader中加载而非从DexClassLoader中加载,也就说类加载在父classloader中已经加载,跟你导出的dex文件没关系
支持(0)反对(0)
 
#31楼2013-03-12 18:44 | zwc2004
@农民伯伯
怎么动态加载另外一个android项目(已经是apk了)?
支持(0)反对(0)
 
#32楼2013-04-06 23:13 | jianglinjun
按文章中写的已经实现了,但是我更新了dynamic.jar后,再按mToastButton,应用就强退了,再启动后就可以打出新的内容.
有没有什么办法可以不强退?
支持(0)反对(0)
 
#33楼[楼主]2013-04-07 21:11 | 农民伯伯
@jianglinjun
强退,有logcat输出么?
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#34楼2013-04-08 11:49 | jianglinjun
@农民伯伯
有些tag是debug的信息,都是一些汇编方面的信息,但是信息太多了就不再这里贴出了.

你有邮箱吗?

我发你邮箱里,你帮我看下是什么问题,谢谢了!!!!

另外:你的例子真的有点问题,就是30楼说的问题.
支持(0)反对(0)
 
#35楼[楼主]2013-04-08 15:45 | 农民伯伯
@jianglinjun
注意看 文章末尾的后期维护部分。
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#36楼2013-04-18 20:19 | bcf
你好,你的这个例子我在4.0.3的系统上运行没有问题,现在最新的android系统已经更新到4.2.2了,好像从4.1开始就不支持这种从SD卡上加载jar/dex/apk的方式了,会报错,不知道你有没有试过?如果不能加载还有什么其它的替代手段吗?谢谢回答
支持(0)反对(0)
 
#37楼2013-05-07 09:01 | tornadoclh
@农民伯伯
你好!感谢你的文章,对我的帮助很大。初学另请教个问题:请问如果想从本工程跳转到jar包中的activity运行要如何操作?
支持(0)反对(0)
 
#38楼2013-12-31 16:53 | zhanchi
D:\adt-bundle-windows-x86-20131030\sdk\platform-tools> dx --dex --output=test.ja
r dynamic.jar
'dx' 不是内部或外部命令,也不是可运行的程序
或批处理文件。

这个是为什么呢?
支持(0)反对(0)
 
#39楼[楼主]2014-01-01 11:46 | 农民伯伯
@zhanchi
你看一下platform-tools是不是有dx,是不是后续版本移到其他文件夹去了
支持(0)反对(0)
http://pic.cnitblog.com/face/u25060.png?id=26205926 
#40楼2014-03-14 11:02 | 赏心悦事
赞一个!真心写的不错。
支持(0)反对(0)
 
#41楼2014-03-27 13:09 | 为梦飞翔
@Cmdmac
用dex生成classes.dex文件时,把libs下的jar包路径注掉
<dex
                dexedlibs="out.dexed.absolute.dir"disableDexMerger="{dex.disable.merger}"
                executable="dx"forceJumbo="{dex.force.jumbo}"
                nolocals="@{nolocals}"
                output="intermediate.dex.file"verbose="{verbose}" >
                <path path="${out.dex.input.absolute.dir}" />
<!--<path refid="out.dex.jar.input.ref" />-->
                <external-libs />
</dex>
支持(0)反对(0)
 
#42楼2014-04-13 20:22 | thecr0w
?java.lang.IllegalArgumentException: Optimized data directory /mnt/sdcard is not owned by the current user. Shared storage cannot protect your application from code injection attacks.
支持(0)反对(0)
 
#43楼2014-07-03 16:54 | nizi_go
你好,我使用 cl.loadClass(Classpath); 这个方法在Android2.x手机上失败找不到,楼主有解决办法么?
支持(0)反对(0)
 
#44楼2014-09-19 16:01 | LanPluto
@农民伯伯
我按照这个例子试了一下,当我把DynamicTest.java删除后,会提示DynamicTest类找不到。我打包时没有将IDynamic.java打进来,而且也用dx工具处理过了。我测试用的手机系统是Android4.3,请问这个和系统有关吗?
支持(0)反对(0)
 
#45楼30310932014/9/19 16:02:232014-09-19 16:02 | LanPluto
@农民伯伯
我按照这个例子试了一下,当我把DynamicTest.java删除后,会提示找不到DynamicTest类。我打包时没有将IDynamic.java打进来,而且也用dx工具处理过了。我测试用的手机系统是Android4.3,请问这个和系统有关吗?
0 0
原创粉丝点击