Android 存储选项之 ContentProvider 启动存在的暗坑

闪存
Android 存储优化系列专题
  • SharedPreferences 系列

Android 之不要滥用 SharedPreferences
Android 之不要滥用 SharedPreferences(2)— 数据丢失

  • ContentProvider 系列(待更)

Android 存储选项之 ContentProvider 的启动性能
《Android 存储选项之 ContentProvider 深入分析》

  • 对象序列化系列

Android 对象序列化之你不知道的 Serializable
Android 对象序列化之 Parcelable 取代 Serializable ?
Android 对象序列化之追求性能完美的 Serial

  • 数据序列化系列(待更)

《Android 数据序列化之 JSON》
《Android 数据序列化之 Protocol Buffer 源码分析》

  • SQLite 存储系列

Android 存储选项之 SQLiteDatabase 创建过程源码分析
Android 存储选项之 SQLiteDatabase 源码分析
数据库连接池 SQLiteConnectionPool 源码分析
SQLiteDatabase 启用事务源码分析
SQLite 数据库 WAL 模式工作原理简介
SQLite 数据库锁机制与事务简介
SQLite 数据库优化那些事儿


前言

在 SharedPreferences 系列《Android 之不要滥用 SharedPreferences》和 《Android 之不要滥用 SharedPreferences(2)— 数据丢失》两篇文章中,详细分析了 SharedPreferences 的实现机制。简单回顾下,Android 系统为什么不把 SharedPreferences 设计成跨进程安全的呢?那是因为 Android 系统更希望我们在这个场景使用 ContentProvider 作为存储方式。

ContentProvider 作为 Android 四大组件之一,为应用开发者提供了不同进程甚至是不同应用程序之间共享数据的机制。

今天我们主要来聊聊 ContentProvider 这个存储方法。


ContentProvider 的使用

Android 系统中比如相册、日历、音频、视频、通讯录等模块都提供了 ContentProvider 的访问支持。它的使用非常简单,你可以参考官方文档

但是,ContentProvider 在使用过程中也存在一些“暗坑”需要我们特别注意。

  • 启动性能

ContentProvider 的生命周期默认在 Application onCreate() 之前,而且都是在主线程创建的。我们自定义的 ContentProvider 类的构造函数、静态代码块、onCreate 函数都尽量不要做耗时的操作,会拖慢启动速度。

ContentProvider 启动流程
  • 稳定性

ContentProvider 在进行跨进程数据传递时,利用了 Android 的 Binder 和匿名共享内存机制。简单来说,就是通过 Binder 传递 CursorWindow 对象内部的匿名共享内存的文件描述符。这样在跨进程传输中,结果数据并不需要跨进程传输,而是在不同进程中通过传输的匿名共享内存文件描述符来操作同一块匿名内存,这样来实现不同进程访问相同数据的目的

CursorWindow

基于 mmap 的匿名共享内存机制也是有代价的。当传输的数据量非常小的时候,可能不一定划算。所以 ContentProvider 提供了一种 call 函数,它会直接通过 Binder 来传输数据。

Android 的 Binder 传输是有大小限制的,一般来说限制是 1 ~ 2MB。ContentProvider 的接口调用参数和 call 函数调用并没有使用匿名共享机制,比如要批量插入很多数据,那么就会出现一个插入数据的数组,如果这个数组太大了,那么这个操作就可能出现数据超大异常。

  • 安全性

虽然 ContentProvider 为应用程序之间的数据共享提供了很好的安全机制,但是如果 ContentProvider 是 exported,当支持执行 SQL 语句时就需要注意 SQL 注入的问题。另外如果我们传入的参数是一个文件路径,然后返回文件内容,这个时候也要校验合法性,不然整个应用的私有数据都有可能被别人拿到,在 Intent 传递参数的时候可能会经常会犯这个错误。

今天我们先来聊聊 ContentProvider 的启动性能,稳定性和安全性放到系列后面文章进行介绍。还是从源码的角度出发,分析自定义 ContentProvider 的创建过程和生命周期回调过程。


