插件化开发
一, 这篇博客我希望你认真一步步往下看写,那么就一定可以实现自己的插件化开发,学习到大型企业牛逼的插件化架构,从原理到项目,如果不能够实现那么联系我。希望带给帮助,我很荣幸的。(对于不知道插件化的朋友可以去百度了解)
项目分为两个模块:

1,从原理讲起写个Demon。
2,然后去学会使用大型企业的插件化框架并附上Demon,我看了好多人的博客,我希望我的博客能给哪些刚刚从事android编程的哥们一写简单易懂图文混搭一步步集成并作出效果。

模块一:,实现主app加载一个插件app的Activity效果如下:
如下图:我为了显示比较高大上,布局截图了支付宝里面的界面。看看淘票票和滴滴出行这两个按钮点击各自跳进不同的activity里面。从而实现了插件化。

插件时架构 插件架构模式的案例_插件化开发

一,阻碍:首先我们(支付宝)App打开之后,在我们的手机目录下有两个插件apk(淘票票)和(滴滴打车),如何从主App跳转到这个目录下的apk的Activity呢?

1,主项目app里面是没有apk的Activity和class文件,上下文,资源。
2, 我们知道acitivity之间跳转需要在清单文件AndroidManifest.xml中需要注册。

二,解决:根据一阻碍分析,我们来解决上面这两个问题。

1,对于加载apk资源和class:估计接触过DexClassLoader很多人都知道这个动态加载类,谷歌大大还是很人性化的。我们可以通过它来加载我们插件apk里面的资源。

2,我们需要一个中间的依赖库提供一套接口(这套接口必须具有主App里面activity的生命周期并让插件activity实现它),让主项目和插件Modle都依赖它,从而主App可以跳转到它(依赖库),它作为一个中间者Activity,在它的onCreate里面来初始化插件Activity,调用插件Activity的onCreate方法就实现了启动了。

三,开始撸代码:

一,新建项目,添加依赖库和两个插件(淘票票,滴滴出行)Modle

插件时架构 插件架构模式的案例_插件化教学_02

中间依赖库用来定义我们的一套标准假装为插件Activity:File->New ->New Modle

插件时架构 插件架构模式的案例_插件化架构_03

插件时架构 插件架构模式的案例_插件化Demon_04

接下来就是淘票票和滴滴出行两个Modle了如下图:New ->New Modle:

插件时架构 插件架构模式的案例_插件化架构_05

插件时架构 插件架构模式的案例_插件化架构_06

插件时架构 插件架构模式的案例_插件化教学_07

最后看看项目目录:

插件时架构 插件架构模式的案例_插件时架构_08

二,让主Activity和两个插件Modle来依赖我们的中间标准依赖库:

插件时架构 插件架构模式的案例_插件时架构_09

插件时架构 插件架构模式的案例_插件化教学_10


插件时架构 插件架构模式的案例_插件化教学_11

三,依赖完成我们来写代码部分:

首先我们去主项目和Modle中写好布局:如下:

插件时架构 插件架构模式的案例_插件化Demon_12


插件时架构 插件架构模式的案例_插件化开发_13

插件时架构 插件架构模式的案例_插件化开发_14

(1)新建一个PluginManager.java类用来加载我们的淘票票和滴滴出行插件apk,并获取插件apk的基本信息和资源等把它放在我们的主项目或者依赖项目中都行。代码如下:

package com.example.ls.pluginstudydemon;

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

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

import dalvik.system.DexClassLoader;

