天天看點

Android動态加載包含so檔案的jar的自定義view控件

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