自定义 ContentProvider 的启动过程分析

我们要从应用程序启动类 ActivityThread 开始,关于 ActivityThread 大家肯定不会感到陌生,它是我们应用进程的入口类,也就是 main 函数所在类。

 public static void main(String[] args) {
    //省略部分

    //主线程Looper创建
    Looper.prepareMainLooper();

    //创建ActivityThread对象
    ActivityThread thread = new ActivityThread();
    //调用自己的attach()方法
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    //省略
    
    //开启出队
    Looper.loop();
    //如果调用了主线程Looper的quit() 抛异常在这里
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

在 main 方法中创建了 ActivityThread 对象,并调用它的 attach 方法,然后在 attach 方法中通过 AMS(ActivityManagerService)远程调用了 attachApplicationLocked 方法,在该方法中完成自定义 ContentProvider 的收集工作,先来看下 ActivityThread 的 attach 方法。

 private void attach(boolean system, long startSeq) {
    sCurrentActivityThread = this;
    mSystemThread = system;
    if (!system) {
    
        ... 省略
        
         //我们重点关注这里
        //这里得到实例ActivityManagerService代理类Proxy
        final IActivityManager mgr = ActivityManager.getService();
        try {
            //然后通过代理类完成远程(跨进程)调用
            //mAppThread是当前进程的ApplicationThread实例
           //在完成跨进程条用完成之后要通过该对象回到当前进程
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
}

通过 ActivityManager 获得 ActivityManagerService 的代理对象,完成跨进程调用,由于 Binder 进程间通信不是我们今天要分析的内容,这里通过一张流程图简单说下该过程。

ActivityManagerService
ActivityManagerSerivce 的 attachApplication 方法分析

前面有简单提到通过 AMS 远程调用该方法,在该方法内开始收集注册在 AndroidManifest.xml 中的 ContentProvider 信息,实际上该方法主要完成两部分工作:

  1. generateApplicationProvidersLocked 方法,通过 PMS 完成 ContentProvider 注册信息(ProviderInfo)的收集工作。
  2. 将收集 Providers 的信息集合,作为参数远程调用 ApplicationThread 的 bindApplication 方法。此时将重新回到应用进程启动类 ActivityThread 中。

在 attachApplication 方法中调用了 attachApplicationLocked ,我们直接看下该方法:

private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid, int callingUid, long startSeq) {

   // ...省略
   
    ProcessRecord app;
    long startTime = SystemClock.uptimeMillis();
    if (pid != MY_PID && pid >= 0) {
        synchronized (mPidsSelfLocked) {
            app = mPidsSelfLocked.get(pid);
        }
    } else {
        app = null;
    }

    // ...省略

    //通过PMS查找应用在Manifest中注册的ContentProvider
    List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;

    if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
        //这里启动Provider的超时机制 10s钟
        Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
        msg.obj = app;
        //通过发送延迟消息
        mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
    }

   //...省略
   
        //调用ApplicationThread的bindApplication(), 
        //重新回到启动类 ActivityThread 中
        if (app.isolatedEntryPoint != null) {
            // This is an isolated process which should just call an entry point instead of
            // being bound to an application.
            thread.runIsolatedEntryPoint(app.isolatedEntryPoint, app.isolatedEntryPointArgs);
        } else if (app.instr != null) {
            thread.bindApplication(processName, appInfo, providers,
                    app.instr.mClass,
                    profilerInfo, app.instr.mArguments,
                    app.instr.mWatcher,
                    app.instr.mUiAutomationConnection, testMode,
                    mBinderTransactionTrackingEnabled, enableTrackAllocation,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    new Configuration(getGlobalConfiguration()), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked(),
                    buildSerial, isAutofillCompatEnabled);
        } else {
            thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                    null, null, null, testMode,
                    mBinderTransactionTrackingEnabled, enableTrackAllocation,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    new Configuration(getGlobalConfiguration()), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked(),
                    buildSerial, isAutofillCompatEnabled);
        }
    } catch (Exception e) {
        //...省略
        return false;
    }
   // ...省略
    return true;
}