/**
 *
public class PluginManager {
    //用来加载我们的插件apk类
    private DexClassLoader dexclassLoader;
    //加载插件的资源文件图片呀,xml等
    private Resources resources;
    private Context context;
    private static PluginManager ourInstance = new PluginManager();
    //获取插件安装包信息如Activity的类明等
    private PackageInfo packageInfo;

    public PackageInfo getPackageInfo() {
        return packageInfo;
    }

    public static PluginManager getInstance() {
        return ourInstance;
    }
    public void setContext(Context context){
        this.context=context;
    }
    public DexClassLoader getClassLoader() {
        return dexclassLoader;
    }

    public Resources getResources() {
        return resources;
    }

    private PluginManager() {

    }

    //路径
    public void loadPath(String path) {
        File dexoutFile = context.getDir("dex", Context.MODE_PRIVATE);
        //能加载外置卡的能力了
        dexclassLoader = new DexClassLoader(path, dexoutFile.getAbsolutePath(), null, context.getClassLoader());
        PackageManager packageManager=context.getPackageManager();
        packageInfo=packageManager.getPackageArchiveInfo(path,PackageManager.GET_ACTIVITIES);

        //需要AssetManager
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager,path);
            resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());

        } catch (InstantiationException e) {


        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

(2)我们需要定义一个标准接口让插件apk的Activity实现它,并且最后在伪装的依赖项目空壳Activity中将插件Activity强制转换为定义的接口并调用插件activity的onCreate实现启动接口代码如下:

package com.example.pluginstand;

import .Activity;
import android.os.Bundle;
import android.view.MotionEvent;

/**

public interface PluginInterface {
    //注入上下文
    public void attach(Activity proxyActivity);
    public void onCreate(Bundle savedInstanceState);
    public void onStart() ;
    public void onResume();
    public void onPause() ;
    public void onStop() ;
    public void onDestroy();
    public void onSaveInstanceState(Bundle outState);
    public boolean onTouchEvent(MotionEvent event) ;
    public void onBackPressed();
}

(3) 我们在依赖项库中新建空壳ProxyActivity用来作为伪装的插件Activity并在onCreate中开启插件Activity的onCreate实现启动:

package com.example.pluginstand;

import .Activity;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.annotation.Nullable;

import com.example.pluginstand.PluginInterface;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 <p>
 * 空壳  这里加载的资源和类都是插件里面的而且生命周期都是插件生命周期都是插件的,所以实质启动了插件的activity
 * <p>
 * 耳机
 */
