源码探索系列9---四大金刚之ContentProvider

好了,终于到了最后一个啦,写到这里,真的觉得不容意啊,以前看这些组件也就那样了,现在还要记录下来,重点是这东西都被分析烂了,我们这些后人屌丝还在写,没点突破的。真没意思呢!就当写作业咯。啦啦啦啦,不管如何,让我们开始看下吧

起航 --- ContentResolver

API: 23

说真的,这个组件我还真的相对于Activity和Service用起来真的好少啊!
现在都不能记起来用他来干嘛了,虽然知道他能用来做跨进程通讯用,不过对于一般的app。
这玩意还真的用的次数少啊! 现在能想起来的一次使用这个就是要获取图片的时候。
以前开发的时候遇到的恶心的是,有些Rom把相册给阉割了,搞了个别的来代替,导致调不了图库!
所以搞到需要自己开发一个,真的很无语!

ContentResolver mContentResolver = this.getContentResolver();
Cursor mCursor = mContentResolver.query(mImageUri, null,
            MediaStore.Images.Media.MIME_TYPE + "=? or "
                    + MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?" + " or " + MediaStore.Images.Media.MIME_TYPE + "=?",
            new String[]{"image/jpeg", "image/png", "image/jpg", "image/jpe"},
            MediaStore.Images.Media.DATA);

从这里我们开始看起吧

public final Cursor query(Uri uri, String[] projection,
        String selection, String[] selectionArgs, String sortOrder) {
    return query(uri, projection, selection, selectionArgs, sortOrder, null);
}

public final Cursor query(final Uri uri, String[] projection,
        String selection, String[] selectionArgs, String sortOrder,
        CancellationSignal cancellationSignal) {
        
    IContentProvider unstableProvider = acquireUnstableProvider(uri);
    if (unstableProvider == null) {
        return null;
    }
    IContentProvider stableProvider = null;
    Cursor qCursor = null;
    try { 
        ...

        ICancellationSignal remoteCancellationSignal = null;
        if (cancellationSignal != null) {
            cancellationSignal.throwIfCanceled();
            remoteCancellationSignal = unstableProvider.createCancellationSignal();
            cancellationSignal.setRemote(remoteCancellationSignal);
        }
        try {
            qCursor = unstableProvider.query(mPackageName, uri, projection,
                    selection, selectionArgs, sortOrder, remoteCancellationSignal);
        } catch (DeadObjectException e) { 
            unstableProviderDied(unstableProvider);
            stableProvider = acquireProvider(uri);
            if (stableProvider == null) {
                return null;
            }
            qCursor = stableProvider.query(mPackageName, uri, projection,
                    selection, selectionArgs, sortOrder, remoteCancellationSignal);
        }
        if (qCursor == null) {
            return null;
        }

        // Force query execution.  Might fail and throw a runtime exception here.
        qCursor.getCount();
        long durationMillis = SystemClock.uptimeMillis() - startTime;
        maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);

        // Wrap the cursor object into CursorWrapperInner object.
        CursorWrapperInner wrapper = new CursorWrapperInner(qCursor,
                stableProvider != null ? stableProvider : acquireProvider(uri));
        stableProvider = null;
        qCursor = null;
        return wrapper;
    } catch (RemoteException e) { 
        return null;
    } finally {
       ...
    }
}
@Override
    protected IContentProvider acquireUnstableProvider(Context c, String auth) {
        return mMainThread.acquireProvider(c,
                ContentProvider.getAuthorityWithoutUserId(auth),
                resolveUserIdFromAuthority(auth), false);
    }

开头调用acquireUnstableProvider(uri),经过一轮跟踪,发现他跑到了ActivityThread里面的acquireProvider,内容如下

public final IContentProvider acquireProvider(
        Context c, String auth, int userId, boolean stable) {
        
    final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
    if (provider != null) {
        return provider;
    } 
    
    IActivityManager.ContentProviderHolder holder = null;
    try {
        holder = ActivityManagerNative.getDefault().getContentProvider(
                getApplicationThread(), auth, userId, stable);
    } catch (RemoteException ex) {
    }
    if (holder == null) {         
        return null;
    } 
    
    // Install provider will increment the reference count for us, and break
    // any ties in the race.
    holder = installProvider(c, holder, holder.info,
            true /*noisy*/, holder.noReleaseNeeded, stable);
    return holder.provider;
}

我们看到他先查找本地有没有,有就返回,没有就去找了AMS要ContentProvider,拿回来后就存本地,然后返回。
好了,让我们去看下AMS里面的实现吧

 @Override