先来跟踪第一部分内容,generateApplicationProviderslocked 方法开始收集 ContentProvider 的注册信息。

private final List<ProviderInfo> generateApplicationProvidersLocked(ProcessRecord app) {
    List<ProviderInfo> providers = null;
    try {
        //通过PackageManager获取 provider 注册信息
        //这里通信过程与ActivityManager类似
        providers = AppGlobals.getPackageManager()
                .queryContentProviders(app.processName, app.uid,
                        STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS
                                | MATCH_DEBUG_TRIAGED_MISSING, /*metadastaKey=*/ null)
                .getList();
    } catch (RemoteException ex) {}
    // 省略
    return providers;
}

上述方法中 AppGlobals 的 getPackageManager 方法实际返回的是 PackageManagerService,然后调用它的 queryContentProviders 方法:

public @NonNull ParceledListSlice<ProviderInfo> queryContentProviders(String processName,
        int uid, int flags, String metaDataKey) {
    //...省略
    ArrayList<ProviderInfo> finalList = null;
    // reader
    synchronized (mPackages) {
        //遍历mProviders
        //第一个mProviders是ProviderIntentResolver类型
        //第二个mProviders是一个ArrayMap
        final Iterator<PackageParser.Provider> i = mProviders.mProviders.values().iterator();
        while (i.hasNext()) {
            //PackageParser.Provider持有ProviderInfo
            final PackageParser.Provider p = i.next();
            PackageSetting ps = mSettings.mPackages.get(p.owner.packageName);
            if (ps != null && p.info.authority != null
                    && (processName == null
                            || (p.info.processName.equals(processName)
                                    && UserHandle.isSameApp(p.info.applicationInfo.uid, uid)))
                    && mSettings.isEnabledAndMatchLPr(p.info, flags, userId)) {

                // See PM.queryContentProviders()'s javadoc for why we have the metaData
                // parameter.
                if (metaDataKey != null
                        && (p.metaData == null || !p.metaData.containsKey(metaDataKey))) {
                    continue;
                }
                final ComponentName component =
                        new ComponentName(p.info.packageName, p.info.name);
                if (filterAppAccessLPr(ps, callingUid, component, TYPE_PROVIDER, userId)) {
                    continue;
                }
                if (finalList == null) {
                    finalList = new ArrayList<ProviderInfo>(3);
                }
                //ProviderInfo表示一个ContentProvider相关信息,包括其包名,类名等。
                //它继承自ComponentInfo并实现了Parcalable
                ProviderInfo info = PackageParser.generateProviderInfo(p, flags,
                        ps.readUserState(userId), userId);
                if (info != null) {
                    finalList.add(info);
                }
            }
        }
    }

    if (finalList != null) {
        Collections.sort(finalList, mProviderInitOrderSorter);
        return new ParceledListSlice<ProviderInfo>(finalList);
    }
    return ParceledListSlice.emptyList();
}

该方法的主要工作是遍历 ProviderIntentResolver 中 mProviders (Map)容器,该容器保存的是 PackageParse.Provider:

 private final ArrayMap<ComponentName, PackageParser.Provider> mProviders
            = new ArrayMap<ComponentName, PackageParser.Provider>();

每个 PackageParse.Provider 持有一个 ProviderInfo,ProviderInfo 代表一个 ContentProvider 相关信息。所以此时我们需要知道 mProviders 是在哪里被添加数据的?

经过查找发现在 commitPackageSettings 方法中添加相关数据