public class ProxyActivity extends Activity {
    /*
    * 需要加载的插件里的class
    * */
    private String className;
    PluginInterface pluginInterface;
    private String keyString;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        className = getIntent().getStringExtra("className");
        keyString=getIntent().getStringExtra("keys");
        try {
            Class activityClass = getClassLoader().loadClass(className);
            Constructor constructor = activityClass.getConstructor(new Class[]{});
            //调用私有的Activity构造函数进行实例化对象。
            Object instance = constructor.newInstance(new Object[]{});
            //这里强行转换Activity为定义的接口,因为我们已经在插件apk里面实现了这个接口。
            pluginInterface = (PluginInterface) instance;
            pluginInterface.attach(this);
            //传入一些信息。这里如果需要住Activity传入插件activity数据可以通过Bundle传递哦
            Bundle bundle = new Bundle();
            bundle.putString("keys",keyString);
            //这里调用开启插件Activity
            pluginInterface.onCreate(bundle);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onStart() {
        //实质启动了插件activity的方法
        pluginInterface.onStart();
        super.onStart();
    }

    @Override
    protected void onPause() {
        //实质启动了插件activity的方法
        pluginInterface.onPause();
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        //实质启动了插件activity的方法
        pluginInterface.onDestroy();
        super.onDestroy();
    }

    /*
    * 加载Activity
    *
    * class  滴滴出行和
    *
    * 资源 淘票票
    * */
    //加载小黄车的资源和类
    @Override
    public Resources getResources() {
        //让这个空壳Activity中加载了插件apk的资源
        return PluginManager.getInstance().getResources();
    }
    //加载弟弟和淘票票的资源和类
    @Override
    public ClassLoader getClassLoader() {
        //让这个空壳Activity中加载了插件apk的类从而实现开启的是真正意义上的插件ac。
        return PluginManager.getInstance().getClassLoader();
    }
}

(4) 我们需要在插件Modle的Activity中去实现接口,很重要的是通过 public void attach(Activity proxyActivity);注入上下文,并使用主项目中的acitivy的方法,才能和我们项目Activity进行交互,所以为了方便,我们建立BaseActivity替换所有插件中的Activity所需方法:

package com.example.plugina;

import android.annotation.SuppressLint;
import .Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import .ApplicationInfo;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;

import com.example.pluginstand.PluginInterface;

/**

public class BaseActivity extends Activity implements PluginInterface{
    protected Activity that;
    @SuppressLint("NewApi")
    @Nullable
    @Override
    public Intent getParentActivityIntent() {
        return that.getParentActivityIntent();
    }

    @Override
    public void attach(Activity proxyActivity) {
        that = proxyActivity;
    }

    @Override
    public Context getBaseContext() {
        return that.getBaseContext();
    }

    @Override
    public Context getApplicationContext() {
        return that.getApplicationContext();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {

    }

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

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        that.setContentView(view, params);
    }

    @Override
    public void setContentView(int layoutResID) {
        that.setContentView(layoutResID);
    }

    @Override
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        that.addContentView(view, params);
    }
    @Override
    public <T extends View> T findViewById(int id) {
        return that.findViewById(id);
    }

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

    @Override
    public ClassLoader getClassLoader() {
        return that.getClassLoader();
    }

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

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

    @NonNull
    @Override
    public MenuInflater getMenuInflater() {
        return that.getMenuInflater();
    }

    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        return that.getSharedPreferences(name, mode);
    }

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

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

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

    @Override
    public Object getSystemService(@NonNull String name) {
        return that.getSystemService(name);
    }

    @Override
    public void finish() {
        that.finish();
    }

    @Override
    public void onBackPressed() {
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        return that.onCreateOptionsMenu(menu);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    }

    @Override
    public void onStart() {
    }

    @Override
    public void onResume() {
    }

    @Override
    public void onPause() {
    }

    @Override
    public void onStop() {
    }

    @Override
    public void onDestroy() {
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {

    }

    @Override
    protected void onRestart() {

    }

    @Override
    public void onSaveInstanceState(Bundle outState) {

    }
}

(5) 最后我们去主Activity中进行加载ap并调用(这里很重要的是手机6.0之后权限问题,如果手机是6.0之前的可以在清单文件中注册就行,否则动态申请权限,我一个手机是root权限的不需要动态也行)

package com.example.ls.pluginstudydemon;

import .Activity;
import android.content.Intent;
import .PackageManager;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import .AppCompatActivity;
import android.os.Bundle;
import android.view.View;

import com.example.pluginstand.PluginManager;
import com.example.pluginstand.ProxyActivity;

import java.io.File;

public class MainActivity extends AppCompatActivity {
    //6.0之后动态必须权限,这里估计很多人都怕坑了。很多博客也没写明白导致很多人最后运行失败。
    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static String[] PERMISSIONS_STORAGE = {
            "android.permission.READ_EXTERNAL_STORAGE",
            "android.permission.WRITE_EXTERNAL_STORAGE"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //这个注入上下文别忘记了
        PluginManager.getInstance().setContext(this);
        //权限动态申请。对于8.0之后没测过哦
        verifyStoragePermissions(this);
    }
    public static void verifyStoragePermissions(Activity activity) {

        try {
            //检测是否有写的权限
            int permission = ActivityCompat.checkSelfPermission(activity,
                    "android.permission.WRITE_EXTERNAL_STORAGE");
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 没有写的权限,去申请写的权限,会弹出对话框
                ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void jumpTaopiaoPiao(View view) {
        File file = new File(Environment.getExternalStorageDirectory(), "taopiaopiao-debug.apk");
        PluginManager.getInstance().loadPath(file.getAbsolutePath());
        //跳转到空壳activity启动插件apk
        Intent intent = new Intent(this,ProxyActivity.class);
        intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);
        intent.putExtra("keys","我是主Activity里面的数据哦!");
        startActivity(intent);
    }

    public void jumpDidi(View view) {
        File file = new File(Environment.getExternalStorageDirectory(), "didichuxing-debug.apk");
        PluginManager.getInstance().loadPath(file.getAbsolutePath());
        //跳转apk
        Intent intent = new Intent(this,ProxyActivity.class);
        intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);
        startActivity(intent);
    }
}

(6) 我们去主项目的清单文件中注册ProxActivity并且加读写权限,然后运行两个插件apk

运行didichuxing我们会在目录下找到apk文件,这里会奔溃因为上下文原因不用管,然后粘贴复制这两个apk到我们的手机目录下:

插件时架构 插件架构模式的案例_插件化架构_15

插件时架构 插件架构模式的案例_插件化Demon_16

插件时架构 插件架构模式的案例_插件化教学_17

插件时架构 插件架构模式的案例_插件化教学_18

插件时架构 插件架构模式的案例_插件时架构_19

接下来删除手机桌面的插件app然后运行主项目:如下所示:

插件时架构 插件架构模式的案例_插件化Demon_20

希望帮助到你,下一篇我将直接使用360插件化架构,隔绝插件和项目之间的任何联系,你只需要写你的插件化Demon和项目,四大组件随便调用,我感觉目前来说使用起来最爽的哦。