Android ContentProvider の onCreate メソッドと Application の onCreate メソッド、どちらが最初に呼び出されますか?

1. 質問

アプリケーションが起動するとき、ContentProvider の onCreate メソッドまたは Application の onCreate メソッドを最初に呼び出す必要がありますか?

回答: ContentProvider の onCreate メソッドが最初に呼び出されます。Application::attachBaseContext-> ContentProvider::onCreate-> Application::onCreate

この質問には、 ContentProvider の onCreate メソッドでApplication.mApplication同様の。ContentProvider は一部のリソースを読み込むために Context を使用する必要がある場合があるため、一部の学生はトラブルを避けるために Context を使用するだけでApplication.mApplication、結果として null ポインターが発生する場合があります。

Context オブジェクトをいつでもどこでも使用するために、ほとんどの開発者Application::onCreateApplicationメソッドでオブジェクトを取得し、クラスにオブジェクトを保存するか、ツール クラスの静的属性にオブジェクトを置きます。次のように使用します。

//方式1 直接用 MyApplication.mApplication
public class MyApplication extends Application {
    public static Application mApplication;
    @Override
    public void onCreate(){
        mApplication = this;
    }

}
//方式2 ContextUtil.getContext()
public class ContextUtil {
    private static Context appContext;
    public static void setContext(Application application) {
        appContext = application;
    }
    public static Context getContext(){
        return appContext;
    }
}



public class MyProvider extends ContentProvider {

    @Override
    public void onCreate() {
        MyInit.init(ContextUtil.getContext());
    }
}

ここで初期化操作が実行されると仮定すると、一部のデータ処理 (データの取得など) にはコンテキスト オブジェクトを使用する必要があり、Context メソッドを使用すると明らかに null ポインター例外が発生します。(私はこれを愚かにやっていたので、アプリがクラッシュしました)

这就导致一个问题,如果在 ContentProvideronCreate 方法中需要使用Application.mApplication对象时,就会出现空指针问题。解决该问题的方法有两种: (1) Application.mApplication 方法的赋值在Appliaction#attachBaseContext方法中进行。该方法能保证在绝大多处使用Application.mApplication 不会出现为空情况。(Application::attachBaseContext 方法在 ContentProvider::onCreate之前调用,调用流程将在后面代码分析) (2) ContentProvideronCreate 中方法中使用getContext. 其实 ContentProvidergetContext获取的对象就是Application对象(具体原因后面代码分析)

2. 分析

具体原因且从Application的创建说起,此处省略内容为 点击桌面A应用的图标后->Launcher3 通过startActivity方法去启动 A应用 的根Activity->调用到了AMS(ActivityManagerService)-> AMS发现应用未启动->AMS通过socket向Zygote进程发起创建新应用进程的请求->Zygote fork了自身 并早RuntimeInit::invokeStaticMain方法内通过反射调用了ActivityThreadmain方法。

进入到ActivityThread方法中。

frameworks/base/core/java/android/app/ActivityThread.java

 public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // Install selective syscall interception
        AndroidOs.install();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        // Call per-process mainline module initialization.
        initializeMainlineModules();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);//代码1

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

main 方法中会创建ActivityThread 对象.并在代码1处调用attach方法。

 @UnsupportedAppUsage
