Android 老兵翻车了,竟拿不到 Application Context?

Android 开发者们对于 Application 并不陌生。有的时候为避免内存泄漏,常常不直接使用 Context 而是通过其提供的 getApplicationConjava语言text() 确保拿到的是 Application 级别的 Context。而本次像通常一样,拿到的 Application 却是 null,到底是发生什么事了?

翻车了

先来回顾一下发rtc是什么意思生问题的代码。

为了避免内存泄漏,在对外提供的 Jar 包里不假思approach索地用了如下代码javaee

private DemoManager(Context context){
    mContext = context.getApplicationContext();
    if(DEBUG){
        mContext.getPackageName();
        ...
    }
}

看似很平常的一个写法,在项目中应用该 Jar 包的时候 ,却发生了崩溃:mContext.getPackageName() 发生了空指针异常。

当看到是此处发生的 cras指针说漫h,属实有点意外、但也没时间多想,暂时将代码改成了这样。

private DemoManager(Context context){
    mContext = context.getApplicationContext();
    if(null == mContext){
        mContext = context;
    }
    if(DEBUG){
        mContext.getPackageName();
        ...
    }
 }

事后有时间了,觉得有必要搞清楚,毕竟这有点颠覆作为一名 Android 老兵的认知。

Application Context 不应该都是先创建的嘛,为什么 Context 都有了 Application 却没有呢?

发生什么事了

尝试写了最小化 Demo 去复现,但是没成功,后来发现java模拟器一般不会发生这样的问题指针式万用表,本次发生是因为运行的 App 比较特殊。

实际的代码在appetite TelephonyProvider App 里添加了自定义的 ContentProvider,并在 query() 里使用了上人头攒动述 Jar 包。指针说漫approve TelephonyProvider App 所依赖的 com.android.phone 系统进程会先启RTC动,之后 TelephonyProvapproachider 才会被加载到该进程。令人意想不到的是,对于 TelephonyProvider App 来说其 Application 一RTC直是 null,并不是它自己的 Application,更不是 Phone Applicatiojava语言n。

