全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

Android系统实现DroidPlugin插件机制

360手机助手使用的 DroidPlugin,它是360手机助手团队在Android系统上实现了一种插件机制。它可以在无需安装、修改的情况下运行APK文件,此机制对改进大型APP的架构,实现多团队协作开发具有一定的好处。

它是一种新的插件机制,一种免安装的运行机制

github地址:https://github.com/DroidPluginTeam/DroidPlugin

参考博客:http://blog.csdn.net/hejjunlin/article/details/52124397

DroidPlugin的的基本原理:

  共享进程:为android提供一个进程运行多个apk的机制,通过API欺骗机制瞒过系统

  占坑:通过预先占坑的方式实现不用在manifest注册,通过一带多的方式实现服务管理

  Hook机制:动态代理实现函数hook,Binder代理绕过部分系统服务限制,IO重定向(先获取原始Object-->Read,然后动态代理Hook Object后-->Write回去,达到瞒天过海的目的)

public abstract class Hook {

 private boolean mEnable = false;//能否hook

 protected Context mHostContext;//宿主context,外部传入
 protected BaseHookHandle mHookHandles;

 public void setEnable(boolean enable, boolean reInstallHook) {
  this.mEnable = enable;
 }

 public final void setEnable(boolean enable) {
  setEnable(enable, false);
 }

 public boolean isEnable() {
  return mEnable;
 }


 protected Hook(Context hostContext) {
  mHostContext = hostContext;
  mHookHandles = createHookHandle();
 }

 protected abstract BaseHookHandle createHookHandle();//用于子类创建Hook机制


 protected abstract void onInstall(ClassLoader classLoader) throws Throwable;//插件安装

 protected void onUnInstall(ClassLoader classLoader) throws Throwable {//插件卸载

 }
}

public class HookedMethodHandler {//Hook方法

 private static final String TAG = HookedMethodHandler.class.getSimpleName();
 protected final Context mHostContext;
 /**
  * 调用方法的时候会到AppOpsService进行判断uid(宿主apk)和插件的包名是否匹配,此处是不匹配的
  * 此时就可以经过转换欺骗系统让程序认为是宿主apk调过来的(这样的前提就需要宿主把所有的权限都申请了)
  * 因为系统只会去检测宿主apk
  * **/
 private Object mFakedResult = null;//用于欺骗系统
 private boolean mUseFakedResult = false;

 public HookedMethodHandler(Context hostContext) {
  this.mHostContext = hostContext;
 }


 public synchronized Object doHookInner(Object receiver, Method method, Object[] args) throws Throwable {
  long b = System.currentTimeMillis();
  try {
   mUseFakedResult = false;
   mFakedResult = null;
   boolean suc = beforeInvoke(receiver, method, args);
   Object invokeResult = null;
   if (!suc) {//false执行原始方法
    invokeResult = method.invoke(receiver, args);
   }
   afterInvoke(receiver, method, args, invokeResult);
   if (mUseFakedResult) {//true返回欺骗结果,false返回正常的调用方法
    return mFakedResult;
   } else {
    return invokeResult;
   }
  } finally {
   long time = System.currentTimeMillis() - b;
   if (time > 5) {
    Log.i(TAG, "doHookInner method(%s.%s) cost %s ms", method.getDeclaringClass().getName(), method.getName(), time);
   }
  }
 }

 public void setFakedResult(Object fakedResult) {
  this.mFakedResult = fakedResult;
  mUseFakedResult = true;
 }

 /**
  * 在某个方法被调用之前执行,如果返回true,则不执行原始的方法,否则执行原始方法
  */
 protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
  return false;
 }

 protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable {
 }

 public boolean isFakedResult() {
  return mUseFakedResult;
 }

 public Object getFakedResult() {
  return mFakedResult;
 }
}

abstract class BinderHook extends Hook implements InvocationHandler {

 private Object mOldObj;