private void attach(boolean system, long startSeq) {
    sCurrentActivityThread = this;
    mSystemThread = system;
    if (!system) {
        android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                UserHandle.myUserId());
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        final IActivityManager mgr = ActivityManager.getService();
        try {
            mgr.attachApplication(mAppThread, startSeq);//代码1
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        // Watch for getting close to heap limit.
        BinderInternal.addGcWatcher(new Runnable() {
            @Override public void run() {
                if (!mSomeActivitiesChanged) {
                    return;
                }
                Runtime runtime = Runtime.getRuntime();
                long dalvikMax = runtime.maxMemory();
                long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                if (dalvikUsed > ((3*dalvikMax)/4)) {
                    if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                            + " total=" + (runtime.totalMemory()/1024)
                            + " used=" + (dalvikUsed/1024));
                    mSomeActivitiesChanged = false;
                    try {
                        ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
            }
        });
    } else {
        // 对于系统应用的处理
    }

    // 系统配置监听,如横竖状态、字体大小等
    ViewRootImpl.ConfigChangedCallback configChangedCallback
            = (Configuration globalConfig) -> {
        synchronized (mResourcesManager) {
            // TODO (b/135719017): Temporary log for debugging IME service.
            if (Build.IS_DEBUGGABLE && mHasImeComponent) {
                Log.d(TAG, "ViewRootImpl.ConfigChangedCallback for IME, "
                        + "config=" + globalConfig);
            }

            // We need to apply this change to the resources immediately, because upon returning
            // the view hierarchy will be informed about it.
            if (mResourcesManager.applyConfigurationToResourcesLocked(globalConfig,
                    null /* compat */)) {
                updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
                        mResourcesManager.getConfiguration().getLocales());

                // This actually changed the resources! Tell everyone about it.
                if (mPendingConfiguration == null
                        || mPendingConfiguration.isOtherSeqNewer(globalConfig)) {
                    mPendingConfiguration = globalConfig;
                    sendMessage(H.CONFIGURATION_CHANGED, globalConfig);
                }
            }
        }
    };
    ViewRootImpl.addConfigCallback(configChangedCallback);
}

在代码1处,应用通过调用AMS的 attachApplication方法,将mAppThread(ApplicationThread类型)binder 对象传递给AMS.

@Override
public final void attachApplication(IApplicationThread thread, long startSeq) {
    if (thread == null) {
        throw new SecurityException("Invalid application interface");
    }
    synchronized (this) {
        int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        attachApplicationLocked(thread, callingPid, callingUid, startSeq);//代码1
        Binder.restoreCallingIdentity(origId);
    }
}
 @GuardedBy("this")
private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
        int pid, int callingUid, long startSeq) {
    // ...
    // 代码2
    thread.bindApplication(processName, appInfo, providerList, null, profilerInfo,
                null, null, null, testMode,
                mBinderTransactionTrackingEnabled, enableTrackAllocation,
                isRestrictedBackupMode || !normalMode, app.isPersistent(),
                new Configuration(app.getWindowProcessController().getConfiguration()),
                app.compat, getCommonServicesLocked(app.isolated),
                mCoreSettingsObserver.getCoreSettingsLocked(),
                buildSerial, autofillOptions, contentCaptureOptions,
                app.mDisabledCompatChanges);
    // ...
}       

从代码1处进入代码2处,attachApplicationLocked 方法实现比较长,此处只关注其调用到了thread.binApplication.threadIApplicationThread类型的对象。而IApplicationThread 是一个aidl文件,前面说的ActivityThread::ApplicationThread内部类就是一个Binder对象。此处的thread就是前面attachApplication传入的ApplicationThreadBinder对象的代理类。

frameworks/base/core/java/android/app/ActivityThread.java

private class ApplicationThread extends IApplicationThread.Stub {
    //...

     @Override
        public final void bindApplication(String processName, ApplicationInfo appInfo,
                ProviderInfoList providerList, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableBinderTracking, boolean trackAllocation,
                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                String buildSerial, AutofillOptions autofillOptions,
                ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges) {
            //省略

            AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.providers = providerList.getList();
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.debugMode = debugMode;
            data.enableBinderTracking = enableBinderTracking;
            data.trackAllocation = trackAllocation;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfilerInfo = profilerInfo;
            data.buildSerial = buildSerial;
            data.autofillOptions = autofillOptions;
            data.contentCaptureOptions = contentCaptureOptions;
            data.disabledCompatChanges = disabledCompatChanges;
            sendMessage(H.BIND_APPLICATION, data);//代码1
        }
    //...
}
class H extends Handler {
           
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case BIND_APPLICATION://代码2
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                //省略    
                }
            }
        }
    }
}