所以,Demo 需要采用上述的特性才能复现。比如做 2 个 App,一个是查询 ContentProvider 的 App:Quer指针是什么y App;另一个是供 ContentProvider 的App :Provider App。

  1. Query App 与 Pandroid下载rovider App 在同一个进程 ,通过 android指针式万用表:process=”XXX” 指定
  2. Qapplicationuery App 先启动,并通过 ContentResolver 调用 Provider App 进行 query(ApplicationContext 为 null 和 Query App 调用 query 没有关系指针数组

起初没注意到 TelephonyProvider 和 Phone 同进程的特性,所以 DjavascriptEMO 怎么也复现不了。

接下来我们在 FW 里深入分析下。

为什么共用进程的 Provider App 拿不到 Application人头攒动的读音

不按套路出牌啊

首先回顾下 ContentProvider 中 Context 是哪儿来的?

// frameworks/base/core/java/android/app/ActivityThread.java
private ContentProviderHolder installProvider(Context context...) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    if (holder == null || holder.provider == null) {
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;
        if (context.getPackageName().equals(ai.packageName)) {
            // 如果 Provider App 是独立进程,context 采用传递过来的 Application 参数
            c = context;
        } else if (mInitialApplication != null &&
                mInitialApplication.getPackageName().equals(ai.packageName)) {
            c = mInitialApplication;
        } else {
            try {
               // 反之调用 createPackageContext 创建特有的 Context
               c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
               }...
            }
            ...
            if (info.splitName != null) {
                try {
                    c = c.createContextForSplit(info.splitName);
                } catch (NameNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
            if (info.attributionTags != null && info.attributionTags.length > 0) {
                final String attributionTag = info.attributionTags[0];
                c = c.createAttributionContext(attributionTag);
            }
            try {
                // 这里的 c 就是传递给 ContentProvider 的实际 Context
                localProvider.attachInfo(c, info);
            ...
            }
        } 
        ...
}

传递给 ContentProvider 的 Context 有多种创建方式。如果 Query App 与 Provider App 的 papproachackageName 不相同,这个时候 Provider App 就不能直接使用 Query App 的 Application,要重新创建一个给它,入口在 createPackageConjava怎么读textAsUser 中。

@Override
public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
            throws NameNotFoundException {
    // 这里会调用 LoadedApk 构造函数
    // LoadedApk 持有 Application 实例默认情况为 null
    LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
                flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
        ...
}

createPackageContextAsUser() 会创建自己的 LoadedApk 实例,而 LoadedApk 持有的 Application 实例默认情况下是 null。所以后面如果没有机会赋值 Application 的话,Provider App人体触电的极限电流是多少 拿到的 Application 永远为空。

而 Context#getAprtc是什么意思plicationContext 获取的 Application 是不是就是它哩?

// frameworks/base/core/java/android/app/ContextImpl.java
public Context getApplicationContext() {
    return (mPackageInfo != null) ?
            mPackageInfo.getApplication() : mMainThread.getApplication();
}

可以看到有两个来源:

  1. mPackageInfo:即 LoadedApk,一般情况下都是经过该实例获取的 Application
  2. mMainThread:当 ActivityThread 在 attach 的android/yunos时候就已经初始化了mInitialApplication,不太可能为 nulandroid的drawable类l ,这里不展开。

所以问题应该就是 LoadedApk 中持有的 Application 为空导致的。

而 LoadedApk 持有的 Application 实例是在 makeApplication() 里创建和赋值指针式万用表使用方法的,所以需要进一步分析一approve下 makeApplication() 的闰土刺猹调用源头。

经过搜索发现在 ActivityThread 中存在如下几个关键调用地方:

  • handleBindApplication(): 进程冷启动的时候创建 Appliappstorecation 实例,即本案例中的 Query App 的 Application
  • performLaunchA指针c语言ctivity(): 启动 Activity 的时候
  • handleCreateService(): 启android手机动 Service 的时候
  • handleRjava模拟器eceiver(): 收到广播的时候

四大组件除了 ContentProvider 都会执行 makeApplication()(这里也可以理解,其他几rtc是什么意思个都是需要启android平板电脑价格动是时候才创建实例,ContentProvider 实例的创建是进程启动中)。

// frameworks/base/core/java/android/app/LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
    ...
    // 创建Application
    app = mActivityThread.mInstrumentation.newApplication(
            cl, appClass, appContext);
    ...
    mActivityThread.mAllApplications.add(app);
    mApplication = app;
    if (instrumentation != null) {
        try {
            // 调用 Application#onCreate()
            instrumentation.callApplicationOnCreate(app);
        ...
        }
    }
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    return app;
}

试试吧

经过了如上的代码分析,不禁产生了如下猜想:

  1. getApplicaapplicationtiandroid下载onContext 为 null ,是不是意味着 Prandroid什么意思ovider app 中的 Application 不会创建了?
    加入如下 Log 复现了一下,发现问题发生的时候确实不会调用 Application#onCrea指针数组和数组指针的区别te()。
public class ProviderApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        android.util.Log.e("ProviderApplication","onCreate");
    }
}
  1. 上文提到 Service、Activity、Receiver 三大组件启动的时候有机会调用 makeApJavaplication(),那么我在 Provider App 里启动一个appstoreService ,是人体触电的极限电流是多少不是就没有问题了?

    答案是肯定的,如下的 Log 可以看到两个 App 共用一个进程,手动启动 Service 之后 Apandroid什么意思plication 实appear例才可以拿appetite到。

    Demo 信息androidstudio安装教程补充如下:

    • Query App,包名 为 com.zxg.testcode

    • Provider App,包名:com.zxg.queryproviderdemo,启动的 Service 为 ProviderService,Appli指针和引用的区别cati指针说漫on 为 ProviderApplication,ContentProvider 为 QueryProvider

// 启动 Query App 第一次查询
2022-04-01 15:14:41.126 18687-18687/com.zxg.testcode E/QueryProvider: query
// getContext() 是 android.app.ContextImpl@869d7cf  
2022-04-01 15:14:41.127 18687-18687/com.zxg.testcode E/QueryProvider: getContext() is android.app.ContextImpl@869d7cf  
// 而 getApplicationContext() 是 null
2022-04-01 15:14:41.127 18687-18687/com.zxg.testcode E/QueryProvider: context is null
// 手动启动一个 service,ProviderApplication 创建了并回调了 onCreate
2022-04-01 15:14:46.378 18687-18687/com.zxg.testcode E/ProviderApplication: onCreate
// Service 启动了并拿到了 Application
2022-04-01 15:14:46.380 18687-18687/com.zxg.testcode E/ProviderService: onStartCommand ApplicationContext is com.zxg.queryproviderdemo.ProviderApplication@472f1c7
// Query App 第二次查询
2022-04-01 15:14:49.564 18687-18687/com.zxg.testcode E/QueryProvider: query
2022-04-01 15:14:49.564 18687-18687/com.zxg.testcode E/QueryProvider: getContext() is android.app.ContextImpl@869d7cf
// 这时候 query() 里也拿到了 Application
2022-04-01 15:14:49.564 18687-18687/com.zxg.testcode E/QueryProvider: context is com.zxg.queryproviderdemo.ProviderApplication@472f1c7

the end

如果提供 ContentP人头攒动rovider 的 App 进程是共用android/yunos的,需要注意其生命周期回调的时候有可能拿不到 Apandroid手机plication 实例这个坑。当然这种情况比较罕见,如果遇到了可以考虑下 Context 实例appointment能不能满足你的需求,并辅以必要apple的 Null 检查。

发表评论

提供最优质的资源集合

立即查看 了解详情