public final ContentProviderHolder getContentProvider(
        IApplicationThread caller, String name, int userId, boolean stable) {
        
    enforceNotIsolatedCaller("getContentProvider");
    if (caller == null) {
        String msg = "null IApplicationThread when getting content provider "
                + name;
        Slog.w(TAG, msg);
        throw new SecurityException(msg);
    } 
    
    return getContentProviderImpl(caller, name, null, stable, userId);
}

他跑去了他的实现函数getContentProviderImpl()....真无语,同个类里面还搞抽象和实现啊,
像前面那样写成realGetContentProviderImpl()不就好了嘛,哈哈,真 · 干活函数
不过这个实现还不小,三百多行,这在AMS里面都是这样的,看着头疼,一堆的判断,截取关键的部分:

private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
        String name, IBinder token, boolean stable, int userId) {
    ContentProviderRecord cpr;
    ContentProviderConnection conn = null;
    ProviderInfo cpi = null;

    ...
    
    proc = startProcessLocked(cpi.processName,
                             cpr.appInfo, false, 0, "content provider",
                             new ComponentName(cpi.applicationInfo.packageName,
                             cpi.name), false, false, false);
                             
     cpr.launchingApp = proc;
     mLaunchingProviders.add(cpr);


     mProviderMap.putProviderByName(name, cpr);
     conn = incProviderCountLocked(r, cpr, token, stable); 
     
    return cpr != null ? cpr.newHolder(conn) : null;
}

这里需要补充的说明是,我们都知道那个ContentProvider的启动是伴随进程的启动,启动进程是有上面的startProcessLocked()来完成的。我们来看下他里面写了什么。。。

  private final void startProcessLocked(ProcessRecord app, String hostingType,
        String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
         ...
        
        checkTime(startTime, "startProcess: asking zygote to start proc");
        Process.ProcessStartResult startResult = Process.start(entryPoint,
                app.processName, uid, uid, gids, debugFlags, mountExternal,
                app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                app.info.dataDir, entryPointArgs);
        checkTime(startTime, "startProcess: returned from zygote!");
         ...
}

这个函数也是一个巨无霸,我想那个AMS一定是靠几个人写完的,因为他的函数风格不在一样,呵呵,虽然我也经常写得不一样。只截取重要的几句..
这个函数最终通过调用Process的start方法来启动。我们继续深入看下

 public static final ProcessStartResult start(final String processClass,
                              final String niceName,
                              int uid, int gid, int[] gids,
                              int debugFlags, int mountExternal,
                              int targetSdkVersion,
                              String seInfo,
                              String abi,
                              String instructionSet,
                              String appDataDir,
                              String[] zygoteArgs) {
    try {
        return startViaZygote(processClass, niceName, uid, gid, gids,
                debugFlags, mountExternal, targetSdkVersion, seInfo,
                abi, instructionSet, appDataDir, zygoteArgs);
    } catch (ZygoteStartFailedEx ex) {
        Log.e(LOG_TAG,
                "Starting VM process through Zygote failed");
        throw new RuntimeException(
                "Starting VM process through Zygote failed", ex);
    }
}

好,遇到了一个传说级别的名字了Zygote,不要问我这个名词代表的故事,百度下吧。
感觉写完这些组件后,可以开始写更底层的部分了...哎。说真的,挺无聊的,看完又没怎么样,但出来混,他们老觉得高级开发就得看底层代码,精通整个Android系统啊!这学习量真的不小,不过也就那点工资...
还不如学校门口一个摆摊的,好了,罗嗦了,继续吧。

结论就是通过Zygote,最后我们到了ActivityThread的main方法,还记得这个玩意嘛?
我们在很久前解析那个Handler的时候有提到,程序的入口方法是ActivityThread的mian方法!
是不是觉得很像以前学java时候!居然在安卓里面遇到人家!

public static void main(String[] args) {
    SamplingProfilerIntegration.start();

    ...
    
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

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

    AsyncTask.init();

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

    Looper.loop();

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

整个的main方法就初始主线程的地方,同时开动主Looper, 然后attach这个方法会最终把消息传递AMS,然后AMS就完成了ContentProvider的创建!

private void attach(boolean system) {
      ...
      
       try {
            mgr.attachApplication(mAppThread);
        } catch (RemoteException ex) {
            // Ignore
        } 
      ...
}

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

    // Find the application record that is being attached...  either via
    // the pid if we are running in multiple processes, or just pull the
    // next app record if we are emulating process with anonymous threads.
    ProcessRecord app;
    ...
    
        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Binding proc "
                + processName + " with config " + mConfiguration);
        ApplicationInfo appInfo = app.instrumentationInfo != null
                ? app.instrumentationInfo : app.info;
        app.compat = compatibilityInfoForPackageLocked(appInfo);
        if (profileFd != null) {
            profileFd = profileFd.dup();
        }
        ProfilerInfo profilerInfo = profileFile == null ? null
                : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop);
        thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
                isRestrictedBackupMode || !normalMode, app.persistent,
                new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
                mCoreSettingsObserver.getCoreSettingsLocked());
        updateLruProcessLocked(app, false, null);
        app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
        
     ...
}