private void handleBindApplication(AppBindData data) {
    //...
    Application app;
    final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
    final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
    try {
    // If the app is being launched for full backup or restore, bring it up in
    // a restricted environment with the base application class.
    app = data.info.makeApplication(data.restrictedBackupMode, null);//代码3

    // Propagate autofill compat state
    app.setAutofillOptions(data.autofillOptions);

    // Propagate Content Capture options
    app.setContentCaptureOptions(data.contentCaptureOptions);

    mInitialApplication = app;//初始化App,进程的第一个App

    // don't bring up providers in restricted mode; they may depend on the
    // app's custom Application class
    if (!data.restrictedBackupMode) {
        if (!ArrayUtils.isEmpty(data.providers)) {
            installContentProviders(app, data.providers);//代码4
        }
    }

    // Do this after providers, since instrumentation tests generally start their
    // test thread at this point, and we don't want that racing.
    try {
        mInstrumentation.onCreate(data.instrumentationArgs);/
    }
    catch (Exception e) {
        throw new RuntimeException(
            "Exception thrown in onCreate() of "
            + data.instrumentationName + ": " + e.toString(), e);
    }
    try {
        mInstrumentation.callApplicationOnCreate(app);//代码5
    } catch (Exception e) {
        if (!mInstrumentation.onException(app, e)) {
            throw new RuntimeException(
              "Unable to create application " + app.getClass().getName()
              + ": " + e.toString(), e);
        }
    }
    } finally {
        // If the app targets < O-MR1, or doesn't change the thread policy
        // during startup, clobber the policy to maintain behavior of b/36951662
        if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1
                || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
            StrictMode.setThreadPolicy(savedPolicy);
        }
    }
    //...
}

代码1处通过Handler 传递消息调用handleBindApplication,在主线程处理bindApplication的操作.进入handleBindApplication 方法。

在代码3处调用了 data.info.makeApplication 方法创建了Application. 其中 data是 AppBindData 对象,infoLoadedApk对象。

frameworks/base/core/java/android/app/LoadedApk.java


public Application makeApplication(boolean forceDefaultAppClass,
         Instrumentation instrumentation) {
     if (mApplication != null) {
         return mApplication;
     }

     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

     Application app = null;

     String appClass = mApplicationInfo.className;
     if (forceDefaultAppClass || (appClass == null)) {
         appClass = "android.app.Application";
     }

     try {
         final java.lang.ClassLoader cl = getClassLoader();
         if (!mPackageName.equals("android")) {
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                     "initializeJavaContextClassLoader");
             initializeJavaContextClassLoader();
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }

         // Rewrite the R 'constants' for all library apks.
         SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers(
                 false, false);
         for (int i = 0, n = packageIdentifiers.size(); i < n; i++) {
             final int id = packageIdentifiers.keyAt(i);
             if (id == 0x01 || id == 0x7f) {
                 continue;
             }

             rewriteRValues(cl, packageIdentifiers.valueAt(i), id);
         }

         ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);//代码1 
         // The network security config needs to be aware of multiple
         // applications in the same process to handle discrepancies
         NetworkSecurityConfigProvider.handleNewApplication(appContext);
         app = mActivityThread.mInstrumentation.newApplication(
                 cl, appClass, appContext);//代码2
         appContext.setOuterContext(app);
     } catch (Exception e) {
         if (!mActivityThread.mInstrumentation.onException(app, e)) {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
             throw new RuntimeException(
                 "Unable to instantiate application " + appClass
                 + ": " + e.toString(), e);
         }
     }
     mActivityThread.mAllApplications.add(app);// 代码3
     mApplication = app;

     if (instrumentation != null) {//代码4
         try {
             instrumentation.callApplicationOnCreate(app);
         } catch (Exception e) {
             if (!instrumentation.onException(app, e)) {
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                 throw new RuntimeException(
                     "Unable to create application " + app.getClass().getName()
                     + ": " + e.toString(), e);
             }
         }
     }

     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

     return app;
 }