private void commitPackageSettings(PackageParser.Package pkg,
                                   @Nullable PackageParser.Package oldPkg, PackageSetting pkgSetting, UserHandle user,
                                   final @ScanFlags int scanFlags, boolean chatty) {
    //...省略
    synchronized (mPackages) {
        // We don't expect installation to fail beyond this point

        // Add the new setting to mSettings
        mSettings.insertPackageSettingLPw(pkgSetting, pkg);
        // Add the new setting to mPackages
        mPackages.put(pkg.applicationInfo.packageName, pkg);
        // Make sure we don't accidentally delete its data.
        final Iterator<PackageCleanItem> iter = mSettings.mPackagesToBeCleaned.iterator();
        while (iter.hasNext()) {
            PackageCleanItem item = iter.next();
            if (pkgName.equals(item.packageName)) {
                iter.remove();
            }
        }

        // Add the package's KeySets to the global KeySetManagerService
        KeySetManagerService ksms = mSettings.mKeySetManagerService;
        ksms.addScannedPackageLPw(pkg);

        /**存储ContentProvider信息*/
        int N = pkg.providers.size();
        StringBuilder r = null;
        int i;
        for (i = 0; i < N; i++) {
            PackageParser.Provider p = pkg.providers.get(i);
            p.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    p.info.processName);
            //在queryContentProviders()方法遍历的便是该Providers中Map容器
            mProviders.addProvider(p);
            p.syncable = p.info.isSyncable;
            if (p.info.authority != null) {
                String names[] = p.info.authority.split(";");
                p.info.authority = null;
                for (int j = 0; j < names.length; j++) {
                    if (j == 1 && p.syncable) {
                        p = new PackageParser.Provider(p);
                        p.syncable = false;
                    }
                    if (!mProvidersByAuthority.containsKey(names[j])) {
                        mProvidersByAuthority.put(names[j], p);
                        if (p.info.authority == null) {
                            p.info.authority = names[j];
                        } else {
                            p.info.authority = p.info.authority + ";" + names[j];
                        }
                        if (DEBUG_PACKAGE_SCANNING) {
                            if (chatty)
                                Log.d(TAG, "Registered content provider: " + names[j]
                                        + ", className = " + p.info.name + ", isSyncable = "
                                        + p.info.isSyncable);
                        }
                    } else {
                        PackageParser.Provider other = mProvidersByAuthority.get(names[j]);
                        Slog.w(TAG, "Skipping provider name " + names[j] +
                                " (in package " + pkg.applicationInfo.packageName +
                                "): name already used by "
                                + ((other != null && other.getComponentName() != null)
                                ? other.getComponentName().getPackageName() : "?"));
                    }
                }
            }
            if (chatty) {
                if (r == null) {
                    r = new StringBuilder(256);
                } else {
                    r.append(' ');
                }
                r.append(p.info.name);
            }
        }
        if (r != null) {
            if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Providers: " + r);
        }

        /**存储Service信息*/
        N = pkg.services.size();
        r = null;
        for (i = 0; i < N; i++) {
            PackageParser.Service s = pkg.services.get(i);
            s.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    s.info.processName);
            mServices.addService(s);
            if (chatty) {
                if (r == null) {
                    r = new StringBuilder(256);
                } else {
                    r.append(' ');
                }
                r.append(s.info.name);
            }
        }
        if (r != null) {
            if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Services: " + r);
        }

        /**存储BrodcastReceiver信息*/
        N = pkg.receivers.size();
        r = null;
        for (i = 0; i < N; i++) {
            PackageParser.Activity a = pkg.receivers.get(i);
            a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    a.info.processName);
            mReceivers.addActivity(a, "receiver");
            if (chatty) {
                if (r == null) {
                    r = new StringBuilder(256);
                } else {
                    r.append(' ');
                }
                r.append(a.info.name);
            }
        }
        if (r != null) {
            if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Receivers: " + r);
        }

        /**存储Activity信息*/
        N = pkg.activities.size();
        r = null;
        for (i = 0; i < N; i++) {
            PackageParser.Activity a = pkg.activities.get(i);
            a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    a.info.processName);
            mActivities.addActivity(a, "activity");
            if (chatty) {
                if (r == null) {
                    r = new StringBuilder(256);
                } else {
                    r.append(' ');
                }
                r.append(a.info.name);
            }
        }
     
        // ...省略
    }

    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}