每次到AMS里面的函数都是一大串!!!这里的thread就是那个ApplicationThread,我们很熟悉。
走了一圈,又跑回来了

 public final void bindApplication(String processName, ApplicationInfo appInfo,
            List<ProviderInfo> providers, ComponentName instrumentationName,
            ProfilerInfo profilerInfo, Bundle instrumentationArgs,
            IInstrumentationWatcher instrumentationWatcher,
            IUiAutomationConnection instrumentationUiConnection, int debugMode,
            boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
            Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
            Bundle coreSettings) {

        ...

        AppBindData data = new AppBindData();
        data.processName = processName;
        data.appInfo = appInfo;
        data.providers = providers;
        data.instrumentationName = instrumentationName;
        data.instrumentationArgs = instrumentationArgs;
        data.instrumentationWatcher = instrumentationWatcher;
        data.instrumentationUiAutomationConnection = instrumentationUiConnection;
        data.debugMode = debugMode;
        data.enableOpenGlTrace = enableOpenGlTrace;
        data.restrictedBackupMode = isRestrictedBackupMode;
        data.persistent = persistent;
        data.config = config;
        data.compatInfo = compatInfo;
        data.initProfilerInfo = profilerInfo;
        sendMessage(H.BIND_APPLICATION, data);
    }

最终这个变成了同我们的H先生发送BIND_APPLICATION消息!

private void handleBindApplication(AppBindData data) {
    ...
    // 1.创建Context        
   ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
          
   java.lang.ClassLoader cl = instrContext.getClassLoader();
   mInstrumentation = (Instrumentation)
         cl.loadClass(data.instrumentationName.getClassName()).newInstance();
     
  mInstrumentation.init(this, instrContext, appContext,
               new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
               data.instrumentationUiAutomationConnection);

   ...
   // 2.创建app
  Application app = data.info.makeApplication(data.restrictedBackupMode, null);
  mInitialApplication = app;

   ...
   // 3. 启动ContentProvider,发送消息,调用onCreate函数
  // don't bring up providers in restricted mode; they may depend on the
  // app's custom Application class
  if (!data.restrictedBackupMode) {
      List<ProviderInfo> providers = data.providers;
      if (providers != null) {
          installContentProviders(app, providers);
          // For process that contains content providers, we want to
          // ensure that the JIT is enabled "at some point".
          mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
      }
  }

  ...
  //4. 启动我们Application的onCreate方法。
  // Do this after providers, since instrumentation tests generally start their
     // test thread at this point, and we don't want that racing.         
   mInstrumentation.onCreate(data.instrumentationArgs); 
    mInstrumentation.callApplicationOnCreate(app);   
    StrictMode.setThreadPolicy(savedPolicy);
 
}

这个函数受AMS污染,也是贼长的一个函数。
在这个handleBindApplication函数里面,我截取保留了核心的信息,同时加了注释。
具体就是创建contextApplication,然后通过installContentProviders()来启动当前进程的ContentProvider,所以下次有人和你提问题,说你知道那个ContentProvider嘛?
你就反问,说你知道这个组件是在哪里初始化的嘛?
不知道你就说是在ActivityThread里面的H先生接受到了BindApplication消息,然后在处理消息对应的handleBindApplication()方法里面的installContentProviders()函数啦!
是不是很装逼的样子!!

为了彻底的装逼,我们来看下这个installContentProviders()到底做了什么!
免得人家也看过代码,惨遭装逼失败的下场。

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

    for (ProviderInfo cpi : providers) {
      
        IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
        if (cph != null) {
            cph.noReleaseNeeded = true;
            results.add(cph);
        }
    }

    try {
        ActivityManagerNative.getDefault().publishContentProviders(
            getApplicationThread(), results);
    } catch (RemoteException ex) {
    }
}