代码1处,调用ContextImpl.createAppContext 方法创建了应用的上下文对象。 代码2处,mInstrumentation 是Instrumentation对象,调用handleNewApplication 对象。并调用了Application::attach方法,而Application::attach中调用了Application::attachBaseContext方法,此处稍后介绍。 代码3处,将创建的Application对象放入了 mAllApplications 列表中。关于mApplications为什么是个数组,暂时还没太搞懂,等以后搞懂了再写篇文章(虽然搜到一些介绍,是多个应用跑到一个进程时用到的,但是具体还没搞太懂)。 代码4处,根据前面ActivityThread::handleBindApplication调用传入可知instrumentation传入为null。所以不会走到 Instrumentation::callApplicationOnCreate方法。(Instrumentation::callApplicationOnCreate方法中调用了Application::onCreate)

从代码2处,我们可以看到Application已经被创建了。接下来看下Instrumentation::handleNewApplication方法。

frameworks/base/core/java/android/app/Instrumentation.java

public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    app.attach(context);//代码1
    return app;
}

frameworks/base/core/java/android/app/Application.java

@UnsupportedAppUsage
/* package */ final void attach(Context context) {
    attachBaseContext(context);//代码2
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

如上,从代码1走到代码2处,调用了Application::attachBaseContext.

接下来我们回到,ActivityThread::handleBindApplication 方法代码4处(往上翻下哈),调用了installContentProviders方法。该方法的进入前提是应用没有运行在 restrictedBackupMode 模式下。

frameworks/base/core/java/android/app/ActivityThread.java

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

    for (ProviderInfo cpi : providers) {
        if (DEBUG_PROVIDER) {
            StringBuilder buf = new StringBuilder(128);
            buf.append("Pub ");
            buf.append(cpi.authority);
            buf.append(": ");
            buf.append(cpi.name);
            Log.i(TAG, buf.toString());
        }
        ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
        if (cph != null) {
            cph.noReleaseNeeded = true;
            results.add(cph);
        }
    }

    try {
        ActivityManager.getService().publishContentProviders(
            getApplicationThread(), results);
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}

@UnsupportedAppUsage
private ContentProviderHolder installProvider(Context context,
        ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    if (holder == null || holder.provider == null) {
        if (DEBUG_PROVIDER || noisy) {
            Slog.d(TAG, "Loading provider " + info.authority + ": "
                    + info.name);
        }
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;
        if (context.getPackageName().equals(ai.packageName)) {//代码1 包名相同,传入的context根据追溯关系是Application对象
            c = context;
        } else if (mInitialApplication != null &&
                mInitialApplication.getPackageName().equals(ai.packageName)) {//
            c = mInitialApplication;
        } else {
            try {
                c = context.createPackageContext(ai.packageName,
                        Context.CONTEXT_INCLUDE_CODE);
            } catch (PackageManager.NameNotFoundException e) {
                // Ignore
            }
        }
        if (c == null) {
            Slog.w(TAG, "Unable to get context for package " +
                  ai.packageName +
                  " while loading content provider " +
                  info.name);
            return null;
        }

        if (info.splitName != null) {
            try {
                c = c.createContextForSplit(info.splitName);
            } catch (NameNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        try {
            final java.lang.ClassLoader cl = c.getClassLoader();
            LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
            if (packageInfo == null) {
                // System startup case.
                packageInfo = getSystemContext().mPackageInfo;
            }
            localProvider = packageInfo.getAppFactory()
                    .instantiateProvider(cl, info.name);//代码 2
            provider = localProvider.getIContentProvider();
            if (provider == null) {
                Slog.e(TAG, "Failed to instantiate class " +
                      info.name + " from sourceDir " +
                      info.applicationInfo.sourceDir);
                return null;
            }
            if (DEBUG_PROVIDER) Slog.v(
                TAG, "Instantiating local provider " + info.name);
            // XXX Need to create the correct context for this provider.
            localProvider.attachInfo(c, info);//代码3
        } catch (java.lang.Exception e) {
            if (!mInstrumentation.onException(null, e)) {
                throw new RuntimeException(
                        "Unable to get provider " + info.name
                        + ": " + e.toString(), e);
            }
            return null;
        }
    } else {
        provider = holder.provider;
        if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
                + info.name);
    }

    // ...
    return retHolder;
}


代码1处根据传入的context调用关系追溯,是 Application对象.接下c属性将在代码3处被传入。 代码2处 getAppFactory获取的是AppComponentFactory对象,AppComponentFactory::instantiateProvider 方法也很简单,只是创建了ContentProvider实例

frameworks/base/core/java/android/app/AppComponentFactory.java

public @NonNull ContentProvider instantiateProvider(@NonNull ClassLoader cl,
        @NonNull String className)
        throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    return (ContentProvider) cl.loadClass(className).newInstance();
}

ActivityThread::installProvider方法中 代码4处,调用了ContentProvider::attachInfo方法,传入了Context对象cProviderInfo对象info.

frameworks/base/core/java/android/content/ContentProvider.java

public void attachInfo(Context context, ProviderInfo info) {
    attachInfo(context, info, false);//代码1
}   

private void attachInfo(Context context, ProviderInfo info, boolean testing) {
    mNoPerms = testing;
    mCallingPackage = new ThreadLocal<>();

    /*
     * Only allow it to be set once, so after the content service gives
     * this to us clients can't change it.
     */
    if (mContext == null) {
        mContext = context;
        if (context != null && mTransport != null) {
            mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
                    Context.APP_OPS_SERVICE);
        }
        mMyUid = Process.myUid();
        if (info != null) {
            setReadPermission(info.readPermission);
            setWritePermission(info.writePermission);
            setPathPermissions(info.pathPermissions);
            mExported = info.exported;
            mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
            setAuthorities(info.authority);
        }
        if (Build.IS_DEBUGGABLE) {
            setTransportLoggingEnabled(Log.isLoggable(getClass().getSimpleName(),
                    Log.VERBOSE));
        }
        ContentProvider.this.onCreate();//代码2
    }
}