从源码中可以看出,commitPackageSettings 方法中不仅包含 ContentProvider 信息,还包括 Service、BroadcastReceiver、Activity 信息。这些信息都是解析 AndroidManifest.xml 文件得到。辅助 PMS 完成解析工作任务的是 PackageParser,完成这一过程的具体方法如下:

private boolean parseBaseApplication(Package owner, Resources res,
        XmlResourceParser parser, int flags, String[] outError)
    throws XmlPullParserException, IOException {
...省略

 //解析Manifest.xml
 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
            continue;
        }
        //获取xml节点名称
        String tagName = parser.getName();
        //解析到 activity 节点
        if (tagName.equals("activity")) {
            Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
                    owner.baseHardwareAccelerated);
            if (a == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            owner.activities.add(a);
         //解析到广播 receiver 节点
        } else if (tagName.equals("receiver")) {
            Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
                    true, false);
            if (a == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            owner.receivers.add(a);
          //解析到服务 service 节点
        } else if (tagName.equals("service")) {
            Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
            if (s == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            owner.services.add(s);
          //解析到ContentProvider 节点
        } else if (tagName.equals("provider")) {
            Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
            if (p == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            owner.providers.add(p);

        } else {
            if (!RIGID_PARSER) {
                Slog.w(TAG, "Unknown element under <application>: " + tagName
                        + " at " + mArchiveSourcePath + " "
                        + parser.getPositionDescription());
                XmlUtils.skipCurrentTag(parser);
                continue;
              } else {
                outError[0] = "Bad element under <application>: " + tagName;
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
              }
         }
     }
    ...省略

    return true;
}

解析过程中对应的 tagName 与我们在 AndroidManifest.xml 中声明一致,这也可以证明我们在 Manifest 中四大组件的声明。

到此 attachApplication 方法工作的第一部分就算是分析完了,该部分内容的主要工作是:通过 PMS 收集在 AndroidManifest 中注册的 ContentProvider 信息,将其封装成 ProviderInfo 集合。

ActivityManagerService attachApplication 方法第二部分分析:

将第一部分收集的 ProviderInfo 信息集合,作为参数远程调用 ApplicationThread 的 bindApplication 方法。此时将重新回到应用进程启动类 ActivityThread 中:

ApplicationThread

ApplicationThread 是 ActivityThread 与系统 PMS 进程通信的桥梁,它本质也是一个 Binder 对象。ApplicationThread bindApplication 方法:

    public final void bindApplication(String processName, ApplicationInfo appInfo,
                                      List<ProviderInfo> providers, ...省略) {

        ... 省略

        //将返回数据都封装在AppBindData中
        AppBindData data = new AppBindData();
        data.processName = processName;
        data.appInfo = appInfo;
        //这是我们要跟踪的ContentProvider集合
        data.providers = providers;
        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.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
        //发送BIND_APPLICATION消息到主线程Handler
        sendMessage(H.BIND_APPLICATION, data);
    }

在 bindApplication 方法发送 BIND_APPLICATION 消息到当前进程的主线程 Handler 中。

小结

在分析 ContentProvider 的收集过程中,验证了自定义 ContentProvider 必须在 AndroidManifest.xml 注册,并且验证了 Activity、Service、BroadcastReceiver
在 Manifest 中的注册过程。


回到 ActivityThread

在 ApplicationThread 的 bindApplication 方法发送消息到主线程,此时来到 ActivityThread Handler 的 handleMessage 方法,先看下在 ActivityThread 中的声明:

class H extends Handler {
    public static final int BIND_APPLICATION        = 110;
    public static final int EXIT_APPLICATION        = 111;
    public static final int RECEIVER                = 113;
    public static final int CREATE_SERVICE          = 114;
    public static final int SERVICE_ARGS            = 115;
    public static final int STOP_SERVICE            = 116;

