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岁后的你只会比周围的人更值钱。