日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用DroidPlugin实践应用的插件化

發布時間:2025/3/15 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用DroidPlugin实践应用的插件化 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

隨著應用的體積越來越大, 插件化也逐漸受到關注,?參考. 應用插件化把模塊完全解耦, 使用下載更新的方式, 擴展應用, 是平臺化類應用的必然選擇. 國內很多公司實現了各式各樣的方法, 360的DroidPlugin是比較有意思的一個, 使用預占位的方式注冊四大組件, 實現熱更新,?參考, 也可以直接讀源碼理解實現邏輯.


Droid Plugin

Talk is cheap, show you the code! 如何把DroidPlugin用起來呢? 這是我比較關注的事情, 開源的Demo寫的如此悲傷, 我來重新梳理一下, 又添加了幾個功能測試. 引入DroidPlugin作為Submodule的依賴.

Github下載地址?和?測試Apk.

使用方法, 生成測試apk, 和其他若干apk, 放入Download文件夾下.
adb命令:?adb push app-debug.apk /sdcard/Download/app-debug.apk
確保Download文件夾下, 有.apk后綴名的文件.

主要
(1) 插件安裝后, 可以直接啟動, 不需要任何冗余操作.
(2) 宿主的權限要多于插件的權限, 否則會權限不足.
(3) 宿主和插件, 可以通過隱式Intent進行通信.


動畫

1. 主頁

使用TabLayout+ViewPager的架構, 包含兩個頁面, 一個是安裝\刪除頁面, 另一個是啟動\卸載頁面. 為了測試和插件的Intent通信, 增加跳轉功能和顯示信息功能.

