Android插件化,带领大家自己实现自己的简易插件化


前言

本文带领大家自己实现自己的简易插件化
本文采用插桩的方式实现。
用户通过下载插件的方式,实现打开这个插件的activity,实现按需下载,按需下载使用插件,这里因为时间关系,游戏下载的步骤:手动上传到sd卡根目录假装用户下载了这个apk。


一、Android插件化是什么?

提到插件化,就不得不提起方法数超过65535的问题,我们可以通过Dex分包来解决,同时也可以通过使用插件化开发来解决。插件化的概念就是由宿主APP去加载以及运行插件APP。

1. 下面是一些插件化的优势:

  • 在一个大的项目里面,为了明确的分工,往往不同的团队负责不同的插件APP,这样分工更加明确
  • 各个模块封装成不同的插件APK,不同模块可以单独编译,提高了开发效率。
  • 解决了上述的方法数超过限制的问题。
  • 可以通过上线新的插件来解决线上的BUG,达到“热修复”的效果。
  • 减小了宿主APK的体积。

2. 下面是插件化开发的缺点:

  • 插件化开发的APP不能在Google Play上线,也就是没有海外市场。

二、使用步骤

这里我们暂时就做一个demo,假设有这样一个需求,需要从主activity跳转到一个loginActivity,然后在点击loginActivity里的按钮,实现在插件内跳转。

1.创建一个新项目

这一步想必大家都会做,就是使用android studio 创建一个新项目就可以了。

2.创建Moudle(login)

既然是插件话,我们就要把业务登录模块挪出来,所以建立一个Login Module,这也是正常的业务逻辑通过下面的方法,名字叫login.
创建一个LoginModule
然后我们在创建一个library,这个library就是我们实现插装的核心库,创建方式跟上面的方式一样,选择那个AndroidLibrary就像,名字取名为plugin_core,这样的你工程就如下面的图片一样。

接下来我们就看一下这些里面都放了啥
app模块里面其实就是一个MainActivity,代码如下

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
	//跳转到login插件的方法
    public void jumpActivity(View view) {
    //这个PluginManager是我们app依赖的pulgin_core库里的一个方法,将在下面给出
        PluginManager.init(getApplicationContext());  
    //这里的login.apk,是我们把login插件打包生成后的apk文件,然后上传到手机的sd卡根目录
    //下假装用户下载了这个插件)    
       PluginManager.getInstance().loadDex(Environment.getExternalStorageDirectory()+"/login.apk");
        PackageInfo packageInfo = PluginManager.getInstance().getPackageInfo();
        ActivityInfo[] activities = packageInfo.activities;
        if(activities == null || activities.length == 0){
            return;
        }
        //其实这里的name是PluginActivity的全路径(包名+类名)
        String name = activities[0].name;
        Intent intent = new Intent(this, PluginActivity.class);
        intent.putExtra("className",name);
        startActivity(intent);
    }

app下的manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.suyong.tinker">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!--这个是插件activity,需要在这里声明,因为所有下面的插件将依靠这个类实现启动-->
        <activity android:name="com.suyong.plugin_core.PluginActivity" />
    </application>

</manifest>

再看一下我们的login模块了有啥?有两个activity 代码如下,这里为了方便展示,就写在一起了,实际上这是两个独立的activity,loginActivity里面的按钮可以启动twoActivity.

/**
BaseActivity 是我plugin_core的类
我规定所有的插件Activity需要继承这个BaseActivity,
否则不能启动这个插件。
**/


//LoginActivity.class
import com.suyong.plugin_core.BaseActivity;

public class LoginActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        findViewById(R.id.jump).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(that,twoActivity.class));
            }
        });
    }
}



//twoActivity.class
public class twoActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_two);
    }
}

login下的manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.suyong.login">

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

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

</manifest>

3.plugin_core插件核心库

下面贴一下这个library里所有类,注意看注释。

  • BaseActivity
package com.suyong.plugin_core;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

//所有的插件类需要继承这个BaseActivity,否则无法启动
public class BaseActivity extends AppCompatActivity implements PluginInterface{

	//这个that是最重要的上下文,是PluginActivity的实列,这样我们就可以意识到
	//我们所有的系统级的api都是通过调用that里的方法实现的,因为我们所有的插件
	//别看都是继承与BaseActivity,但BaseActivity的方法都是调用的that,所以所有
	//的插件activity都是共享PluginActivity上下文。
    protected Activity that;
    
    @Override
    public void attach(Activity activity) {
        that = activity;
    }
    @Override
    public void setContentView(int layoutResID) {
        that.setContentView(layoutResID);
    }

    @Override
    public void setContentView(View view) {
        that.setContentView(view);
    }

    @Override
    public <T extends View> T findViewById(int id) {
        return that.findViewById(id);
    }

