插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)

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

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

插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第1张图片
这里写图片描述

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


插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第2张图片
这里写图片描述
插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第3张图片
这里写图片描述

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

插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第4张图片
这里写图片描述
插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第5张图片
这里写图片描述
插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第6张图片
这里写图片描述

最后看看项目目录:

插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第7张图片
这里写图片描述

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

插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第8张图片
这里写图片描述
插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第9张图片
这里写图片描述
插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第10张图片
这里写图片描述

三,依赖完成我们来写代码部分:
首先我们去主项目和Modle中写好布局:如下:

插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第11张图片
这里写图片描述

插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第12张图片
这里写图片描述
插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第13张图片
这里写图片描述

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

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.InvocationTargetException;
import java.lang.reflect.Method;

 import dalvik.system.DexClassLoader;

  /**
  * 作者:王飞
  * 邮箱:[email protected]
  * create on 2018/6/14 16:23
  */
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 android.app.Activity;
    import android.os.Bundle;
    import android.view.MotionEvent;

     /**
      * 作者:王飞
      * 邮箱:[email protected]
      * create on 2018/6/14 15:43
     */
 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 android.app.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;

/**
 * 作者:王飞
 * 邮箱:[email protected]
 * create on 2018/6/14 16:16
 * 

* 空壳 这里加载的资源和类都是插件里面的而且生命周期都是 插件生命周期都是插件的,所以实质启动了插件的activity *

* 耳机 */ 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 android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.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;

/**
 * 作者:王飞
 * 邮箱:[email protected]
 * create on 2018/6/14 15:51
*/
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 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 android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.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到我们的手机目录下:

插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第14张图片
这里写图片描述

插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第15张图片
这里写图片描述
插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第16张图片
这里写图片描述
插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第17张图片
这里写图片描述
插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug)_第18张图片
这里写图片描述

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

这里写图片描述

希望帮助到你,下一篇我将直接使用360插件化架构,隔绝插件和项目之间的任何联系,你只需要写你的插件化Demon和项目,四大组件随便调用,我感觉目前来说使用起来最爽的哦。如果帮助到你请你点赞分享,或者不明白的地方可以加我qq1276998208我会对你负责的0o0。

我的博客地址:https://blog.csdn.net/m0_37667770/article/details/80716073#comments
项目地址:https://github.com/luhenchang/PluginStudyDemon.git

你可能感兴趣的:(插件化从实现原理案例Demon,到大型企业插件化框架(360企业级别DroidPlug))