最近在拜读博主姜维的文章,感觉获益匪浅。
原文在http://blog.csdn.net/jiangwei0910410003/article/details/17679823。里面详细讲解了android动态加载的原理及实例。
看了文章免不了要自己动手复现一遍,在复现的过程中遇到了一些问题。
我将自己复现成功的过程与大家分享,希望能够少走一些弯路吧。
文章中的测试工程有些绕,不太适合新手,这里我重新写了一遍,简化了与动态加载机制不太相关的东西,更聚焦问题所在吧。
第一步,建立一个android工程,并建立动态代码类。
为了简化,我只保留了4个方法。
package com.example.interfaces;
import android.app.Activity;
public interface IDynamic {
/**初始化方法*/
public void init(Activity activity);
/**自定义方法*/
public void showBanner();
public void showDialog();
/**销毁方法*/
public void destory();
}
Dynamic.java代码如下:
package com.example.impl;
import android.app.Activity;
import android.widget.Toast;
import com.example.interfaces.IDynamic;
public class Dynamic implements IDynamic {
public Activity mActivity;
@Override
public void init(Activity activity) {
mActivity = activity;
}
@Override
public void showBanner() {
Toast.makeText(mActivity, "我是ShowBannber方法", Toast.LENGTH_LONG).show();
}
@Override
public void showDialog() {
Toast.makeText(mActivity, "我是ShowDialog方法", Toast.LENGTH_LONG).show();
}
@Override
public void destory() {
}
}
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">第二步,打包并转换jar文件</span>
将上面开发好的动态类打包成.jar,这里要注意的是只打包实现类Dynamic.java,不打包接口类IDynamic.java,
然后将打包好的jar文件拷贝到android sdk的安装目录中的\sdk\build-tools\android-4.4W目录下。(android-4.4W更换成你的开发版本就好了,此处原文中有误)
使用dx命令转换jar文件:(我的jar文件是test.jar)
dx --dex --output=load.jar test.jar
将load.jar放入sdcard adb push load.jar /sdcard/
第三步,在android工程中删除com.dynamic.impl包及其中的java代码,保留com.dynamic.interfaces包及代码。
删除com.dynamic.impl包及其中的java代码,主要是为了更清楚地显示后面的代码是动态加载的,当然你不删除也可以。
保留com.dynamic.interfaces包及代码,主要是为了在动态加载的时候能够识别动态类,原文作者是通过导入jar包来实现的,这里我们直接写入代码是一样的效果。
第四步、编写app,动态加载类。
首先在AndroidManifest.xml中加入<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>,因为我们要把动态加载的jar包放在sdcard根目录。
下面是activity_main.xml,我加入了两个button
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.dynamic.demo.MainActivity" >
<Button
android:id="@+id/show_banner_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="showBanner" />
<Button
android:id="@+id/show_dialog_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="showDialog" />
</LinearLayout>
接下来是MainActivity.java的代码, 里面最大的改动就是将DexClassLoader的第二个参数,也就是要解压到的路径改为了程序本身目录下的cache目录。主要原因是,在android 4.1之后,不允许将dex文件解压到sccard目录下。这也是大部分人遇到错误的原因吧。
package com.example.testdynamic;
import java.io.File;
import com.example.interfaces.IDynamic;
import dalvik.system.DexClassLoader;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
IDynamic lib;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().toString()
+ File.separator + "load.jar");
String pathString = optimizedDexOutputPath.getAbsolutePath();
String outPath = getApplicationContext().getCacheDir().toString();
DexClassLoader classLoader = new DexClassLoader(pathString, outPath, null, getClassLoader());
Class libProviderClazz = null;
try {
libProviderClazz = classLoader.loadClass("com.example.impl.Dynamic");
lib = (IDynamic) libProviderClazz.newInstance();
if (lib != null) {
lib.init(MainActivity.this);
System.out.println("lib is not null");
}else {
System.out.println("null");
}
} catch (Exception e) {
// TODO: handle exception
}
Button showBannerBtn = (Button) findViewById(R.id.show_banner_btn);
Button showDialogBtn = (Button) findViewById(R.id.show_dialog_btn);
showBannerBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
if (lib != null) {
lib.showBanner();
} else {
Toast.makeText(MainActivity.this, "load error", Toast.LENGTH_LONG).show();
}
}
});
showDialogBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
if (lib != null) {
lib.showDialog();
} else {
Toast.makeText(MainActivity.this, "load error", Toast.LENGTH_LONG).show();
}
}
});
}
}
最后,运行成功加载。