前段時間到阿里巴巴參加支付寶技術分享沙龍,看到支付寶在Android使用插件化的技術,挺好奇的。正好這幾天看到了農民伯伯的相關文章,所以簡單整理了下,有什麼錯誤但願大神指正。java
核心類android
1.1 DexClassLoader類
能夠加載jar/apk/dex,能夠從SD卡中加載爲安裝的apk。
1.2 PathClassLoader類
只能加載已經安裝到Android系統中的apk文件。sql
1、正文windows
1.1 app
相似於eclipse的插件化實現, 首先定義好接口, 用戶實現接口功能後便可經過動態加載的方式載入jar文件, 以實現具體功能。 注意 , 這裏的jar包須要通過android dx工具的處理 , 不然不能使用。eclipse
首先咱們定義以下接口 :ide
package com.example.interf; /** * @Title: ILoader.java * @Package com.example.loadjardemo * @Description: 通用接口, 須要用戶實現 * @version V1.0 */public interface ILoader { public String sayHi(); }
用戶需實現,該接口, 而且將工程導出爲jar包的形式。工具
示例以下 :ui
public class JarLoader implements ILoader { public JarLoader() { } @Override public String sayHi() { return "I am jar loader."; } }
最後, 實現功能的代碼打包成jar包 :this
首先選中工程, 右鍵後選擇「導出」, 而後選擇「java」-----「jar文件」, 而後將你的具體功能實現類導出爲jar,文件名爲loader.jar,以下圖所示 :
將打包好的jar拷貝到SDK安裝目錄android-sdk-windows\platform-tools下,DOS進入這個目錄,執行以下命令:
dx --dex --output=loader_dex.jar loader.jar
而後將loader_dex.jar放到android手機中, 這裏咱們放到SD卡根目錄下。
動態加載代碼 :
/** * * @Title: loadJar * @Description: 項目工程中必須定義接口, 而被引入的第三方jar包實現這些接口,而後進行動態加載 。 * 至關於第三方按照接口協議來開發, 使得第三方應用能夠以插件的形式動態加載到應用平臺中。 * @return void * @throws */ private void loadJar(){ final File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().toString() + File.separator + "loader_dex.jar"); BaseDexClassLoader cl = new BaseDexClassLoader(Environment.getExternalStorageDirectory().toString(), optimizedDexOutputPath, optimizedDexOutputPath.getAbsolutePath(), getClassLoader()); Class libProviderClazz = null; try { // 載入JarLoader類, 而且經過反射構建JarLoader對象, 而後調用sayHi方法 libProviderClazz = cl.loadClass("com.example.interf.JarLoader"); ILoader loader = (ILoader)libProviderClazz.newInstance(); Toast.makeText(MainActivity.this, loader.sayHi() , Toast.LENGTH_SHORT).show(); } catch (Exception exception) { // Handle exception gracefully here. exception.printStackTrace(); } }
效果以下圖所示 :
1.2 加載未安裝的apk
首先新建一個Android項目, 定義以下接口 :
public interface ISayHello { public String sayHello() ; }
定義一個Activity實現該接口, 以下:
package com.example.loaduninstallapkdemo;import android.app.Activity;import android.os.Bundle;import android.view.Menu;import android.view.View;import android.view.View.OnClickListener;import android.widget.Toast;/** * * @ClassName: UninstallApkActivity * @Description: 這是被動態加載的Activity類 * */public class UninstallApkActivity extends Activity implements ISayHello{ private View mShowView = null ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mShowView = findViewById(R.id.show) ; mShowView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(UninstallApkActivity.this, "這是已安裝的apk被動態加載了", Toast.LENGTH_SHORT).show(); } }) ; } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true; } @Override public String sayHello(){ return "Hello, this apk is not installed"; } }
而後將該編譯生apk, 而且將該apk拷貝到SD卡根目錄下。
動態加載未安裝的apk
/** * * @Title: loadUninstallApk * @Description: 動態加載未安裝的apk * @return void * @throws */ private void loadUninstallApk(){ String path = Environment.getExternalStorageDirectory() + File.separator; String filename = "LoadUninstallApkDemo.apk"; // 4.1之後不可以將optimizedDirectory設置到sd卡目錄, 不然拋出異常. File optimizedDirectoryFile = getDir("dex", 0) ; DexClassLoader classLoader = new DexClassLoader(path + filename, optimizedDirectoryFile.getAbsolutePath(), null, getClassLoader()); try { // 經過反射機制調用, 包名爲com.example.loaduninstallapkdemo, 類名爲UninstallApkActivity Class mLoadClass = classLoader.loadClass("com.example.loadunstallapkdemo.UninstallApkActivity"); Constructor constructor = mLoadClass.getConstructor(new Class[] {}); Object testActivity = constructor.newInstance(new Object[] {}); // 獲取sayHello方法 Method helloMethod = mLoadClass.getMethod("sayHello", null); helloMethod.setAccessible(true); Object content = helloMethod.invoke(testActivity, null); Toast.makeText(MainActivity.this, content.toString(), Toast.LENGTH_LONG).show(); } catch (Exception e) { e.printStackTrace(); } }
DexClassLoader 注意點 :
A class loader that loads classes from .jar
and .apk
files containing a classes.dex
entry. This can be used to execute code not installed as part of an application.
This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int)
to create such a directory:
File dexOutputDir = context.getDir("dex", 0);
Do not cache optimized classes on external storage. External storage does not provide access controls necessary to protect your application from code injection attacks.
效果如圖 :
1.3 加載已安裝的apk
將1.2中的apk安裝到手機中,個人例子中,該apk的包名爲「com.example.loaduninstallapkdemo」,Activity名爲"UninstallApkActivity". 加載代碼以下 :
/** * * @Title: loadInstalledApk * @Description: 動態加載已安裝的apk * @return void * @throws */ private void loadInstalledApk() { try { String pkgName = "com.example.loaduninstallapkdemo"; Context context = createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE) ; // 獲取動態加載獲得的資源 Resources resources = context.getResources() ; // 過去該apk中的字符串資源"tips", 而且toast出來,apk換膚的實現就是這種原理 String toast = resources.getString(resources.getIdentifier("tips", "string", pkgName) ) ; Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show() ; Class cls = context.getClassLoader().loadClass(pkgName + ".UninstallApkActivity") ; // 跳轉到該Activity startActivity(new Intent(context, cls)) ; } catch (NameNotFoundException e) { e.printStackTrace(); }catch (ClassNotFoundException e) { Log.d("", e.toString()) ; } }
效果如圖:
消息被Toast出來, 而且跳轉到了目標Activity.