Android中的動態載入(簡單實現)
阿新 • • 發佈:2019-02-20
先說明一點,這篇文章說的動態載入,只能載入dex檔案中的功能,涉及到資源的就不可以了。
動態載入步驟
1、在宿主程式中寫外掛介面
2、在外掛中實現宿主程式的介面
這裡要注意,外掛介面的包名要和宿主程式中的一樣。
3、將外掛打包成dex檔案,注意不能講宿主介面打包,否則在呼叫時會出錯。
打包這裡先將實現類打成jar包,不會在Android studio上打jar包的看這篇文章:http://blog.csdn.net/u013174702/article/details/52251540
我在gradle中是這麼寫的
//打包任務 task makeJar(type:org.gradle.api.tasks.bundling.Jar) { //指定生成的jar名 baseName 'dynamic' //從哪裡打包class檔案 from('build/intermediates/classes/debug/test/halewang/com/dynamicloadtest') //打包到jar後的目錄結構 into('test/halewang/com/dynamicloadtest/') //去掉不需要打包的目錄和檔案 exclude('dynamic/', 'BuildConfig.class', 'R.class',"MainActivity.class") //去掉R$開頭的檔案 exclude{ it.name.startsWith('R$');} }
然後需要把生成的jar包轉化成含dex檔案的jar包
最後生成的test.jar就在F:\SDK\build-tools\23.0.2的目錄下
4、寫配置檔案xxx.prop. 然後將配置檔案放在含dex檔案的jar包內,與dex檔案在同一目錄下
這是配置檔案
然後將test.jar改成test.zip,然後直接將寫好的res.prop檔案拖進去,再把test.zip改成test.jar
然後將test.jar推送到目標機的sdcard/dex/目錄下, 寫配置檔案的目的是獲取入口類的全名稱和一些外掛的設定。
5、將打包好的jar包推送到目標機上的指定路徑,然後在宿主程式中載入。
在宿主程式的activity中
/** *列出外掛列表 */ private void listMyPlugin(){ ViewGroup root = mList; root.removeAllViews(); File dexFiles = new File(Environment.getExternalStorageDirectory().toString() + File.separator + "dex"); File[] dexes = dexFiles.listFiles(); if(dexes != null) { for (File file : dexes) { String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "dex" + File.separator + file.getName(); PropBean propBean = null; if(pluginManager != null){ propBean = pluginManager.getProp(getApplicationContext(),dexPath); } if(propBean != null){ switch (propBean.getPluginType()){ case 0: //代表外掛型別為一個按鈕 createButton(root, propBean.getDesc(),dexPath); break; case 1: //代表外掛型別為滑動按鈕 createLayout(root,dexPath,propBean); break; default: break; } } } }else{ Toast.makeText(PluginLoadActivity.this,"沒有任何外掛",Toast.LENGTH_SHORT).show(); } } //建立一個按鈕 private void createButton(ViewGroup root, String desc, final String dexPath){ Button button = new Button(this); button.setPadding(10, 25, 10, 25); LinearLayout.LayoutParams layoutParam = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); layoutParam.topMargin = 25; layoutParam.bottomMargin = 25; layoutParam.gravity = Gravity.CENTER; root.addView(button, layoutParam); button.setText(desc); button.setBackgroundResource(R.drawable.btn_send_selector); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { pluginManager.loadDex(PluginLoadActivity.this, dexPath); } }); }
Pluginmanager.java
package com.tencent.wecarbugreport.plugin; import android.content.Context; import android.util.Log; import com.tencent.wecarbugreport.myinterface.IPlugin; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import dalvik.system.DexClassLoader; /** * Created by halewang on 2016/8/22. */ public class PluginManager { /** * 配置檔名稱 */ private static final String RES_NAME = "res.prop"; /** * 入口類全名稱 */ private static final String ENTER_CLASS = "enterClass"; /** * 外掛型別 * 0代表普通按鈕 * 1代表滑動按鈕 */ private static final String PLUGIN_TYPE = "pluginType"; /** * 外掛描述 */ private static final String DESC = "desc"; /** * 滑動開關狀態 */ private static final String SLIDE_STATUS = "slideStatus"; private static final PluginManager mPluginManager = new PluginManager(); /** * 儲存外掛,在以後呼叫時,如果存在直接接呼叫方法而不重新建立 */ private static Map<String,IPlugin> mPlugins = new HashMap<String,IPlugin>(); /** * 儲存外掛配置 */ private static Map<String,PropBean> mProps = new HashMap<String,PropBean>(); /** * 外掛檔案全路徑名 */ private static List<String> mPaths = new ArrayList<String>(); private PluginManager(){ } public static PluginManager getInstance(){ return mPluginManager; } public void addPlugin(String pluginName, IPlugin mPlugin){ mPlugins.put(pluginName, mPlugin); } public void removePlugin(String pluginName){ mPlugins.get(pluginName).onPluginDestroy(); mPlugins.remove(pluginName); } public IPlugin getPlugin(String pluginName){ IPlugin plugin; plugin = mPlugins.get(pluginName); return plugin; } //載入外掛功能 public void loadDex(Context context, String dexPath){ if(getPlugin(dexPath) != null){ PropBean propBean = mProps.get(dexPath); switch (propBean.getPluginType()) { case 0: getPlugin(dexPath).callMethod(context); break; case 1: if(propBean.getSlideStatus().equals("0")){ getPlugin(dexPath).callMethod(context,true); propBean.setSlideStatus("1"); }else{ getPlugin(dexPath).callMethod(context,false); propBean.setSlideStatus("0"); } } }else { File dexOutputDir = context.getDir("dex1", 0); DexClassLoader loader = new DexClassLoader(dexPath, dexOutputDir.getAbsolutePath(), null, context.getClassLoader()); try { Class clz = loader.loadClass(getClassName(loader)); IPlugin impl = (IPlugin) clz.newInstance(); PropBean propBean = mProps.get(dexPath); switch (propBean.getPluginType()) { case 0: impl.callMethod(context); break; case 1: if(propBean.getSlideStatus().equals("0")){ impl.callMethod(context,true); propBean.setSlideStatus("1"); }else{ impl.callMethod(context,false); propBean.setSlideStatus("0"); } } addPlugin(dexPath, impl); } catch (Exception e) { Log.e("PluginManager", "error happened", e); } } } /** * 獲取類全名 * @param loader * @return */ public String getClassName(DexClassLoader loader){ String className = null; InputStream is = loader.getResourceAsStream(RES_NAME); Properties pp = new Properties(); try { pp.load(is); if(!pp.isEmpty()) { className = pp.getProperty(ENTER_CLASS); } } catch (IOException e) { e.printStackTrace(); }finally { try { is.close(); pp.clear(); } catch (IOException e) { e.printStackTrace(); } } return className; } /** * 獲取配置實體 * @param dexPath * @return */ public PropBean getProp(Context context, String dexPath){ if(mProps.get(dexPath) != null) { return mProps.get(dexPath); }else { //dex檔案解壓目錄 File dexOutputDir = context.getDir("dex1", 0); //得到類載入器 DexClassLoader loader = new DexClassLoader(dexPath, dexOutputDir.getAbsolutePath(), null, context.getClassLoader()); PropBean propBean = new PropBean(); InputStream is = loader.getResourceAsStream(RES_NAME); Properties pp = new Properties(); try { pp.load(is); if (!pp.isEmpty()) { propBean.setEnterClass(pp.getProperty(ENTER_CLASS)); propBean.setPluginType(Integer.parseInt(pp.getProperty(PLUGIN_TYPE))); propBean.setDesc(pp.getProperty(DESC)); propBean.setSlideStatus(pp.getProperty(SLIDE_STATUS)); Log.d("propStatus","狀態是--------------"+propBean.getSlideStatus()); Log.d("propStatus","描述是--------------"+propBean.getDesc()); mProps.put(dexPath, propBean); mPaths.add(dexPath); } } catch (IOException e) { e.printStackTrace(); } finally { try { is.close(); pp.clear(); } catch (IOException e) { e.printStackTrace(); } } return propBean; } } public void removeAllPlugin(){ for(String path : mPaths){ IPlugin plugin = mPlugins.get(path); if(plugin != null) { plugin.onPluginDestroy(); mPlugins.remove(path); } } mProps.clear(); Log.d("propStatus","配置檔案個數"+mProps.size()); } }
這裡就大致實現了動態載入的功能,不過博主水平有限,這種方式對外掛編寫的要求很多,還不支援資源載入。並且耦合度高,一旦修改或新增某些功能就要全都修改,並且現在網上有很多動態載入的框架,功能要強大很多,有興趣的可以自行了解。
推薦一個公眾號,不吐槽,不毒舌,偶爾發發文章,偶爾推薦好物,歡迎關注或者有女票的程式狗們推薦給女票