插件化从实现原理案例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

这里写图片描述

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


这里写图片描述
这里写图片描述

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

这里写图片描述
这里写图片描述
这里写图片描述

最后看看项目目录:

这里写图片描述

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

这里写图片描述
这里写图片描述
这里写图片描述

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

这里写图片描述

这里写图片描述
这里写图片描述

(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;

  /**
  * 作者:王飞
  * 邮箱:1276998208@qq.com
  * 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;

     /**
      * 作者:王飞
      * 邮箱:1276998208@qq.com
      * 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;

/**
 * 作者:王飞
 * 邮箱:1276998208@qq.com
 * create on 2018/6/14 16:16
 * <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 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;

/**
 * 作者:王飞
 * 邮箱:1276998208@qq.com
 * 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 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 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到我们的手机目录下:

这里写图片描述

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

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

这里写图片描述

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容