这里面遍历了providers,然后installProvider去启动他们。最后找AMS把他们给注册保存了。
而且是保存在一个叫mProviderMap的map里面中,便于别的进程通过AMS来调用!
有代码有真相,有木有,就在下面,没骗人的啊!

 public final void publishContentProviders(IApplicationThread caller,
        List<ContentProviderHolder> providers) {
        
     ...
       
    enforceNotIsolatedCaller("publishContentProviders");
    synchronized (this) {
        final ProcessRecord r = getRecordForAppLocked(caller);
         final long origId = Binder.clearCallingIdentity();
         

        final int N = providers.size();
        for (int i=0; i<N; i++) {
            ContentProviderHolder src = providers.get(i);

            ...
            ContentProviderRecord dst = r.pubProviders.get(src.info.name);
                          
            if (dst != null) {
                ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
                mProviderMap.putProviderByClass(comp, dst);
                String names[] = dst.info.authority.split(";");
                for (int j = 0; j < names.length; j++) {
                    mProviderMap.putProviderByName(names[j], dst);
                }
                ...
                 updateOomAdjLocked(r);
            }
        }
        Binder.restoreCallingIdentity(origId);
    }
}

在彻底的装逼,我们需要来看下这个installProvider函数

private IActivityManager.ContentProviderHolder installProvider(Context context,
        IActivityManager.ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;

    ...
    
        try { //用ClassLoader去加载!
            final java.lang.ClassLoader cl = c.getClassLoader();
            localProvider = (ContentProvider)cl.
                loadClass(info.name).newInstance();
            provider = localProvider.getIContentProvider();
             ...
            // XXX Need to create the correct context for this provider.
            localProvider.attachInfo(c, info);
        } catch (java.lang.Exception e) {
           ...   
         }
            return null;
        }
    }  

    ...
}

好了他的启动过程基本到这里结束,我们回溯到前面的步骤,回到我们处理Application消息的第四点

 //4. 启动我们Application的onCreate方法。
  // Do this after providers, since instrumentation tests generally start their
     // test thread at this point, and we don't want that racing.         
   mInstrumentation.onCreate(data.instrumentationArgs); 
    mInstrumentation.callApplicationOnCreate(app);   
    
    StrictMode.setThreadPolicy(savedPolicy);

好了,整个过程基本这样了。

我们从一开始的acquireUnstableProvider(uri);函数跑到这里,都快忘记原本是要干嘛了。

续航 ==== Query

让我们继续启程,回到一开始query函数,在获得了我们IContentProvider后,就调用了他的query方法。

    qCursor = unstableProvider.query(mPackageName, uri, projection,
                    selection, selectionArgs, sortOrder, remoteCancellationSignal);

这个IContentProvider 是一个Binder接口,具体的干活的人是谁呢?
IContentProvider的具体实现是ContentProviderNative,然后Transport 又继承了它。

abstract public class ContentProviderNative extends Binder implements IContentProvider 

class Transport extends ContentProviderNative 

这个类是躲在ContentProvider的里面的即ContentProvider.Transport 。好了,写了这么多,回主线

@Override
    public Cursor query(String callingPkg, Uri uri, String[] projection,
            String selection, String[] selectionArgs, String sortOrder,
            ICancellationSignal cancellationSignal) {
        validateIncomingUri(uri);
        uri = getUriWithoutUserId(uri);
        if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
               ...
             Cursor cursor = ContentProvider.this.query(uri, projection, selection,
                    selectionArgs, sortOrder, CancellationSignal.fromTransport(
                            cancellationSignal));
            if (cursor == null) {
                return null;
            }

            // Return an empty cursor for all columns.
            return new MatrixCursor(cursor.getColumnNames(), 0);
        }
        
        final String original = setCallingPackage(callingPkg);
        try {
            return ContentProvider.this.query(
                    uri, projection, selection, selectionArgs, sortOrder,
                    CancellationSignal.fromTransport(cancellationSignal));
        } finally {
            setCallingPackage(original);
        }
    }

到这里去调用了ContentProvider的query方法。这样整个过程就结束了。其余的方法也在这个类里面,套路类似的,就不再写了,这篇文章已经写了很长了,而且现在有事,得去做点别了!

后记

今天写得好晚,看到都有点累,不段的翻滚。。。写到后面都没精神和注意力了。。
先写到这里,洗个澡睡觉,明天抽时间在继续做补充。。

但无论如何把四大金刚的源码都写了一遍了。

====
更新:
重新补充了部分内容,把整个流程更细化了下。
不过还是有些具体的内容不是很清楚,
下一次在游览别的时候,能有空再把这个过程写得更清楚的话,就再来更新吧

对于Zygote这个下次得抽时间再写一篇他的专辑。
谷歌起这个名字给它,这么有深度东西,很有故事。

下一篇就这么定是他了 ^_ ^

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

推荐阅读更多精彩内容