源碼探索系列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閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容