    // 省略

    public void handleMessage(Message msg) {
        switch (msg.what) {
            //通过 ApplicationThread 发送的 BIND_APPLICATION
            case BIND_APPLICATION:
                AppBindData data = (AppBindData) msg.obj;
                //调用 handleBindApplication 开始真正创建 Application
                handleBindApplication(data);
                break;
                
                ...省略
        }
    }
}

不知道大家有没有注意到,到现在为止我们应用的 Application 对象还没有被创建。先来跟踪下 handleBindApplication 方法(这个方法超级长,可能是 ActivityThread 中最长的一个方法)。虽然该方法体较长,但是我们只分析主线部分也可以将它划分成两个部分:

  • 遍历 PMS 收集到的所有 ContentProvider 集合信息(ProviderInfo),并创建所有 ContentProvider 实例。回调其 onCreate 方法。

  • 创建当前进程的 Application 对象,首先回调其 attach 方法,这步发生在遍历 ContentProvider 集合之前,创建每个 ContentProvider 并回调其 onCreate 方法之后,回调 Application 的 onCreate。

handleBindApplication 方法实现如下:

    private void handleBindApplication(AppBindData data) {
        ...省略
        
         /**
         * 这里创建了Application对象,并回调其attach()
         */
        app = data.info.makeApplication(data.restrictedBackupMode, null);
        
        if (!data.restrictedBackupMode) {
            //判断Provider集合信息不为空
            if (!ArrayUtils.isEmpty(data.providers)) {
                /**
                 * 创建所有在Manifest中注册的Provider,并回调onCreate
                 * */
                installContentProviders(app, data.providers);
                mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10 * 1000);
            }
        }
        
        ...省略
        
        /**
        * 回调Application的onCreate
         * */
        mInstrumentation.callApplicationOnCreate(app);
        
        ... 省略
    }

先来看下 Application 的创建过程,makeApplication 方法:

public Application makeApplication(boolean forceDefaultAppClass,
    Instrumentation instrumentation) {

... 省略

  try {
      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);
      }
      //创建ContextImpl将其关联到Application
      ContextImpl appContext =     ContextImpl.createAppContext(mActivityThread, this);
      //通过Instrumentation创建Application 并回调其attach方法
      app = mActivityThread.mInstrumentation.newApplication(
              cl, appClass, appContext);
      appContext.setOuterContext(app);
  } catch (Exception e) {
      ... 省略
  }

  ... 省略

  return app;
}

通过 Instrumentation 的 newApplication 方法完成创建:

public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    //通过反射创建Application
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    //调用Application的attach
    app.attach(context);
    return app;
}

此时应用进程的 Application 对象才被创建,但是生命周期 onCreate 方法并没有被回调。重新回到 handBindApplication 方法,看下 Provider 的创建过程。

installContentProviders 方法如下:
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());
        }
        //遍历创建所有的ContentProvider
        ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
        if (cph != null) {
            cph.noReleaseNeeded = true;
            results.add(cph);
        }
    }
    // 省略
}

这里我们重点跟踪 installProvider 方法如下:

private ContentProviderHolder installProvider(Context context,
                                              ContentProviderHolder holder, ProviderInfo info,
                                              boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    //这是一个Binder对象,实际类型是ContentProviderProxy
    IContentProvider provider;
    if (holder == null || holder.provider == null) {
        // 方法参数中holder传递进来的就是null,故会走该逻辑
        //...省略
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;
        //根据包名判断该ContentProvider是否属于当前应用
        //该部分if-else逻辑主要为获得一个合适的Context,然后通过该Context获得ClassLoader
        if (context.getPackageName().equals(ai.packageName)) {
            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) {
            }
        }
        if (c == null) {
            //如果获取不到上下文,直接return。
            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;
            }

            //通过类加载器加载, 反射创建 Class.newInstance() ContentProvider实例
            localProvider = packageInfo.getAppFactory()
                    .instantiateProvider(cl, info.name);
            //这里得到是当前ContentProvider的Binder对象,它的实际类型是ContentProviderProxy
            provider = localProvider.getIContentProvider();

            if (provider == null) {
                Slog.e(TAG, "Failed to instantiate class " +
                        info.name + " from sourceDir " +
                        info.applicationInfo.sourceDir);
                //如果未能正确初始化该ContentProvider的Binder,直接return。
                return null;
            }
            if (DEBUG_PROVIDER) Slog.v(
                    TAG, "Instantiating local provider " + info.name);

            //ContentProvider的attachInfo中会回调ContentProvider的onCreate()方法
            //在attachInfo中毁掉了ContentProvider的onCreate()方法。
            localProvider.attachInfo(c, info);

        } 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;
}