 public BinderHook(Context hostContext) {
  super(hostContext);
 }

 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
   if (!isEnable()) {//如果不能Hook,执行原方法
    return method.invoke(mOldObj, args);
   }
   HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method);
   if (hookedMethodHandler != null) {
    return hookedMethodHandler.doHookInner(mOldObj, method, args);
   } else {
    return method.invoke(mOldObj, args);
   }
  } catch (InvocationTargetException e) {
   Throwable cause = e.getTargetException();
   if (cause != null && MyProxy.isMethodDeclaredThrowable(method, cause)) {
    throw cause;
   } else if (cause != null) {
    RuntimeException runtimeException = !TextUtils.isEmpty(cause.getMessage()) ? new RuntimeException(cause.getMessage()) : new RuntimeException();
    runtimeException.initCause(cause);
    throw runtimeException;
   } else {
    RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException();
    runtimeException.initCause(e);
    throw runtimeException;
   }
  } catch (IllegalArgumentException e) {
   try {
    StringBuilder sb = new StringBuilder();
    sb.append(" DROIDPLUGIN{");
    if (method != null) {
     sb.append("method[").append(method.toString()).append("]");
    } else {
     sb.append("method[").append("NULL").append("]");
    }
    if (args != null) {
     sb.append("args[").append(Arrays.toString(args)).append("]");
    } else {
     sb.append("args[").append("NULL").append("]");
    }
    sb.append("}");

    String message = e.getMessage() + sb.toString();
    throw new IllegalArgumentException(message, e);
   } catch (Throwable e1) {
    throw e;
   }
  } catch (Throwable e) {
   if (MyProxy.isMethodDeclaredThrowable(method, e)) {
    throw e;
   } else {
    RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException();
    runtimeException.initCause(e);
    throw runtimeException;
   }
  }
 }

 abstract Object getOldObj() throws Exception;

 void setOldObj(Object mOldObj) {
  this.mOldObj = mOldObj;
 }

 public abstract String getServiceName();//具体Hook哪一个service

 /**
  * 先调用ServiceManagerCacheBinderHook的onInstall()方法更新一下service cache
  * 然后生成一个新的代理对象放到mProxiedObjCache里。这样下次不管是从cache里取,还是直接通过binder调用,就都会返回我们的代理对象。
  * **/
 @Override
 protected void onInstall(ClassLoader classLoader) throws Throwable {
  new ServiceManagerCacheBinderHook(mHostContext, getServiceName()).onInstall(classLoader);
  mOldObj = getOldObj();
  Class<?> clazz = mOldObj.getClass();//得到class
  List<Class<?>> interfaces = Utils.getAllInterfaces(clazz);
  Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
  //用原始对象的classloader传入动态代理,得到代理对象
  Object proxiedObj = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, this);
  MyServiceManager.addProxiedObj(getServiceName(), proxiedObj);
 }
}

结论就是读取插件apk,和宿主的uid对比,然后进行包替换,在利用binder代理Hook,启动插件,这概括很是大概,不过涉及太复杂

然后是使用了,结束和使用都很多资料,很详细,不过自己研究了一翻记录下心得,也能加深理解和印象

public class MainActivity extends AppCompatActivity {

 private String filepath = null, packageName = "cn.liuzhen.plugin";
 private TextView tv_val;
 private Context context;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  context = MainActivity.this;
  tv_val = (TextView)findViewById(R.id.tv_val);
  filepath = Environment.getExternalStorageDirectory().getAbsolutePath().concat("/test.apk");
 }

 public void click(View view) {
  if (filepath == null){
   Toast.makeText(context,"filepath is null",Toast.LENGTH_SHORT).show();
   return;
  }
  String result = null;
  int code = -1;
  try {
   switch (view.getId()) {
    case R.id.btn_install:
     code = PluginManager.getInstance().installPackage(filepath, PackageManagerCompat.INSTALL_REPLACE_EXISTING);
     result = "install";
     switch (code) {
      case PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION:
       result = "安装失败,文件请求的权限太多";
       break;
      case PackageManagerCompat.INSTALL_FAILED_NOT_SUPPORT_ABI:
       result = "宿主不支持插件的abi环境,可能宿主运行时为64位,但插件只支持32位";
       break;
      case PackageManagerCompat.INSTALL_SUCCEEDED:
       result = "安装完成";
       break;
     }
     break;
    case R.id.btn_del:
     PluginManager.getInstance().deletePackage(packageName, 0);
     result = "del";
     break;
    case R.id.btn_open:
     PackageManager pm = getPackageManager();
     Intent intent = pm.getLaunchIntentForPackage("cn.liuzhen.plugin");
     if (intent == null){
      result = "intent is null";
     }else
      startActivity(intent);
     break;
   }

  } catch (RemoteException e) {
   result = "安装失败 "+e.getMessage();
  }
  tv_val.setText(result);
 }

}

