前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android插件化浅析

Android插件化浅析

作者头像
老马的编程之旅
发布2022-06-22 09:50:37
5730
发布2022-06-22 09:50:37
举报
文章被收录于专栏:深入理解Android

插件化是2016年移动端最火爆的几个名词之一,目前淘宝、百度、腾讯等都有成熟的动态加载框架,包括apkplug, 本篇博客就来探讨一下插件化设计。本博客主要从以下几个方面对插件化进行解析:

Ø  为什么会提出插件化?

Ø  插件化概述

Ø  插件化例子

1.      为什么会提出插件化?

一个Android应用在开发到了一定阶段以后,功能模块将会越来越多,APK安装包也越来越大。此时可能就需要考虑如何分拆整个应用了。随着Android应用的不断成熟,一般会遇到如下的问题:

1)     代码越来越庞大,维护的困难度增加,应对bug反应越来越慢

2)     需求越来越多,某一模块的小改动都要重新发布版本,发布时间越来越不可控。

3)     还有就是65535方法数的问题,如果超过最大限制,无法编译

在这些问题下,Android插件化开发就应运而生了。

2.      插件化概述

Ø  插件化的概念:

Android 插件化 —— 指将一个程序划分为不同的部分,也就说把一个很大的app分成n多个比较小的app,其中有一个app是主app,比如一般 App 的皮肤样式就可以看成一个插件。目前来说,结合插件包的格式来说插件的方式有三种:

1,apk安装,

2,apk不安装,

3,dex包.

三种方式其实主要是解决两个方面的问题:

1,加载插件中的类,

2,加载插件中的资源.

第一个加载类的问题,这三个方式都可以很好的解决.但目前三种方式都没有很完美的解决第2个问题.

Ø  插件化的优缺点

插件化的优点主要有以下几个方面:

1)     模块解耦,应用程序扩展性强

2)     解除单个dex函数不能超过 65535的限制

3)     动态升级,下载更新节省流量

4)     高效开发(编译速度更快)

Ø  插件化的缺点:

1)     增加了主应用程序的逻辑难度

2)     技术有难度,目前一些成熟的框架都是闭源的

3.      插件化例子

在介绍完插件化的概念和优缺点之后,我们就先一个小的案例,来帮助大家更好的理解插件的原理是什么样的。

先上项目效果图:

项目描述:该Demo很简单,就是点击“切换背景”的按钮之后,会弹出一个PopupWindow,里面是一个listview,这个listview里面item显示是插件的名字,点击相应插件的名字,背景图片就会更改为插件中图片。

布局代码activity_main.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/relativeLayout"
    android:background="@drawable/kenan1"
    tools:context="com.example.jikeyoujikeyou.plugindemo.MainActivity">


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/button"
        android:text="切换背景"/>
</RelativeLayout>

PopupWindow的布局代码

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/listview"/>

</LinearLayout>

初始化控件

代码语言:javascript
复制
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {

    private ListView mListview;
    private RelativeLayout mRelativeLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);
        mRelativeLayout = (RelativeLayout) findViewById(R.id.relativeLayout);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showPopWindow(view);
            }


        });

    }

点击按钮弹出PopupWindow的逻辑