    @Override
    public Intent getIntent() {
        return that.getIntent();
    }

    @Override
    public Resources getResources() {
        return that.getResources();
    }

    @Override
    public WindowManager getWindowManager() {
        return that.getWindowManager();
    }

    @Override
    public Window getWindow() {
        return that.getWindow();
    }

    @Override
    public void startActivity(Intent intent) {
        Intent i = new Intent();
        i.putExtra("className",intent.getComponent().getClassName());
        that.startActivity(i);
    }

    @NonNull
    @Override
    public LayoutInflater getLayoutInflater() {
        return that.getLayoutInflater();
    }

    @Override
    public ClassLoader getClassLoader() {
        return that.getClassLoader();
    }
    
    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstance) {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStart() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onResume() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onPause() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStop() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onDestroy() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onSaveInstanceState(Bundle outState) {

    }

	//这个千万不要调用that.onBackPressed(),不然会出现应用死循环,一直调用onBackPressed()
    @Override
    public void onBackPressed(){

    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        return true;
    }

    @Override
    public ApplicationInfo getApplicationInfo() {
        return that.getApplicationInfo();
    }


}

  • PluginActivity
package com.suyong.plugin_core;

import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.MotionEvent;

import androidx.appcompat.app.AppCompatActivity;

//插件化路由框架
//所有的插件类启动都是调用这个方法,通过在intent
//添加哦 className 传递要启动activity的全路径。
public class PluginActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstance){
        super.onCreate(savedInstance);
        String className = getIntent().getStringExtra("className");
        try {
        //通过全路径解析出class
            Class<?> aClass = PluginManager.getInstance().getDexClassLoader().loadClass(className);
            //实例化这个class
            Object activity = aClass.newInstance();
            if(activity instanceof BaseActivity){
            	//这里就判断了非继承自BaseActivity的插件将不能启动
                PluginInterface instance = (PluginInterface) activity;
                //把上下文传给BaseActivity,方便插件类调用上下文
                instance.attach(PluginActivity.this);
                //启动插件类的onCreate()方法。
                instance.onCreate(savedInstance);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getDexClassLoader();
    }

    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getResources();
    }

    @Override
    public void startActivity(Intent intent) {
      String className = intent.getStringExtra("className");
      //注意这里哦,我们是启动了自己,className存放了我们要启动的类的全路径
      Intent i = new Intent(PluginActivity.this,PluginActivity.class);
      i.putExtra("className",className);
      super.startActivity(i);
    }
}

  • PluginInterface
public interface PluginInterface {
    void attach(Activity activity);
    void onCreate(Bundle savedInstance);
    void onStart();
    void onResume();
    void onPause();
    void onStop();
    void onDestroy();
    void onBackPressed();
    boolean onTouchEvent(MotionEvent event);
    void onSaveInstanceState(Bundle outState);
}

  • PluginManager
package com.suyong.plugin_core;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;

import java.io.File;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;
/**
我们这个类的目的就是为了记载外部的Apk,为什么呢,因为插件类实际上只是以文件的新式存在,app启动时候系统并不会加载外部插件类,所以我们要手动通过这个类加载外部的apk.
**/

public class PluginManager {

    private static Context mContext;
    //加载外部的DexClassLoader
    private DexClassLoader mDexClassLoader;
    //资源管理
    private Resources mResources;
    //包信息管理类
    private PackageInfo mPackageInfo;
     
     //一定要先调用这个方法,不然会报错
    public static void init(Context context) {
        mContext = context;
    }

    private static PluginManager instance = new PluginManager();

    private PluginManager() {
    }

    public static PluginManager getInstance() {
        return instance;
    }

	//这个方法就是用来加载指定路径的外部Apk路径的
    public void loadDex(String apkPath) {
        //创建缓存路径
        File file = mContext.getDir("plugin", Context.MODE_PRIVATE);
        mDexClassLoader = new DexClassLoader(apkPath, file.getAbsolutePath(), null, mContext.getClassLoader());
        mPackageInfo = mContext.getPackageManager().getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager,apkPath);
            mResources = new Resources(assetManager, mContext.getResources().getDisplayMetrics(), mContext.getResources().getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public DexClassLoader getDexClassLoader() {
        return mDexClassLoader;
    }

    public Resources getResources() {
        return mResources;
    }

    public PackageInfo getPackageInfo() {
        return mPackageInfo;
    }
}

实现效果:
在这里插入图片描述
可以看到成功启动,也可以返回,这与平常没什么两样,记住多看注释哦!


总结

这里只是用了插装的方法实现了简易的插件化,并不是特别好的方法,大家可以采用插件化框架,肯定比我这个兼容性 实用性要强许多,时间不早了,溜了溜了,欢迎留言。
PS:纯属个人理解,希望指正!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值