运行程序成功,然后把运行的apk复制一份,我上面的名称是写死的,test.apk,然后放在根目录,点击安装,显示成功后在点击打开,就能见到跳转到插件界面了,插件化通了。

接下来就是看自己怎么设计和开发了,什么东西也不能随便使用,得好好考虑,个人觉得插件化不宜大范围使用,适合小菜单的集成,毕竟都是反射的,而且还得考虑好安全问题。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# DroidPlugin  # 插件  # 解析离线安装Eclipse的Android ADT开发插件的具体操作(图文)  # Android实现QQ抢红包插件  # Android中微信抢红包插件原理解析及开发思路  # 为Android Studio编写自定义Gradle插件的教程  # 分享Android微信红包插件  # Android抢红包插件实现原理浅析  # Android上使用jspf插件框架的方法  # Android桌面插件App Widget用法分析  # APP添加CNZZ统计插件教程 Android版添加phonegap  # Android微信自动抢红包插件优化和实现  # 它是  # 都是  # 放在  # 太多  # 就能  # 多个  # 子类  # 瞒天过海  # 也能  # 是从  # 有一定  # 服务管理  # 还得  # 它可以  # 不支持  # 什么东西  # 会去  # 提供一个  # 用在  # 大家多多 


相关文章: 实例解析Array和String方法  如何通过wdcp面板快速创建网站?  如何高效完成独享虚拟主机建站?  定制建站流程解析:需求评估与SEO优化功能开发指南  如何通过建站之星自助学习解决操作问题?  手机网站制作与建设方案,手机网站如何建设?  无锡制作网站公司有哪些,无锡优八网络科技有限公司介绍?  免费制作小说封面的网站有哪些,怎么接网站批量的封面单?  建站之星安装后如何自定义网站颜色与字体?  网站制作员失业,怎样查看自己网站的注册者?  如何通过VPS搭建网站快速盈利?  盐城做公司网站,江苏电子版退休证办理流程?  济南专业网站制作公司,济南信息工程学校怎么样?  如何在IIS7上新建站点并设置安全权限?  道歉网站制作流程,世纪佳缘致歉小吴事件,相亲网站身份信息伪造该如何稽查?  购物网站制作公司有哪些,哪个购物网站比较好?  济南企业网站制作公司,济南社保单位网上缴费步骤?  可靠的网站设计制作软件,做网站设计需要什么样的电脑配置?  浙江网站制作公司有哪些,浙江栢塑信息技术有限公司定制网站做的怎么样?  如何制作公司的网站链接,公司想做一个网站,一般需要花多少钱?  如何高效配置IIS服务器搭建网站?  新网站制作渠道有哪些,跪求一个无线渠道比较强的小说网站,我要发表小说?  网站制作公司排行榜,抖音怎样做个人官方网站  建站之星与建站宝盒如何选择最佳方案?  如何在服务器上三步完成建站并提升流量?  北京网站制作费用多少,建立一个公司网站的费用.有哪些部分,分别要多少钱?  已有域名如何快速搭建专属网站?  电视网站制作tvbox接口,云海电视怎样自定义添加电视源?  如何快速搭建个人网站并优化SEO?  如何制作算命网站,怎么注册算命网站?  安徽网站建设与外贸建站服务专业定制方案  如何在阿里云完成域名注册与建站?  制作无缝贴图网站有哪些,3dmax无缝贴图怎么调?  如何在云服务器上快速搭建个人网站?  网站网页制作专业公司,怎样制作自己的网页?  seo网站制作优化,网站SEO优化步骤有哪些?  建站之星各版本价格是多少?  如何零基础在云服务器搭建WordPress站点?  如何挑选高效建站主机与优质域名?  重庆网站制作公司哪家好,重庆中考招生办官方网站?  较简单的网站制作软件有哪些,手机版网页制作用什么软件?  建站之星伪静态规则如何正确配置?  香港服务器如何优化才能显著提升网站加载速度?  如何在Golang中引入测试模块_Golang测试包导入与使用实践  免费制作统计图的网站有哪些,如何看待现如今年轻人买房难的情况?  如何使用Golang安装API文档生成工具_快速生成接口文档  如何快速登录WAP自助建站平台?  已有域名和空间,如何快速搭建网站?  如何确保西部建站助手FTP传输的安全性?  制作网站建设的公司有哪些,网站建设比较好的公司都有哪些? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。