公司要求把某自定義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;
}
</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互動了。