使用DroidPlugin实践应用的插件化

欢迎Follow我的GitHub, 关注我的. 其余参考Android目录.

使用DroidPlugin实践应用的插件化_第1张图片
Droid Plugin

本文的合集已经编著成书,高级Android开发强化实战,欢迎各位读友的建议和指导。在京东即可购买:https://item.jd.com/12385680.html

Android

随着应用的体积越来越大, 插件化也逐渐受到关注, 参考. 应用插件化把模块完全解耦, 使用下载更新的方式, 扩展应用, 是平台化类应用的必然选择. 国内很多公司实现了各式各样的方法, 360的DroidPlugin是比较有意思的一个, 使用预占位的方式注册四大组件, 实现热更新, 参考, 也可以直接读源码理解实现逻辑.

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; // 跳转插件的按钮

    @Override
    protected 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的适配器
 * 

* 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; } @Override public CharSequence getPageTitle(int position) { return TITLES[position]; } }

2. 加载页

显示Download文件夹下的Apk信息. 使用RecyclerView实现apk列表, 复用Adapter, 标志位(ApkOperator.TYPE_STORE)区分页面. 使用Rx异步扫描Download文件夹, 添加至列表. 接收服务连接状态, 成功则自动显示Apk.

/**
 * 安装Apk的页面, 使用RecyclerView.
 * 

* 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 @Override public 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); } } // 加载Apk private void loadApks() { // 异步加载, 防止Apk过多, 影响速度 Observable.just(getApkFromDownload()) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(mStoreAdapter::setApkItems); } // 从下载文件夹获取Apk private ArrayList getApkFromDownload() { File files = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); PackageManager pm = getActivity().getPackageManager(); ArrayList 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; } @Override public void onDestroyView() { super.onDestroyView(); ButterKnife.unbind(this); PluginManager.getInstance().removeServiceConnection(mServiceConnection); } }

适配器, 负责列表显示, 操作交由ViewHolder进行处理.

/**
 * 启动的适配器
 * 

* Created by wangchenlong on 16/1/13. */ public class ApkListAdapter extends RecyclerView.Adapter { private ArrayList mApkItems; private Activity mActivity; private int mType; // 类型 public ApkListAdapter(Activity activity, int type) { mActivity = activity; mApkItems = new ArrayList<>(); mType = type; } public void setApkItems(ArrayList 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
 * 

* 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; // 类型 } // 绑定ViewHolder public 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 { @Override protected void onPostExecute(String v) { Toast.makeText(mContext, v, Toast.LENGTH_LONG).show(); } @Override protected String doInBackground(Void... params) { return mApkOperator.installApk(mApkItem); } } }

注意, 安装Apk使用异步线程(AsyncTask), 不能使用Rx.

3. 启动页

启动页面, 显示已安装的Apk, 包含启动和卸载功能. 与安装页不同, 额外增加一个接收器, 负责接收安装成功之后的广播, 用于更新列表.

/**
 * 启动Apk页面
 * 

* 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 @Override public 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); } } @Override public void onDestroyView() { super.onDestroyView(); ButterKnife.unbind(this); mInstallApkReceiver.unregisterReceiver(this.getActivity()); } // 加载Apk private void loadApks() { // 异步加载, 防止Apk过多, 影响速度 Observable.just(getApkFromInstall()) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(mApkListAdapter::setApkItems); } // 获取安装中获取Apk private ArrayList getApkFromInstall() { ArrayList apkItems = new ArrayList<>(); try { final List infos = PluginManager.getInstance().getInstalledPackages(0); if (infos == null) { return apkItems; } final PackageManager pm = getActivity().getPackageManager(); // noinspection all for (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); } @Override public 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
 * 

* Created by wangchenlong on 16/1/13. */ public class ApkOperator { public static final int TYPE_STORE = 0; // 存储Apk public static final int TYPE_START = 1; // 启动Apk private Activity mActivity; // 绑定Dialog private RemoveCallback mCallback; // 删除Item的回调 public ApkOperator(Activity activity, RemoveCallback callback) { mActivity = activity; mCallback = callback; } // 删除Apk public 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; } // 卸载Apk public 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(); } // 打开Apk public 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调用删除Item public 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;

    @Override
    protected 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.

你可能感兴趣的:(使用DroidPlugin实践应用的插件化)