代码语言:javascript
复制
private void showPopWindow(View v) {
    View popview = getLayoutInflater().inflate(R.layout.popwindow_layout, null);
    ListView listView = (ListView) popview.findViewById(R.id.listview);
    PopupWindow popupwindow = new PopupWindow(popview, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    popupwindow.setBackgroundDrawable(getResources().getDrawable(R.drawable.kenan1));
    popupwindow.setFocusable(true);
    popupwindow.setOutsideTouchable(true);

    List<Map<String, String>> pluginList = findPluginList();

    if (pluginList == null || pluginList.size() == 0) {
        Toast.makeText(this, "手机里并没有插件哦!", Toast.LENGTH_SHORT).show();
        return;
    }
    SimpleAdapter simpleAdapter = new SimpleAdapter(this, pluginList, android.R.layout.simple_list_item_1, new String[]{"label"}, new int[]{android.R.id.text1});
    listView.setAdapter(simpleAdapter);

    popupwindow.setHeight(100 * pluginList.size());
    popupwindow.setWidth(300);
    popupwindow.showAsDropDown(v);

    listView.setOnItemClickListener(this);
}

这一段代码十分简单,没什么需要解释的,唯一需要强调的是popupwindow.setBackgroundDrawable(getResources().getDrawable(R.drawable.kenan1));必须给popupwindow设置一个背景,否则它弹不出来,具体原因请参考popupwindow源码,这里面有一个findPluginList()方法,这个方法是我自己定义的,用来返回手机中该项目的插件列表,该方法逻辑如下:

代码语言:javascript
复制
private List<Map<String, String>> findPluginList() {
    List<Map<String, String>> pluginList = new ArrayList<Map<String, String>>();
    //如何获取插件列表?
    PackageManager packageManager = this.getPackageManager();
    //获取已经安卓的app
    List<PackageInfo> packages = packageManager.getInstalledPackages(PackageManager.GET_ACTIVITIES);

    //获取当前应用的包信息
    try {
        PackageInfo currentPackageInfo = packageManager.getPackageInfo(getPackageName(), 0);

        for (PackageInfo packageInfo : packages) {
            String packageName = packageInfo.packageName;
            String shareUserId = packageInfo.sharedUserId;
            //判断当前的包,是不是我们需要的插件
            //如果是以下三种情况,就不是我们的插件,直接返回
            if (currentPackageInfo.packageName.equals(packageName) || !currentPackageInfo.sharedUserId.equals(shareUserId) || TextUtils.isEmpty(shareUserId)) {
                continue;
            }
            //就是我们的插件
            Map<String, String> pluginMap = new HashMap<String, String>();
            //获取应用程序的名字
            String label = packageInfo.applicationInfo.loadLabel(packageManager).toString();
            pluginMap.put("packageName", packageName);
            pluginMap.put("label", label);
            pluginList.add(pluginMap);

        }

    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }

    return pluginList;
}

这个方法内主要就是通过packageManager获取已经安装在手机里的应用程序列表,然后进行判断是否是我们主应用的插件,如果是的话,就将其应用程序名字和包名存入一个map集合中,然后添加到我创建的pluginList中,值得强调的一点是,如何确定是我们应用的插件呢?在这里我们主要通过在清单文件中声明android:sharedUserId="com.android.plugin",只要主程序和插件程序具有相同的sharedUserId,他们就可以相互识别出来。

以下是我的清单文件:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.jikeyoujikeyou.plugindemo"
    android:sharedUserId="com.android.plugin">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

上述代码,我们就已经完成了popupwindow显示插件列表的逻辑,接下来就是给popupwindow中的listview设置点击事件了,点击之后会进行主程序背景图片的切换,逻辑如下

代码语言:javascript
复制
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
    //点击插件,加载资源
    //资源需要通过资源加载器进行加载--context
    //记住是plugin的context
    //1.获取插件的上下文

    Context pluginContext = findPluginContext(position);
    //2.从插件上下文加载资源
    int resId = findResoucesId(pluginContext, position);
    if (resId != 0) {
        Drawable drawable = pluginContext.getResources().getDrawable(resId);
        mRelativeLayout.setBackgroundDrawable(drawable);

    }

}

需要加载插件应用中的资源,那就必须使用到插件的上下文,所以我定义了一个方法findPluginContext,来获取插件应用的Context,逻辑如下:

代码语言:javascript
复制
private Context findPluginContext(int position) {
    Map<String, String> map = this.findPluginList().get(position);
    String packageName = map.get("packageName");
    try {
        return createPackageContext(packageName, CONTEXT_IGNORE_SECURITY);
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
        return null;
    }
}

这里有一个方法需要说吗一下createPackageContext(packageName,CONTEXT_IGNORE_SECURITY);该方法可以通过包名来获取对应的上下文。

最后我还定义了一个方法findResoucesId,里面逻辑就是通过反射机制,使用插件的Context来获取R.java文件下的静态类drawable,返回插件应用里的图片id,代码如下:

代码语言:javascript
复制
private int findResoucesId(Context pluginContext, int position) {
    //使用反射机制
    ClassLoader classLoader = new PathClassLoader(pluginContext.getPackageResourcePath(), PathClassLoader.getSystemClassLoader());
    String pluginPackageName = this.findPluginList().get(position).get("packageName");
    try {
        //获取R下的静态类drawable
        Class<?> drawableClass = Class.forName(pluginPackageName + ".R$drawable", true, classLoader);
        //获取里面的属性
        Field[] fields = drawableClass.getFields();
        for (Field field : fields) {
            //获取属性名称
            String name = field.getName();
            if ("kenan1".equals(name)) {
                //获取资源的id
                return field.getInt(R.drawable.class);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    return 0;
}

插件的图片id,都拿到了,最后给背景设置一下,就可以完成切换了,到这里,本篇博客就到此结束了,这里仅仅是我目前对于插件化一些理解,插件化还有很多需要深入研究的地方,等深入研究之后,会继续和大家进行分享。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016-07-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档