由于其方法参数 holder 传递为 null,故会走 holder == null 的逻辑:

    packageInfo.getAppFactory().instantiateProvider(cl, info.name)

在该方法内实际通过 ClassLoader 加载,并反射 newInstance 创建该 ContentProvider 实例。

 public @NonNull ContentProvider instantiateProvider(@NonNull ClassLoader cl,
        @NonNull String className)
        throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    //通过类加载加载,并反射创建该Provider实例
    return (ContentProvider) cl.loadClass(className).newInstance();
}

回到 installProvider 方法,创建并返回 ContentProvider 实例后会调用它的 attachInfo 方法:

private void attachInfo(Context context, ProviderInfo info, boolean testing) {
    mNoPerms = testing;
    if (mContext == null) {
        mContext = context;
        if (context != 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);
        }
        //这里回调了ContentProvider的onCreate方法
        ContentProvider.this.onCreate();
    }
}

首次创建 ConentProvider 时,该 mContext 变量肯定是为 null 。在方法的最后回调了 onCreate 方法,此时我们自定义的 ContextProvider 的 onCreate 就会被回调。

这里我们也得到验证,自定义 ContentProvider 的创建过程以及生命周期默认在 Application 的 onCreate 方法之前

重新回到 handleBindApplication 方法,Application 的生命周期还没有回调呢!

/**
  * 回调Application的onCreate
  * */
mInstrumentation.callApplicationOnCreate(app);

知道此刻,我们的 Application 才算是真正创建完成。

handleBindApplication 方法中首先创建了当前进程的 Applicaiton 对象,并没有立即回调其 onCreate 方法,而是创建所有在 Manifest 注册的 ContentProvider 对象,并回调其生命周期 onCreate 完成之后,才重新回调Application 的 onCreate 方法。


总结

大家是否注意到 ContentProvider 加载和创建都是在主线程完成,并且还都是在应用启动过程完成,ContentProvider 的生命周期默认在 Application onCreate 之前。这也验证了文章开头为大家介绍的启动性能,在使用 ContentProvider 需要注意的“暗坑”,自定义 ContentProvider 类的构造函数、静态代码块、onCreate 函数都尽量不要做耗时的操作,会拖慢启动速度


以上便是个人在学习 ContentProvider 启动性能的心得和体会,文中如有不妥或有更好的分析结果,欢迎大家指出!

文章如果对你有帮助,就请留个赞吧!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,270评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,489评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,630评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,906评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,928评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,718评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,442评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,345评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,802评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,984评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,117评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,810评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,462评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,011评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,139评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,377评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,060评论 2 355

推荐阅读更多精彩内容

  • 先给出一个需要注意的点:ContentProvider的onCreate方法比Application的onCrea...
    潇风寒月阅读 529评论 0 1
  • ContentProvider是内容提供者,对外提供数据。内部运行依赖Binde机制。想要自己写一个Content...
    sososeen09阅读 1,324评论 0 3
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,523评论 16 22
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,567评论 0 11
  • 可爱进取,孤独成精。努力飞翔,天堂翱翔。战争美好,孤独进取。胆大飞翔,成就辉煌。努力进取,遥望,和谐家园。可爱游走...
    赵原野阅读 2,730评论 1 1