您的位置:首页 > 移动开发 > Android开发

Android动态加载包含so文件的jar的自定义view控件

2015-05-29 15:08 621 查看
公司要求把某自定义view控件打包成jar,提供给某项目(这里叫它APP吧)通过网络下载的方式实现动态加载控件,该APP通过反射来构造出该view,并且调用里面的方法。这样通过反射动态加载的方式(暂且叫他反射方式)和普通的把自定义view的jar导入工程预先加载的方式(暂且叫它预先加载)不同的是:



1.预先加载是先把jar复制到工程下的lib目录,然后 build path,使用该自定义的方法是通过 new 这个view的构造方法来产生,然后再通过该view的实例对象来操作里面的public方法。
2.反射动态加载的方式就比较酷炫了,可以通过网络下载jar和它的一些资源文件(比如so文件、drawable资源、assets资源等等),实现动态添加APP的功能模块,首先通过dexClassLoader来加载下载下来的jar(比如保存在SD卡某目录),然后通过反射调用,从而实例出该view使用动态加载自定义view的好处:

(1)当更新APP某功能模块时,不需要用户再次下载整个APP来重新安装,只需要下载新jar来重新
动态加载就OK了(要知道,让用户下完整个APP来更新的用户体检很差的,动不动就是下载安
装)。


(2)减小APP的体积,用户需要哪些功能模块,就让他去自由的选择下载(对应jar和所需的资源文
件),反正我是不想看到APP里我不需要的功能。




开始现实动态加载

1.编写完自定义view的代码时,如果有drawable 文件,不能用普通R.来获取,不然会报找不到资源id错误,应该这么获取drawable的id:

</pre><pre name="code" class="java">public class ResourceUtils {
public static int getIdByName(Context context, String className, String name) {
String packageName = context.getPackageName();
Class r = null;
int id = 0;
try {
r = Class.forName(packageName + ".R");

Class[] classes = r.getClasses();
Class desireClass = null;

for (int i = 0; i < classes.length; ++i) {
if (classes[i].getName().split("\\$")[1].equals(className)) {
desireClass = classes[i];
break;
}
}

if (desireClass != null)
id = desireClass.getField(name).getInt(desireClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}

return id;
}


path传入SD卡上的drawable资源(不能再把图片等资源放在drawable目录下了)

2.如果有用到JNI的,不能把so文件一起打包到jar里面,需要在程序加载view前拷贝到APP的data/data/包名下的某目录(可以自己新建),然后把所有System.loadLibrary()改成 System.load(),路径就是刚才存放so文件的路径。拷贝代码如下:
public static void copySoLib(Context context, String dexPath, String nativeLibDir) {
try {
ZipFile zip = new ZipFile(dexPath);
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry ze = (ZipEntry) entries.nextElement();
if (ze.isDirectory()) {
continue;
}
String zipEntryName = ze.getName();
if (zipEntryName.endsWith(".so")) {
String libName = zipEntryName.substring(zipEntryName.lastIndexOf("/") + 1);
InputStream ins = zip.getInputStream(ze);
FileOutputStream fos = new FileOutputStream(new File(nativeLibDir, libName));
byte[] buf = new byte[8192];
int len = -1;

while ((len = ins.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fos.close();
ins.close();
Log.d(TAG, "copy so lib success: " + zipEntryName);
}
}
zip.close();
} catch (IOException e) {
e.printStackTrace();
}
}
dexPath是so的压缩包在SD卡的路径。

3.在APP用dexClassLoader来加载jar,拿到class后再用它获取构造方法,再用该构造方法newInstance得到view的最终实例对象。代码如下:
ClassLoader dexLoader = new DexClassLoader(jarPath, dexPath, libPath, context.getClassLoader());
Class<?> mClass = dexLoader.loadClass("com.xxx.xxx.MyPluginView");
Object mViewObj = jarConstructor.newInstance(context);
mViewObj就是我们实例出来的view了,可以强制类型转换成view然后直接拿来add。

4.反射调用方法。
Method method = mClass.getMethod("方法名",int.class,........);
method.invoke(mViewObj,  new Object[]{width});


到这里就可以实现和jar里面的view交互了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息