Android---使用-ContentProvider-无侵入获取-Context,从草根到百万年薪程序员的十年风雨之路

73 阅读2分钟

val clazz = Class.forName("android.app.ActivityThread") val currentActivityThread = clazz.getMethod("currentActivityThread").apply { isAccessible = true } val getApplication = clazz.getMethod("getApplication").apply { isAccessible = true } activityThread = currentActivityThread.invoke(null) application= getApplication.invoke(activityThread) as Context } catch (e: Throwable) { // 存在未适配的风险 } } return application!! } 复制代码

运行测试一下,context()的返回结果:

android.app.Application@c12661f 复制代码

2.3 小结

  • 优点:

依赖方不需要传递Context对象给库进行初始化,减少了代码耦合,有利于组件化

  • 缺点:

需要反射调用私有API,存在系统版本适配风险;使用反射有一定性能损耗


3. 使用 ContentProvider 获取 ApplicationContext

这一节介绍一种通过ContentProvider.java获得Application的方法。ContentProvider通常的用法是为当前进程 / 远程进程提供内容服务,它们会在应用启动的时候初始化,正因如此,我们可以利用ContentProvider来获得Context。

3.1 源码分析

ActivityThread.java

private void handleBindApplication(AppBindData data) { // ... Application app; app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; // 初始化所有 ContentProvider installContentProviders(app, data.providers); // ... }

private void installContentProviders(Context context, List providers) { final ArrayList results = new ArrayList<>();

for (ProviderInfo cpi : providers) { // 依次初始化 ContentProvider ContentProviderHolder cph = installProvider(context, null, cpi, false /noisy/, true /noReleaseNeeded/, true /stable/); if (cph != null) { cph.noReleaseNeeded = true; results.add(cph); } } // ... } 复制代码

可以看到,在ActivityThread中,创建Application对象之后会调用installContentProviders()安装属于当前进程(processName)的ContentProvider;而在ContentProvider声明的方法中,提供了getContext()获得ApplicationContext,所以,我们就可以ContentProvider启动的机制,从ContentProvider启动时拿到ApplicationContext

3.2 使用步骤

步骤一:实现 ContentProvider 子类

// ContextProvider.kt

internal class ContextProvider : ContentProvider(){

override fun onCreate(): Boolean { init(context!!) return true }

// 其他方法直接 return } // Context.kt private lateinit var application : Context

fun init(context : Context){ application= context }

fun context() : Context{ return application } 复制代码

步骤二:在 AndroidManifest 中配置

// AndroidManifest.xml

复制代码

步骤三:使用

Toast.makeText(context(),"",Toast.LENGTH_SHORT).show() 复制代码

3.3 小结

  • 优点:

依赖方不需要传递Context对象给库进行初始化,减少了代码耦合,有利于组件化

  • 缺点:

在App 启动时就初始化ContentProvider,不是懒初始化

  • 风险:

应保证初始化非常轻量,否则会降低App的启动速度


4. 案例

下面举出一些基于ContentProvider机制实现无侵入地获取Context的例子:

  • LeakCanary 2.4

AppWatcherInstaller.java

internal sealed class AppWatcherInstaller : ContentProvider() {

internal class MainProcess : AppWatcherInstaller()

internal class LeakCanaryProcess : AppWatcherInstaller()

override fun onCreate(): Boolean { val application = context!!.applicationContext as Application AppWatcher.manualInstall(application) return true }

// 其他方法直接 return } 复制代码

  • AutoSize 1.1.2

InitProvider.java

public class InitProvider extends ContentProvider { @Override public boolean onCreate() { AutoSizeConfig.getInstance() .setLog(true) .init((Application) getContext().getApplicationContext()) .setUseDeviceSize(false); return true; }

// 其他方法直接 return } 复制代码

  • Picasso 2.7

PicassoProvider.java

public final class PicassoProvider extends ContentProvider {

@SuppressLint("StaticFieldLeak") static Context context;

@Override public boolean onCreate() { context = getContext(); return true; }

// 其他方法直接 return }

写在最后

在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。

如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧

加入我们吧!群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。