应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

如何将Android程序做成插件化的形式?详解插件化实现原理

  作者:ZYLAB文章概览发展历史常用名词使用Gradle简化插件开发流程类加载器:ClassLoader插件化实现难点四大组件的插件化实现一、发展历史2012年:AndroidDynamicLoader给予Fragment实现了插件化框架,可以动态加载插件中的Fragment实现页面的切换。2013年:阿里技术沙龙上,伯奎做了Atlas插件化框架的分享,说明那时候阿里已经在做插件化的运用和开发了。2014年:任玉刚开源了dynamicloadapk,通过代理分发的方式实现了动态化2015年:张勇发布了DroidPlugin,使用hook系统方式实现插件化。2017年:阿里推出Atlas2019年:腾讯推出了Shadow,号称是零反射,并且框架自身也可实现动态化,看了代码以后发现,其实本质上还是使用了代理分发生命周期实现四大组件动态化,然后抽象接口来实现框架的动态化。后面有机会可以对其做一下分析。
  从2012至今,可以说插件化技术基本成型了,主要是代理和hook系统两种方式二、常用名词
  在插件化中有一些专有名词,如果是第一次接触可能不太了解,这里解释一下。宿主:负责加载插件的apk,一般来说就是已经安装的应用本身。StubActivity:宿主中的占位Activity,注册在宿主Manifest文件中,负责加载插件Activity。PluginActivity:插件Activity,在插件apk中,没有注册在Manifest文件中,需要StubActivity来加载。三、使用gradle简化插件开发流程
  在学习和开发插件化的时候,我们需要动态去加载插件apk,所以开发过程中一般需要有两个apk,一个是宿主apk,一个是插件apk,对应的就需要有宿主项目和插件项目。
  在CommonTec这里创建了app作为宿主项目,plugin为插件项目。为了方便,我们直接把生成的插件apk放到宿主apk中的assets中,apk启动时直接放到内部存储空间中方便加载。这样的项目结构,我们调试问题时的流程就是下面这样:修改插件项目编译生成插件apk拷贝插件apk到宿主assets修改宿主项目编译生成宿主apk安装宿主apk验证问题如果每次我们修改一个很小的问题,都经历这么长的流程,那么耐心很快就耗尽了。最好是可以直接编译宿主apk的时候自动打包插件apk并拷贝到宿主assets目录下,这样我们不管修改什么,都直接编译宿主项目就好了。如何实现呢?还记得我们之前讲解过的gradle系列么?现在就是学以致用的时候了。首先在plugin项目的build。gradle添加下面的代码:project。afterEvaluate{project。tasks。each{if(it。nameassembleDebug){it。doLast{copy{fromnewFile(project。getBuildDir(),outputsapkdebugplugindebug。apk)。absolutePathintonewFile(project。getRootProject()。getProjectDir(),appsrcmainassets)renameplugindebug。apk,plugin。apk}}}}}
  这段代码是在afterEvaluate的时候,遍历项目的task,找到打包task也就是assembleDebug,然后在打包之后,把生成的apk拷贝到宿主项目的assets目录下,并且重命名为plugin。apk。然后在app项目的build。gradle添加下面的代码:project。afterEvaluate{project。tasks。each{if(it。namemergeDebugAssets){it。dependsOn:plugin:assembleDebug}}}
  找到宿主打包的mergeDebugAssets任务,依赖插件项目的打包,这样每次编译宿主项目的时候,会先编译插件项目,然后拷贝插件apk到宿主apk的assets目录下,以后每次修改,只要编译宿主项目就可以了。四、ClassLoader
  ClassLoader是插件化中必须要掌握的,因为插件是未安装的apk,系统不会处理其中的类,所以需要我们自己来处理。
  4。1java中的ClassLoader
  BootstrapClassLoader负责加载JVM运行时的核心类,比如JAVAHOMElibrt。jar等等
  ExtensionClassLoader负责加载JVM的扩展类,比如JAVAHOMElibext下面的jar包
  AppClassLoader负责加载classpath里的jar包和目录
  4。2android中的ClassLoader
  在这里,我们统称dex文件,包含dex的apk文件以及jar文件为dex文件PathClassLoader用来加载系统类和应用程序类,可以加载已经安装的apk目录下的dex文件
  DexClassLoader用来加载dex文件,可以从存储空间加载dex文件。
  我们在插件化中一般使用的是DexClassLoader。
  4。3双亲委派机制
  每一个ClassLoader中都有一个parent对象,代表的是父类加载器,在加载一个类的时候,会先使用父类加载器去加载,如果在父类加载器中没有找到,自己再进行加载,如果parent为空,那么就用系统类加载器来加载。通过这样的机制可以保证系统类都是由系统类加载器加载的。下面是ClassLoader的loadClass方法的具体实现。protectedClasslt;?loadClass(Stringname,booleanresolve)throwsClassNotFoundException{First,checkiftheclasshasalreadybeenloadedClasslt;?cfindLoadedClass(name);if(cnull){try{if(parent!null){先从父类加载器中进行加载cparent。loadClass(name,false);}else{cfindBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){ClassNotFoundExceptionthrownifclassnotfoundfromthenonnullparentclassloader}if(cnull){没有找到,再自己加载cfindClass(name);}}returnc;}
  4。4如何加载插件中的类
  要加载插件中的类,我们首先要创建一个DexClassLoader,先看下DexClassLoader的构造函数需要那些参数。publicclassDexClassLoaderextendsBaseDexClassLoader{publicDexClassLoader(StringdexPath,StringoptimizedDirectory,StringlibrarySearchPath,ClassLoaderparent){。。。}}
  构造函数需要四个参数:dexPath是需要加载的dexapkjar文件路径optimizedDirectory是dex优化后存放的位置,在ART上,会执行oat对dex进行优化,生成机器码,这里就是存放优化后的odex文件的位置librarySearchPath是native依赖的位置parent就是父类加载器,默认会先从parent加载对应的类
  创建出DexClassLaoder实例以后,只要调用其loadClass(className)方法就可以加载插件中的类了。具体的实现在下面:从assets中拿出插件apk放到内部存储空间privatefunextractPlugin(){varinputStreamassets。open(plugin。apk)File(filesDir。absolutePath,plugin。apk)。writeBytes(inputStream。readBytes())}privatefuninit(){extractPlugin()pluginPathFile(filesDir。absolutePath,plugin。apk)。absolutePathnativeLibDirFile(filesDir,pluginlib)。absolutePathdexOutPathFile(filesDir,dexout)。absolutePath生成DexClassLoader用来加载插件类pluginClassLoaderDexClassLoader(pluginPath,dexOutPath,nativeLibDir,this::class。java。classLoader)}五、插件化需要解决的难点
  插件化,就是从插件中加载我们想要的类并运行,如果这个类是一个普通类,那么使用上面说到的DexClassLoader就可以直接加载了,如果这个类是特殊的类,比如说Activity等四大组件,那么就需要一些特殊的处理,因为四大组件是需要和系统进行交互的。插件化中,四大组件需要解决的难点如下:Activity生命周期如何调用如何使用插件中的资源Service生命周期如何调用BroadcastReceiver静态广播和动态广播的注册ContentProvider如何注册插件Provider到系统六、Activity的插件化实现
  6。1难点分析
  我们之前说到Activity插件化的难点,我们先来理顺一下为什么会有这两个问题。因为插件是动态加载的,所以插件的四大组件不可能注册到宿主的Manifest文件中,而没有在Manifest中注册的四大组件是不能和系统直接进行交互的。可能有些同学会问,那为什么不能直接把插件的Activity注册到宿主Manifest里呢?这样是可以,不过就失去了插件化的动态特性,如果每次插件中新增Activity都要修改宿主Manifest并且重新打包,那就和直接写在宿主中没什么区别了。我们再来说一下为什么没有注册的Activity不能和系统交互这里的不能直接交互的含义有两个系统会检测Activity是否注册如果我们启动一个没有在Manifest中注册的Activity,会发现报如下error:android。content。ActivityNotFoundException:Unabletofindexplicitactivityclass{com。zy。commonteccom。zy。plugin。PluginActivity};haveyoudeclaredthisactivityinyourAndroidManifest。xml?
  这个log在Instrumentation的checkStartActivityResult方法中可以看到:publicclassInstrumentation{publicstaticvoidcheckStartActivityResult(intres,Objectintent){if(!ActivityManager。isStartResultFatalError(res)){return;}switch(res){caseActivityManager。STARTINTENTNOTRESOLVED:caseActivityManager。STARTCLASSNOTFOUND:if(intentinstanceofIntent((Intent)intent)。getComponent()!null)thrownewActivityNotFoundException(Unabletofindexplicitactivityclass((Intent)intent)。getComponent()。toShortString();haveyoudeclaredthisactivityinyourAndroidManifest。xml?);thrownewActivityNotFoundException(NoActivityfoundtohandleintent);。。。}}}Activity的生命周期无法被调用其实一个Activity主要的工作,都是在其生命周期方法中调用了,既然上一步系统检测了Manifest注册文件,启动Activity被拒绝,那么其生命周期方法也肯定不会被调用了。从而插件Activity也就不能正常运行了。
  其实上面两个问题,最终都指向同一个难点,那就是插件中的Activity的生命周期如何被调用。解决问题之前我们先看一下正常系统是如何启动一个Activity的。这里对Activity的启动流程进行一些简单的介绍,具体的流程代码就不分析了,因为分析的话大概又能写一篇文章了,而且其实关于Activity的启动过程也有不少文章有分析了。这里放一张简图说明一下:
  整个调用路径如下:Activity。startActivityInstrumentation。execStartActivityBinderAMS。startActivityActivityStarter。startActivityMayWaitstartActivityLockedstartActivityUnCheckedActivityStackSupervisor。resumeFocusedStackTopActivityLockedActivityStatk。resumeTopAcitivityUncheckLockedresumeTopActivityInnerLockedActivityStackSupervisor。startSpecificActivityLockedrealStartActivityLockedBinderApplictionThread。scheduleLauchActivityHActivityThread。scheduleLauchActivityhandleLaunchActivityperformLaunchActivityInstrumentation。newActivity创建ActivitycallActivityOnCreate一系列生命周期
  其实我们可以把AMS理解为一个公司的背后大Boss,Activity相当于小职员,没有权限直接和大Boss说话,想做什么事情都必须经过秘书向上汇报,然后秘书再把大BossAMS的命令传达下来。而且大Boss那里有所有职员的名单,如果想要混入非法职员时不可能的。而我们想让没有在大Boss那里注册的编外人员执行任务,只有两种方法,一种是正式职员领取任务,再分发给编外人员,另一种就是欺骗Boss,让Boss以为这个职员是已经注册的。
  对应到实际的解决方法就是:我们手动去调用插件Activity的生命周期欺骗系统,让系统以为Activity是注册在Manifest中的
  说完生命周期的问题,再来看一下资源的问题在Activity中,基本上都会展示界面,而展示界面基本上都要用到资源。在Activity中,有一个mResources变量,是Resources类型。这个变量可以理解为代表了整个apk的资源。
  在宿主中调用的Activity,mResources自然代表了宿主的资源,所以需要我们对插件的资源进行特殊的处理。我们先看一下如何生成代表插件资源的Resources类。首先要生成一个AssetManager实例,然后通过其addAssetPath方法添加插件的路径,这样AssetManager中就包含了插件的资源。然后通过Resources构造函数生成插件资源。具体代码如下:privatefunhandleResources(){try{首先通过反射生成AssetManager实例pluginAssetManagerAssetManager::class。java。newInstance()然后调用其addAssetPath把插件的路径添加进去。valaddAssetPathMethodpluginAssetManager?。javaClass?。getMethod(addAssetPath,String::class。java)addAssetPathMethod?。invoke(pluginAssetManager,pluginPath)}catch(e:Exception){}调用Resources构造函数生成实例pluginResourcesResources(pluginAssetManager,super。getResources()。displayMetrics,super。getResources()。configuration)}
  前期准备的知识点差不多介绍完了,我们接着就看看具体的实现方法。
  6。2手动调用Activity生命周期
  手动调用生命周期原理如下图:
  我们手动调用插件Activity生命周期时,需要在正确的时机去调用,如何在正确的时机调用呢?那就是启动一个真正的Activity,我们俗称占坑Activity(StubActivity),然后在StubActivity的生命周期里调用插件Activity对应的生命周期,这样就间接的启动了插件Activity。在StubActivity中调用插件Activity生命周期的方法有两种,一种是直接反射其生命周期方法,粗暴简单,唯一的缺点就是反射的效率问题。另外一种方式就是生成一个接口,接口里对应的是生命周期方法,让插件Activity实现这个接口,在StubActivity里就能直接调用接口方法了,从而避免了反射的效率低下问题。
  具体的代码实现在CommonTec项目里可以找到,这里贴一下主要的实现(这里的实现和CommonTec里的可能会有些区别,CommonTec里有些代码做了一些封装,这里主要做原理的解释)。
  6。2。1通过反射调用Activity生命周期
  具体的实现见反射调用生命周期,下面列出了重点代码。classStubReflectActivity:Activity(){protectedvaractivityClassLoader:ClassLoader?nullprotectedvaractivityNameprivatevarpluginPathprivatevarnativeLibDir:String?nullprivatevardexOutPath:String?nulloverridefunonCreate(savedInstanceState:Bundle?){super。onCreate(savedInstanceState)nativeLibDirFile(filesDir,pluginlib)。absolutePathdexOutPathFile(filesDir,dexout)。absolutePathpluginPathintent。getStringExtra(pluginPath)activityNameintent。getStringExtra(activityName)创建插件ClassLoaderactivityClassLoaderDexClassLoader(pluginPath,dexOutPath,nativeLibDir,this::class。java。classLoader)}以onCreate方法为例,其他onStart等生命周期方法类似funonCreate(savedInstanceState:Bundle?){获取插件Activity的onCreate方法并调用getMethod(onCreate,Bundle::class。java)?。invoke(activity,savedInstanceState)}fungetMethod(methodName:String,varargparams:Class):Method?{returnactivityClassLoader?。loadClass(activity)?。getMethod(methodName,params)}}
  6。2。2通过接口调用Activity生命周期
  具体的实现见接口调用生命周期,下面列出了重点代码。通过接口调用Activity生命周期的前提是要定义一个接口IPluginActivityinterfaceIPluginActivity{funattach(proxyActivity:Activity)funonCreate(savedInstanceState:Bundle?)funonStart()funonResume()funonPause()funonStop()funonDestroy()}
  然后在插件Activity中实现这个接口openclassBasePluginActivity:Activity(),IPluginActivity{varproxyActivity:Activity?nulloverridefunattach(proxyActivity:Activity){this。proxyActivityproxyActivity}overridefunonCreate(savedInstanceState:Bundle?){if(proxyActivitynull){super。onCreate(savedInstanceState)}}。。。}
  在StubActivity通过接口调用插件Activity生命周期classStubInterfaceActivity:StubBaseActivity(){protectedvaractivityClassLoader:ClassLoader?nullprotectedvaractivityNameprivatevarpluginPathprivatevaractivity:IPluginActivity?nulloverridefunonCreate(savedInstanceState:Bundle?){super。onCreate(savedInstanceState)nativeLibDirFile(filesDir,pluginlib)。absolutePathdexOutPathFile(filesDir,dexout)。absolutePathpluginPathintent。getStringExtra(pluginPath)activityNameintent。getStringExtra(activityName)生成插件ClassLoaderactivityClassLoaderDexClassLoader(pluginPath,dexOutPath,nativeLibDir,this::class。java。classLoader)加载插件Activity类并转化成IPluginActivity接口activityactivityClassLoader?。loadClass(activityName)?。newInstance()asIPluginActivity?activity?。attach(this)通过接口直接调用对应的生命周期方法activity?。onCreate(savedInstanceState)}}
  6。2。3资源处理方式
  proxyresources
  由于手动调用生命周期的方式,会重写大量的Activity生命周期方法,所以我们只要重写getResources方法,返回插件的资源实例就可以了。下面是具体代码。openclassStubBaseActivity:Activity(){protectedvaractivityClassLoader:ClassLoader?nullprotectedvaractivityNameprivatevarpluginPathprivatevarpluginAssetManager:AssetManager?nullprivatevarpluginResources:Resources?nullprivatevarpluginTheme:Resources。Theme?nullprivatevarnativeLibDir:String?nullprivatevardexOutPath:String?nulloverridefunonCreate(savedInstanceState:Bundle?){super。onCreate(savedInstanceState)nativeLibDirFile(filesDir,pluginlib)。absolutePathdexOutPathFile(filesDir,dexout)。absolutePathpluginPathintent。getStringExtra(pluginPath)activityNameintent。getStringExtra(activityName)activityClassLoaderDexClassLoader(pluginPath,dexOutPath,nativeLibDir,this::class。java。classLoader)handleResources()}overridefungetResources():Resources?{这里返回插件的资源,这样插件Activity中使用的就是插件资源了returnpluginResources?:super。getResources()}overridefungetAssets():AssetManager{returnpluginAssetManager?:super。getAssets()}overridefungetClassLoader():ClassLoader{returnactivityClassLoader?:super。getClassLoader()}privatefunhandleResources(){try{生成AssetManagerpluginAssetManagerAssetManager::class。java。newInstance()添加插件apk路径valaddAssetPathMethodpluginAssetManager?。javaClass?。getMethod(addAssetPath,String::class。java)addAssetPathMethod?。invoke(pluginAssetManager,pluginPath)}catch(e:Exception){}生成插件资源pluginResourcesResources(pluginAssetManager,super。getResources()。displayMetrics,super。getResources()。configuration)}}
  6。3hook系统相关实现的方式欺骗系统,让系统调用生命周期
  6。3。1hookInstrumentation
  上面讲了如何通过手动调用插件Activity的生命周期方法来启动插件Activity,现在来看一下欺骗系统的方法。
  上面简单介绍了Activity的启动流程,我们可以看到,其实Android系统的运行是很巧妙的,AMS是系统服务,应用通过Binder和AMS进行交互,其实和我们日常开发中客户端和服务端交互有些类似,只不过这里使用了Binder做为交互方式,关于Binder,可以简单看看这篇文章。我们暂时只要知道通过Binder应用可以和AMS进行对话就行。这种架构的设计方式,也为我们提供了一些机会。理论上来说,我们只要在启动Activity的消息到达AMS之前把Activity的信息就行修改,然后再消息回来以后再把信息恢复,就可以达到欺骗系统的目的了。
  在这个流程里,有很多hook点可以进行,而且不同的插件化框架对于hook点的选择也不同,这里我们选择hookInstrumentation的方式进行介绍(原因是个人感觉这种方式要简单一点)。简化以后的流程如下:
  Instrumentation相当于Activity的管理者,Activity的创建,以及生命周期的调用都是AMS通知以后通过Instrumentation来调用的。我们上面说到,AMS相当于一个公司的背后大Boss,而Instrumentation相当于秘书,Activity相当于小职员,没有权限直接和大Boss说话,想做什么事情都必须经过秘书向上汇报,然后Instrumentation再把大BossAMS的命令传达下来。而且大Boss那里有所有职员的名单,如果想要混入非法职员时不可能的。不过在整个过程中,由于java的语言特性,大Boss在和秘书Instrumentation对话时,不会管秘书到底是谁,只会确认这个人是不是秘书(是否是Instrumentation类型)。
  我们加载插件中的Activity,相当于让一个不在Boss名单上的编外职员去申请执行任务。在正常情况下,大Boss会检查职员的名单,确认职员的合法性,一定是通过不了的。但是上有政策,下有对策,我们悄悄的替换了秘书,在秘书和Boss汇报时,把职员名字改成大Boss名单中的职员,在Boss安排工作以后,秘书再把名字换回来,让编外职员去执行任务。而我们hook的方式就是替换调Instrumentation,修改Activity类名,达到隐瞒AMS的效果。
  hook方式原理图:
  接下来看看具体的代码实现。具体的实现见hook实现插件化,下面主要讲解重点代码。替换Instrumentation之前,首先我们要实现一个我们自己的Instrumentation,具体实现如下:classAppInstrumentation(varrealContext:Context,varbase:Instrumentation,varpluginContext:PluginContext):Instrumentation(){privatevalKEYCOMPONENTcommonteccomponentcompanionobject{funinject(activity:Activity,pluginContext:PluginContext){hook系统,替换Instrumentation为我们自己的AppInstrumentation,Reflect是从VirtualApp里拷贝的反射工具类,使用很流畅varreflectReflect。on(activity)varactivityThreadreflect。get(mMainThread)varbaseReflect。on(activityThread)。getInstrumentation(mInstrumentation)varappInstrumentationAppInstrumentation(activity,base,pluginContext)Reflect。on(activityThread)。set(mInstrumentation,appInstrumentation)Reflect。on(activity)。set(mInstrumentation,appInstrumentation)}}overridefunnewActivity(cl:ClassLoader,className:String,intent:Intent):Activity?{创建Activity的时候会调用这个方法,在这里需要返回插件Activity的实例valcomponentNameintent。getParcelableExtraComponentName(KEYCOMPONENT)varclazzpluginContext。classLoader。loadClass(componentName。className)intent。componentcomponentNamereturnclazz。newInstance()asActivity?}privatefuninjectIntent(intent:Intent?){varcomponent:ComponentName?nullvaroldComponentintent?。componentif(componentnullcomponent。packageNamerealContext。packageName){替换intent中的类名为占位Activity的类名,这样系统在Manifest中查找的时候就可以找到ActivitycomponentComponentName(com。zy。commontec,com。zy。commontec。activity。hook。HookStubActivity)intent?。componentcomponentintent?。putExtra(KEYCOMPONENT,oldComponent)}}funexecStartActivity(who:Context,contextThread:IBinder,token:IBinder,target:Activity,intent:Intent,requestCode:Int):Instrumentation。ActivityResult?{启动activity的时候会调用这个方法,在这个方法里替换Intent中的ClassName为已经注册的宿主ActivityinjectIntent(intent)returnReflect。on(base)。call(execStartActivity,who,contextThread,token,target,intent,requestCode)。get()}。。。}
  在AppInstrumentation中有两个关键点,execStartActivity和newActivity。execStartActivity是在启动Activity的时候必经的一个过程,这时还没有到达AMS,所以,在这里把Activity替换成宿主中已经注册的StubActivity,这样AMS在检测Activity的时候就认为已经注册过了。newActivity是创建Activity实例,这里要返回真正需要运行的插件Activity,这样后面系统就会基于这个Activity实例来进行对应的生命周期的调用。
  6。3。2hook系统的资源处理方式
  因为我们hook了Instrumentation的实现,还是把Activity生命周期的调用交给了系统,所以我们的资源处理方式和手动调用生命周期不太一样,这里我们生成Resources以后,直接反射替换掉Activity中的mResource变量即可。下面是具体代码。classAppInstrumentation(varrealContext:Context,varbase:Instrumentation,varpluginContext:PluginContext):Instrumentation(){privatefuninjectActivity(activity:Activity?){valintentactivity?。intentvalbaseactivity?。baseContexttry{反射替换mResources资源Reflect。on(base)。set(mResources,pluginContext。resources)Reflect。on(activity)。set(mResources,pluginContext。resources)Reflect。on(activity)。set(mBase,pluginContext)Reflect。on(activity)。set(mApplication,pluginContext。applicationContext)fornativeactivityvalcomponentNameintent!!。getParcelableExtraComponentName(KEYCOMPONENT)valwrapperIntentIntent(intent)wrapperIntent。setClassName(componentName。packageName,componentName。className)activity。intentwrapperIntent}catch(e:Exception){}}overridefuncallActivityOnCreate(activity:Activity?,icicle:Bundle?){在这里进行资源的替换injectActivity(activity)super。callActivityOnCreate(activity,icicle)}}publicclassPluginContextextendsContextWrapper{privatevoidgenerateResources(){try{反射生成AssetManager实例assetManagerAssetManager。class。newInstance();调用addAssetPath添加插件路径MethodmethodassetManager。getClass()。getMethod(addAssetPath,String。class);method。invoke(assetManager,pluginPath);生成Resources实例resourcesnewResources(assetManager,context。getResources()。getDisplayMetrics(),context。getResources()。getConfiguration());}catch(Exceptione){e。printStackTrace();}}}
  讲完上面两种方法,我们这里对比一下这两种方法的优缺点:
  七、Service的插件化实现
  Service比起Activity要简单不少,Service没有太复杂的生命周期需要处理,类似的onCreate或者onStartCommand可以直接通过代理分发。可以直接在宿主app里添加一个占位Service,然后在对应的生命周期里调用插件Service的生命周期方法即可。classStubService:Service(){varserviceName:String?nullvarpluginService:Service?nullcompanionobject{varpluginClassLoader:ClassLoader?nullfunstartService(context:Context,classLoader:ClassLoader,serviceName:String){pluginClassLoaderclassLoadervalintentIntent(context,StubService::class。java)intent。putExtra(serviceName,serviceName)context。startService(intent)}}overridefunonStartCommand(intent:Intent?,flags:Int,startId:Int):Int{valressuper。onStartCommand(intent,flags,startId)serviceNameintent?。getStringExtra(serviceName)pluginServicepluginClassLoader?。loadClass(serviceName)?。newInstance()asServicepluginService?。onCreate()returnpluginService?。onStartCommand(intent,flags,startId)?:res}overridefunonDestroy(){super。onDestroy()pluginService?。onDestroy()}overridefunonBind(intent:Intent?):IBinder?{returnnull}}八、BroadcastReceiver的插件化实现
  动态广播的处理也比较简单,也没有复杂的生命周期,也不需要在Manifest中进行注册,使用的时候直接注册即可。所以只要通过ClassLoader加载插件apk中的广播类然后直接注册就好。classBroadcastUtils{companionobject{privatevalbroadcastMapHashMapString,BroadcastReceiver()funregisterBroadcastReceiver(context:Context,classLoader:ClassLoader,action:String,broadcastName:String){valreceiverclassLoader。loadClass(broadcastName)。newInstance()asBroadcastReceivervalintentFilterIntentFilter(action)context。registerReceiver(receiver,intentFilter)broadcastMap〔action〕receiver}fununregisterBroadcastReceiver(context:Context,action:String){valreceiverbroadcastMap。remove(action)context。unregisterReceiver(receiver)}}}
  静态广播稍微麻烦一点,这里可以解析Manifest文件找到其中静态注册的Broadcast并进行动态注册,这里就不对Manifest进行解析了,知道其原理即可。九、ContentProvider的插件化实现
  其实在日常开发中对于插件化中的ContentProvider使用还是比较少的,这里只介绍一种比较简单的ContentProvider插件化实现方法,就是类似Service,在宿主app中注册占位ContentProvider,然后转发相应的操作到插件ContentProvider中。代码如下:classStubContentProvider:ContentProvider(){privatevarpluginProvider:ContentProvider?nullprivatevaruriMatcher:UriMatcher?UriMatcher(UriMatcher。NOMATCH)overridefuninsert(uri:Uri?,values:ContentValues?):Uri?{loadPluginProvider()returnpluginProvider?。insert(uri,values)}overridefunquery(uri:Uri?,projection:ArrayoutString?,selection:String?,selectionArgs:ArrayoutString?,sortOrder:String?):Cursor?{loadPluginProvider()if(isPlugin1(uri)){returnpluginProvider?。query(uri,projection,selection,selectionArgs,sortOrder)}returnnull}overridefunonCreate():Boolean{uriMatcher?。addURI(com。zy。stubprovider,plugin1,0)uriMatcher?。addURI(com。zy。stubprovider,plugin2,0)returntrue}overridefunupdate(uri:Uri?,values:ContentValues?,selection:String?,selectionArgs:ArrayoutString?):Int{loadPluginProvider()returnpluginProvider?。update(uri,values,selection,selectionArgs)?:0}overridefundelete(uri:Uri?,selection:String?,selectionArgs:ArrayoutString?):Int{loadPluginProvider()returnpluginProvider?。delete(uri,selection,selectionArgs)?:0}overridefungetType(uri:Uri?):String{loadPluginProvider()returnpluginProvider?。getType(uri)?:}privatefunloadPluginProvider(){if(pluginProvidernull){pluginProviderPluginUtils。classLoader?。loadClass(com。zy。plugin。PluginContentProvider)?。newInstance()asContentProvider?}}privatefunisPlugin1(uri:Uri?):Boolean{if(uriMatcher?。match(uri)0){returntrue}returnfalse}}
  这里面需要处理的就是,如何转发对应的Uri到正确的插件Provider中呢,解决方案是在Uri中定义不同的插件路径,比如plugin1的Uri对应就是content:com。zy。stubproviderplugin1,plugin2对应的uri就是content:com。zy。stubproviderplugin2,然后在StubContentProvider中根据对应的plugin分发不同的插件Provider。十、总结
  本文介绍了插件化的相关实现,主要集中在Activity的实现上。重点如下:
  最后推荐大家在学习插件化的同时,也去学习一些四大组件以及Binder的系统实现。
  在这里就分享一份由大佬亲自收录整理的学习PDF架构视频面试文档源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料
  这些都是我现在闲暇时还会反复翻阅的精品资料。里面对近几年的大厂面试高频知识点都有详细的讲解。相信可以有效地帮助大家掌握知识、理解原理,帮助大家在未来取得一份不错的答卷。
  当然,你也可以拿去查漏补缺,提升自身的竞争力。
  真心希望可以帮助到大家,Android路漫漫,共勉!
  如果你有需要的话,只需私信我【进阶】即可获取

如果世界上有什么奇迹,一定是尽力理解某个人并与之同甘共苦疫情自19年年底爆发以来,让我感触最深的是活着的珍贵,这两个月以来最让我揪心的无非就是新疆的疫情了,每天醒来一打开手机就是今日头条推送的全国新增,新疆新增,起初我以为这场战役用……华为Mate50大量现货,8G512G更受青睐,国人还是很给在目前的道路上你会经常看到有华为问界M5,不得不说,华为电动车现在也做得比较成功,不过对于大部人而言,最喜欢的还是华为手机这种产品,电动车毕竟卖得贵,又不是刚需品,不像手机这种……西方国家为啥不像我们一样大力发展电商?看下我们的街道就懂了为什么西方的国家不像我们一样,大力的发展电商呢?尽管这两年全球经济环境不好,但是国外普通老百姓的生活好像始终没有受影响,他们总是可以无忧无虑地出门逛街购物,还过着以往的生活。但……跟着王霏霏学穿搭,印花套装雅致洋气,堪称时尚女性的穿搭模板今天穿什么日常出门最快的单品选择,除了连衣裙最简单不费力的就是套装。无论是款式细节还是颜色都已经设计好,上身可以肆无忌惮展现自己的独特美感。套装大部分的风格已经固定,在选……我的的家乡我的家乡来自农村,它坐落在大山脚下,这里虽没有高楼大厦,也没有车水马龙。但是环境优美,物产丰富,有古朴的村落,热情好客的人民,秀丽的山川风景,特色小吃,悠久历史文化,民间文艺才……加密货币监管风波再起币安被曝已接受美国司法部刑事调查4年四位知情人士称,美国司法部对币安的调查始于2018年,重点针对币安是否违反美国反洗钱法和制裁规定。部分检察官认为已收集到足够证据,可以对币安采取积极行动,并对包括创始人赵长鹏在……感染性角膜炎风险增80倍隐形眼镜竟是眼睛的最佳损友相信不少近视的朋友会因为戴框架眼镜而烦恼:起雾的镜片总是不合时宜地遮挡视野,从鼻梁滑下的镜架宣告着皮肤又出油了,而运动时摇曳的镜腿也像是在昭示眼镜渴望自由的心于是,饱受框……浙南最负盛名的寺院文成安福寺国庆到温州采风进入第三天,按照高华老师的行程安排,我们将入住文成的安福寺,在深山谷地里、暮鼓晨钟中静享安宁。从文成县出发,沿着56省道往龙川方向一直开,差不多四十来分钟就到达安……40。50岁中年人给30岁年轻小伙的忠告1,阅读不论你是初中毕业,还是高中毕业,亦或是研究学位还是博士学位,希望你能养成阅读的好习惯,可以选择自己感兴趣的书籍,每天睡前翻阅几页书籍,你会岁的更好,时间久了,你会……为何很多人买了新能源车,第一件事就要关闭动能回收模式?新能源汽车的不断发展,一些全新技术开始在这些车型上应用,但有个功能一直以来却是出现了不同的看法,那就是动能回收模式。有人觉得这虽然节省了能耗但影响驾驶感受,而另一部分则觉得很好……六种最强养肝食物,每天吃一点肝脏更健康以食养身是最简单最根本的一种养生方法,只有通过摄取食物中的营养物质,我们的身体才能更加健康,才能有利于促进各个器官系统的正常运行。接下来小编给大家介绍六种最强的养肝食物,每天吃……西班牙最美的五个小镇,你去过了几个?西班牙是旅游资源非常丰富的国家,是世界文化遗产数量最多的国家之一,每年慕名而来的游客络绎不绝。这里除了那些著名的旅游城市外,还有很多散落在周边的最美小镇,今天给您介绍其中五个,……
广州荔湾湖畔的老牌酒家,装修古色古香,游客却觉得味道差强人意这里是刘小顺的旅行和生活研究所。广州不仅是我国经济最发达的城市之一,同时也是一座以美食闻名的著名旅游城市,很多游客到广州来旅游都会专门寻找当地的特色美食。前段时间,……通信员一人挑9条枪,步行13天终于找到县大队,路上九死一生作者:风影在艰苦卓绝的抗日战争中,八路军和地方抗日武装、抗日群众与日伪军作斗争,创造了诸多人间奇迹。许多抗日军民的经历惊心动魄,比影视剧情节还传奇。(一)突围路上子……未来具备哪6种能力,会让你越来越增值在人工智能技术越来越普及的今天,很多行业被颠覆,很多工种被替代,我们被社会认可需要的能力也在逐渐地进化和演变。1997年的时候,当时的计算机跟世界顶级的国际象棋大师已经下……经常胃胀吃不下,还老打嗝反酸?想治好它,得先壮胆胃病是我们日常生活中常见的一种疾病,有的人因为以前不健康饮食习惯或者先天肠胃抵抗力差等等原因导致胃部不舒服,在日常生活中多多注意胃部疾病。50多岁的李女士,本来已经到了退……四月德国人最喜欢去的三大度假胜地,阳光,沙滩,美景一个都不能又是一年4月花开季,也是时候出去走走逛逛,放松一下自己了。如果,你正在计划出国前往欧洲他国旅游,不妨来看看这份攻略,希望能够对大家有所帮助!马略卡岛春季出游,……都搭载骁龙778G,看完realmeQ3s和荣耀70的价格,骁龙778G可以说是高通近两年推出的神U,功耗低且性能足够绝大多数用户使用,以至于来到2022年,依旧有厂商推出骁龙778G手机,譬如小米Civi1S、华为nova10、荣耀7……过节回家老爸点名要的白酒,实惠的粮食酒,好喝还不贵1、泸州老窖特曲(浓香)35852vol口感:清亮醇厚清冽、甘爽推荐指数:5星2、华世福(酱香)288元53vol口感:酱香醇厚回味悠长不辣喉不上头跟飞天有一拼推荐指数:5星3……板块出现重大布局机会,这家零部件已供货比亚迪蔚小理等车企【电报解读】6月新能源车零售销量大增130,机构称三季度行业有望迎来库存周期与朱格拉周期共振,这家公司零部件已供货比亚迪、蔚小理等车企。6月新能源车零售销量大增130,机……中锋时代的杰出控卫,全能型的代表人物,改变了NBA的领袖NBA联盟发展至今,已经是世界上最大的篮球联盟了,这是无数人奋斗的结果,尤其是老一辈球员们,他们不仅用实际的行动,促进了篮球的发展,为后人走出一条新路,还为球员们得到更好的待遇……近十年来三分最准的NBA球员!错过大三分时代的JJ雷迪克自从库里在201516年赛季投进破纪录的402个三分球后,NBA正式进入大三分时代,三分球成为主要进攻手段的现今,不管是哪个位置,多数球员都稍微有点三分能力,但滥竽怎能充数,真……付炳锋构建供应链新格局,打造产业链新生态封面故事新起点新格局来源:【汽车纵横全媒体】编者按6月27日29日,以融合创新、绿色发展打造中国汽车产业新生态为主题的2022中国汽车供应链大会暨首届中国新能源智能网联汽车生态大会在湖……2。58万亿元红包落地组合式税收政策助稳经济大盘来源:人民网原创稿编者按:组合式税费支持政策是今年宏观财政政策的一大亮点。当前,新的组合式税费支持政策正在平稳有序落地,其中微型、小型、中型、大型企业存量留抵退税梯次性集……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网