从代码1处走到了代码2处,调用了ContentProvider::onCreate方法。此处确定了ContentProvider::onCraete落后于Application::attachBaseContext.

接下来再回到ActivityThread::handleBindApplication 代码5(可以从头开始翻)处,调用了Instrumentation::callApplicationOnCreate方法。

frameworks/base/core/java/android/app/Instrumentation.java

public void callApplicationOnCreate(Application app) {
    app.onCreate();
}

由此,总算是调用到了Application::onCreate处。

3 结论

Application::attachBaseContext -> ContentProvider::onCreate -> Application::onCreate.这个调用顺序确认完毕了。

ContentProvider::getContext 获取的对象是 Application.

4 扩展 (关于利用ContentProvider 调用顺序的一些骚操作)

ContentProvider 的 onCreate() 的调用时机介于 Application 的 attachBaseContext() 和 onCreate() 之间,因此可以将一些需要在Application进行初始化的方法放到ContentProvider中进行,这种方式可以将一些外部库需要在Application时进行的初始化操作放入库自身,不需要开发者调用初始化方法。(当然,这种方式具有一定的局限性,在 restrictedBackupMode 模式下库就不行了)。LeakCanary2.0就是使用的这种方法,具体请看这篇文章

ContentProvider妙用 --- 完成application初始化

おすすめ

転載: juejin.im/post/7233720373237596220