前言
在目前的软硬件环境下,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=dynamic.dex 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 + "dynamic.dex");
- // 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 执行结果