/*** 主頁面, 使用TabLayout+ViewPager.* 子頁面, 使用RecyclerView.** @author wangchenlong*/ public class MainActivity extends AppCompatActivity {@Bind(R.id.main_tl_tabs) TabLayout mTlTabs; // Tabs@Bind(R.id.main_vp_container) ViewPager mVpContainer; // ViewPager@Bind(R.id.main_b_goto) Button mBGoto; // 跳轉插件的按鈕@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager());mVpContainer.setAdapter(adapter);mTlTabs.setupWithViewPager(mVpContainer);mBGoto.setOnClickListener(this::gotoPlugin);Intent intent = getIntent();if (intent != null && intent.getStringExtra(PluginConsts.MASTER_EXTRA_STRING) != null) {String words = "say: " + intent.getStringExtra(PluginConsts.MASTER_EXTRA_STRING);Toast.makeText(this, words, Toast.LENGTH_SHORT).show();}}// 跳轉控件private void gotoPlugin(View view) {if (isActionAvailable(view.getContext(), PluginConsts.PLUGIN_ACTION_MAIN)) {Intent intent = new Intent(PluginConsts.PLUGIN_ACTION_MAIN);intent.putExtra(PluginConsts.PLUGIN_EXTRA_STRING, "Hello, My Plugin!");startActivity(intent);} else {Toast.makeText(view.getContext(), "跳轉失敗", Toast.LENGTH_SHORT).show();}}// Action是否允許public static boolean isActionAvailable(Context context, String action) {Intent intent = new Intent(action);return context.getPackageManager().resolveActivity(intent, 0) != null;} }

ViewPager適配器

/*** ViewPager的適配器* <p>* Created by wangchenlong on 16/1/8.*/ public class PagerAdapter extends FragmentPagerAdapter {private static final String[] TITLES = {"已安裝","未安裝"};public PagerAdapter(FragmentManager fm) {super(fm);}@Override public Fragment getItem(int position) {if (position == 0) {return new StartFragment(); // 已安裝頁} else {return new StoreFragment(); // 想要安裝頁}}@Override public int getCount() {return TITLES.length;}@Overridepublic CharSequence getPageTitle(int position) {return TITLES[position];} }

2. 加載頁

顯示Download文件夾下的Apk信息. 使用RecyclerView實現apk列表, 復用Adapter, 標志位(ApkOperator.TYPE_STORE)區分頁面. 使用Rx異步掃描Download文件夾, 添加至列表. 接收服務連接狀態, 成功則自動顯示Apk.

/*** 安裝Apk的頁面, 使用RecyclerView.* <p>* Created by wangchenlong on 16/1/8.*/ public class StoreFragment extends Fragment {@Bind(R.id.list_rv_recycler) RecyclerView mRvRecycler;private ApkListAdapter mStoreAdapter; // 適配器// 服務連接private ServiceConnection mServiceConnection = new ServiceConnection() {@Override public void onServiceConnected(ComponentName name, IBinder service) {loadApks();}@Override public void onServiceDisconnected(ComponentName name) {}};@Nullable @Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_list, container, false);ButterKnife.bind(this, view);return view;}@Override public void onViewCreated(View view, Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);LinearLayoutManager llm = new LinearLayoutManager(view.getContext());llm.setOrientation(LinearLayoutManager.VERTICAL);mRvRecycler.setLayoutManager(llm);mStoreAdapter = new ApkListAdapter(getActivity(), ApkOperator.TYPE_STORE);mRvRecycler.setAdapter(mStoreAdapter);if (PluginManager.getInstance().isConnected()) {loadApks();} else {PluginManager.getInstance().addServiceConnection(mServiceConnection);}}// 加載Apkprivate void loadApks() {// 異步加載, 防止Apk過多, 影響速度Observable.just(getApkFromDownload()).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(mStoreAdapter::setApkItems);}// 從下載文件夾獲取Apkprivate ArrayList<ApkItem> getApkFromDownload() {File files = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);PackageManager pm = getActivity().getPackageManager();ArrayList<ApkItem> apkItems = new ArrayList<>();for (File file : files.listFiles()) {if (file.exists() && file.getPath().toLowerCase().endsWith(".apk")) {final PackageInfo info = pm.getPackageArchiveInfo(file.getPath(), 0);apkItems.add(new ApkItem(pm, info, file.getPath()));}}return apkItems;}@Overridepublic void onDestroyView() {super.onDestroyView();ButterKnife.unbind(this);PluginManager.getInstance().removeServiceConnection(mServiceConnection);} }

適配器, 負責列表顯示, 操作交由ViewHolder進行處理.

/*** 啟動的適配器* <p>* Created by wangchenlong on 16/1/13.*/ public class ApkListAdapter extends RecyclerView.Adapter<ApkItemViewHolder> {private ArrayList<ApkItem> mApkItems;private Activity mActivity;private int mType; // 類型public ApkListAdapter(Activity activity, int type) {mActivity = activity;mApkItems = new ArrayList<>();mType = type;}public void setApkItems(ArrayList<ApkItem> apkItems) {mApkItems = apkItems;notifyDataSetChanged();}public void addApkItem(ApkItem apkItem) {mApkItems.add(apkItem);notifyItemInserted(mApkItems.size() + 1);}public void removeApkItem(ApkItem apkItem) {mApkItems.remove(apkItem);notifyDataSetChanged();}public ApkItem getApkItem(int index) {return mApkItems.get(index);}public int getCount() {return mApkItems.size();}@Override public ApkItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.apk_item, parent, false);return new ApkItemViewHolder(mActivity, view, mType, this::removeApkItem);}@Override public void onBindViewHolder(ApkItemViewHolder holder, int position) {holder.bindTo(mApkItems.get(position));}@Override public int getItemCount() {return mApkItems.size();} }

注意, 在設置Item時, 需要刷新列表, 使用notifyDataSetChanged或notifyItemInserted.

ViewHolder, 控制列表點擊事件, 根據頁面類型, 修改調用方法.

/*** Apk的列表, 參考: R.layout.apk_item* <p>* Created by wangchenlong on 16/1/13.*/ public class ApkItemViewHolder extends RecyclerView.ViewHolder {@Bind(R.id.apk_item_iv_icon) ImageView mIvIcon; // 圖標@Bind(R.id.apk_item_tv_title) TextView mTvTitle; // 標題@Bind(R.id.apk_item_tv_version) TextView mTvVersion; // 版本號@Bind(R.id.apk_item_b_do) Button mBDo; // 確定按鈕@Bind(R.id.apk_item_b_undo) Button mBUndo; // 取消按鈕private ApkItem mApkItem; // Apk項private Context mContext; // 上下文private ApkOperator mApkOperator; // Apk操作private int mType; // 類型/*** 初始化ViewHolder** @param activity Dialog綁定Activity* @param itemView 項視圖* @param type 類型, 加載或啟動* @param callback 刪除Item的調用*/public ApkItemViewHolder(Activity activity, View itemView, int type, ApkOperator.RemoveCallback callback) {super(itemView);ButterKnife.bind(this, itemView);mContext = activity.getApplicationContext();mApkOperator = new ApkOperator(activity, callback); // Apk操作mType = type; // 類型}// 綁定ViewHolderpublic void bindTo(ApkItem apkItem) {mApkItem = apkItem;mIvIcon.setImageDrawable(apkItem.icon);mTvTitle.setText(apkItem.title);mTvVersion.setText(String.format("%s(%s)", apkItem.versionName, apkItem.versionCode));// 修改文字if (mType == ApkOperator.TYPE_STORE) {mBUndo.setText("刪除");mBDo.setText("安裝");} else if (mType == ApkOperator.TYPE_START) {mBUndo.setText("卸載");mBDo.setText("啟動");}mBUndo.setOnClickListener(this::onClickEvent);mBDo.setOnClickListener(this::onClickEvent);}// 點擊事件private void onClickEvent(View view) {if (mType == ApkOperator.TYPE_STORE) {if (view.equals(mBUndo)) {mApkOperator.deleteApk(mApkItem);} else if (view.equals(mBDo)) {// 安裝Apk較慢需要使用異步線程new InstallApkTask().execute();}} else if (mType == ApkOperator.TYPE_START) {if (view.equals(mBUndo)) {mApkOperator.uninstallApk(mApkItem);} else if (view.equals(mBDo)) {mApkOperator.openApk(mApkItem);}}}// 安裝Apk的線程, Rx無法使用.private class InstallApkTask extends AsyncTask<Void, Void, String> {@Overrideprotected void onPostExecute(String v) {Toast.makeText(mContext, v, Toast.LENGTH_LONG).show();}@Overrideprotected String doInBackground(Void... params) {return mApkOperator.installApk(mApkItem);}} }

注意, 安裝Apk使用異步線程(AsyncTask), 不能使用Rx.

3. 啟動頁

啟動頁面, 顯示已安裝的Apk, 包含啟動和卸載功能. 與安裝頁不同, 額外增加一個接收器, 負責接收安裝成功之后的廣播, 用于更新列表.

/*** 啟動Apk頁面* <p>* Created by wangchenlong on 16/1/13.*/ public class StartFragment extends Fragment {@Bind(R.id.list_rv_recycler) RecyclerView mRvRecycler;private ApkListAdapter mApkListAdapter; // 適配器private InstallApkReceiver mInstallApkReceiver; // Apk安裝接收器// 服務連接private final ServiceConnection mServiceConnection = new ServiceConnection() {@Override public void onServiceConnected(ComponentName name, IBinder service) {loadApks();}@Override public void onServiceDisconnected(ComponentName name) {}};@Nullable @Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_list, container, false);ButterKnife.bind(this, view);return view;}@Override public void onViewCreated(View view, Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);LinearLayoutManager llm = new LinearLayoutManager(view.getContext());llm.setOrientation(LinearLayoutManager.VERTICAL);mRvRecycler.setLayoutManager(llm);mApkListAdapter = new ApkListAdapter(getActivity(), ApkOperator.TYPE_START);mRvRecycler.setAdapter(mApkListAdapter);mInstallApkReceiver = new InstallApkReceiver();mInstallApkReceiver.registerReceiver(this.getActivity());if (PluginManager.getInstance().isConnected()) {loadApks();} else {PluginManager.getInstance().addServiceConnection(mServiceConnection);}}@Overridepublic void onDestroyView() {super.onDestroyView();ButterKnife.unbind(this);mInstallApkReceiver.unregisterReceiver(this.getActivity());}// 加載Apkprivate void loadApks() {// 異步加載, 防止Apk過多, 影響速度Observable.just(getApkFromInstall()).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(mApkListAdapter::setApkItems);}// 獲取安裝中獲取Apkprivate ArrayList<ApkItem> getApkFromInstall() {ArrayList<ApkItem> apkItems = new ArrayList<>();try {final List<PackageInfo> infos = PluginManager.getInstance().getInstalledPackages(0);if (infos == null) {return apkItems;}final PackageManager pm = getActivity().getPackageManager();// noinspection allfor (final PackageInfo info : infos) {apkItems.add(new ApkItem(pm, info, info.applicationInfo.publicSourceDir));}} catch (RemoteException e) {e.printStackTrace();}return apkItems;}// 安裝Apk接收器private class InstallApkReceiver extends BroadcastReceiver {// 注冊監聽public void registerReceiver(Context context) {IntentFilter filter = new IntentFilter();filter.addAction(PluginManager.ACTION_PACKAGE_ADDED);filter.addAction(PluginManager.ACTION_PACKAGE_REMOVED);filter.addDataScheme("package");context.registerReceiver(this, filter);}// 關閉監聽public void unregisterReceiver(Context context) {context.unregisterReceiver(this);}@Overridepublic void onReceive(Context context, Intent intent) {// 監聽添加和刪除事件if (PluginManager.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {try {PackageManager pm = getActivity().getPackageManager();String pkg = intent.getData().getAuthority();PackageInfo info = PluginManager.getInstance().getPackageInfo(pkg, 0);mApkListAdapter.addApkItem(new ApkItem(pm, info, info.applicationInfo.publicSourceDir));} catch (Exception e) {e.printStackTrace();}} else if (PluginManager.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {String pkg = intent.getData().getAuthority();int num = mApkListAdapter.getCount();ApkItem removedItem = null;for (int i = 0; i < num; i++) {ApkItem item = mApkListAdapter.getApkItem(i);if (TextUtils.equals(item.packageInfo.packageName, pkg)) {removedItem = item;break;}}if (removedItem != null) {mApkListAdapter.removeApkItem(removedItem);}}}} }

復用Adapter和ViewHolder, 代碼簡介之道.

4. 方法類

四大方法, 安裝\刪除\啟動\卸載, 在刪除和卸載時, 均會提示Dialog. 注意的是安裝Apk, 耗時較長, 需要使用異步線程.

/*** Apk操作, 包含刪除\安裝\卸載\啟動Apk* <p>* Created by wangchenlong on 16/1/13.*/ public class ApkOperator {public static final int TYPE_STORE = 0; // 存儲Apkpublic static final int TYPE_START = 1; // 啟動Apkprivate Activity mActivity; // 綁定Dialogprivate RemoveCallback mCallback; // 刪除Item的回調public ApkOperator(Activity activity, RemoveCallback callback) {mActivity = activity;mCallback = callback;}// 刪除Apkpublic void deleteApk(final ApkItem item) {AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);builder.setTitle("警告");builder.setMessage("你確定要刪除" + item.title + "么?");builder.setNegativeButton("刪除", (dialog, which) -> {if (new File(item.apkFile).delete()) {mCallback.removeItem(item);Toast.makeText(mActivity, "刪除成功", Toast.LENGTH_SHORT).show();} else {Toast.makeText(mActivity, "刪除失敗", Toast.LENGTH_SHORT).show();}});builder.setNeutralButton("取消", null);builder.show();}/*** 安裝Apk, 耗時較長, 需要使用異步線程** @param item Apk項* @return [0:成功, 1:已安裝, -1:連接失敗, -2:權限不足, -3:安裝失敗]*/public String installApk(final ApkItem item) {if (!PluginManager.getInstance().isConnected()) {return "連接失敗"; // 連接失敗}if (isApkInstall(item)) {return "已安裝"; // 已安裝}try {int result = PluginManager.getInstance().installPackage(item.apkFile, 0);boolean isRequestPermission = (result == PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION);if (isRequestPermission) {return "權限不足";}} catch (RemoteException e) {e.printStackTrace();return "安裝失敗";}return "成功";}// Apk是否安裝private boolean isApkInstall(ApkItem apkItem) {PackageInfo info = null;try {info = PluginManager.getInstance().getPackageInfo(apkItem.packageInfo.packageName, 0);} catch (RemoteException e) {e.printStackTrace();}return info != null;}// 卸載Apkpublic void uninstallApk(final ApkItem item) {AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);builder.setTitle("警告");builder.setMessage("警告,你確定要卸載" + item.title + "么?");builder.setNegativeButton("卸載", (dialog, which) -> {if (!PluginManager.getInstance().isConnected()) {Toast.makeText(mActivity, "服務未連接", Toast.LENGTH_SHORT).show();} else {try {PluginManager.getInstance().deletePackage(item.packageInfo.packageName, 0);mCallback.removeItem(item);Toast.makeText(mActivity, "卸載完成", Toast.LENGTH_SHORT).show();} catch (RemoteException e) {e.printStackTrace();}}});builder.setNeutralButton("取消", null);builder.show();}// 打開Apkpublic void openApk(final ApkItem item) {PackageManager pm = mActivity.getPackageManager();Intent intent = pm.getLaunchIntentForPackage(item.packageInfo.packageName);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);mActivity.startActivity(intent);}// 刪除Item回調, Adapter調用刪除Itempublic interface RemoveCallback {void removeItem(ApkItem apkItem);} }

5. 互動Apk

為了測試DroidPlugin的一些特性, 又寫了一個測試Apk.
測試插件和宿主的通信, 插件類的生命周期.

public class MainActivity extends AppCompatActivity {private static final String TAG = "DEBUG-WCL: " + MainActivity.class.getSimpleName();@Bind(R.id.main_b_goto_master) Button mBGotoMaster;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);Intent intent = getIntent();if (intent != null && intent.getStringExtra(PluginConsts.PLUGIN_EXTRA_STRING) != null) {String words = "say: " + intent.getStringExtra(PluginConsts.PLUGIN_EXTRA_STRING);Toast.makeText(this, words, Toast.LENGTH_SHORT).show();}mBGotoMaster.setOnClickListener(this::gotoMaster);Log.d(TAG, "onCreate"); // 測試生命周期}@Override protected void onDestroy() {super.onDestroy();Log.d(TAG, "onDestroy"); // 測試生命周期}// 跳轉控件private void gotoMaster(View view) {if (isActionAvailable(view.getContext(), PluginConsts.MASTER_ACTION_MAIN)) {Intent intent = new Intent(PluginConsts.MASTER_ACTION_MAIN);intent.putExtra(PluginConsts.MASTER_EXTRA_STRING, "Hello, My Master!");startActivity(intent);} else {Toast.makeText(view.getContext(), "跳轉失敗", Toast.LENGTH_SHORT).show();}}// Action是否允許public static boolean isActionAvailable(Context context, String action) {Intent intent = new Intent(action);return context.getPackageManager().resolveActivity(intent, 0) != null;} }

有時間可以再試試其他公司的插件化. One by One.

OK, that's all! Enjoy It.


原文地址:?http://www.jianshu.com/p/f1217cce93ef

總結

以上是生活随笔為你收集整理的使用DroidPlugin实践应用的插件化的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。