1. 程式人生 > >ContentProvider淺析---寫點你平時沒注意到的~~ ...

ContentProvider淺析---寫點你平時沒注意到的~~ ...

(一) 前言
ContentProvider是android元件之一,可以提供資料的跨應用程式訪問,提供資料的跨程序無縫隙訪問,所以是非常重要的東東。使用方法一般是 複製內容到剪貼簿
程式碼:
getContentResolver().query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder); 那麼下面來提幾個問題:
1. 在應用程式A裡面怎麼跨程序拿到ContentProvider的物件呢?
2. ContentProvider例項物件是儲存在哪裡呢?
3. ContentProvider的方法實現要注意執行緒安全嗎?


如果你能很清晰的回答這幾個問題,那麼下面的你就不需要繼續看了,如果還有疑問,咱們一起往下面學習吧~

(二) 怎麼跨程序拿到ContentProvider的物件
1. 我們來看ContentResolver.query方法是怎麼實現的
a. 首先它會去找ContentProvider物件,是這樣寫的 複製內容到剪貼簿
程式碼:
IContentProvider unstableProvider = acquireUnstableProvider(uri); b. 然後acquireUnstableProvider(uri)方法是這樣的: 複製內容到剪貼簿
程式碼:
public final IContentProvider acquireUnstableProvider(Uri uri) {
        if (!SCHEME_CONTENT.equals(uri.getScheme())) {
            return null;
        }
        String auth = uri.getAuthority();//取得ContentProvider名字,拿這個名字去尋找對應的ContentProvider
        if (auth != null) {
            return acquireUnstableProvider(mContext, uri.getAuthority());
        }
        return null;
    }
在這段程式碼裡面,關鍵地方在這裡 String auth = uri.getAuthority();這裡取得的auth就是我們在AndroidManifes.xml檔案中配置的ContentProvider的android:authorities的值
如: 複製內容到剪貼簿
程式碼:
<provider android:name=".TestProvider"
                android:authorities="com.android.test"></provider>
所以,這個android:authorities屬性配置的就是該ContentProvider的名字,是它在Android系統中的名字,我們是通過這個名字去找對應的ContentProvider物件的。


c. ok..既然現在我們拿到ContentProvider的名字了,我們就來看看acquireUnstableProvider方法怎麼通過名字來找到ContentProvider物件的。
這個acquireUnstableProvider方法會呼叫到ActivityThread的acquireProvider方法,這個方法的實現是: 複製內容到剪貼簿
程式碼:
public final IContentProvider acquireProvider(Context c, String name, boolean stable) {
        IContentProvider provider = acquireExistingProvider(c, name, stable);
        if (provider != null) {
            return provider;
        }

        // There is a possible race here.  Another thread may try to acquire
        // the same provider at the same time.  When this happens, we want to ensure
        // that the first one wins.
        // Note that we cannot hold the lock while acquiring and installing the
        // provider since it might take a long time to run and it could also potentially
        // be re-entrant in the case where the provider is in the same process.
        IActivityManager.ContentProviderHolder holder = null;
        try {
            holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), name, stable);
        } catch (RemoteException ex) {
        }
        if (holder == null) {
            Slog.e(TAG, "Failed to find provider info for " + name);
            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;
    }
這裡就是查詢ContentProvider實現的精髓所在了。。
首先,它去找acquireExistingProvider方法,這個方法其實就是根據我們傳過來的名稱在一個map裡面找,如:
ProviderClientRecord pr = mProviderMap.get(name);
由於我們的ActivityThread和我們的應用程式還在一個程序裡面,所以這個步驟我們可以理解為:在本地快取中尋找ContentProvider物件
ok...在本地找了之後,如果找到了,就直接返回。
if (provider != null) {
            return provider;
        }
如果沒有找到,就繼續往下面走:
holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), name, stable);
這個方法就是呼叫到ActivityManagerService的getContentProvider方法去尋找ContentProvider.這裡是一個跨程序呼叫,因為ActivityThread和ActivityManagerService不在一個程序裡面。
至於ActivityThread和ActivityManagerService的關係,可以參考我以前的這篇帖子:
http://bbs.51cto.com/thread-1008812-1.html

而ActivityManagerService會把所有的ContentProvider都例項化出來,並且快取在一個map裡面,所以我們就可以通過 複製內容到剪貼簿
程式碼:
holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), name, stable);
從ActivityManagerService遠端得到一個ContentProvider物件。那麼這一步,我們可以理解為:從遠端服務中尋找ContentProvider物件
ok..從遠端ActivityManagerService得到ContentProvider物件之後,我們繼續往下面走。 複製內容到剪貼簿
程式碼:
holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
首先,會呼叫installProvider方法,這個方法其實就是往本地的ContentProvider map快取中新增一條快取記錄。
ok...那麼這整個過程,我們就可以理解為這樣:
i.  第一步,它從ActivityThread裡面本地快取尋找ContentProvider物件,所以找到了,就一切ok..
ii. 第二步,如果第一步沒有找到,那麼就去ActivityManagerService遠端服務中尋找ContentProvider物件。
iii.第三步,從遠端服務中找到ContentProvider物件之後,就把這個物件快取在本地,那麼下次找的話,直接就可以從本地快取中查找了。
那麼,它為什麼要有這個機制呢?個人猜測:因為跨程序呼叫是需要時間和資源消耗的,所以,它才有了本地快取這麼個東東。

(三) ContentProvider例項物件是儲存在哪裡
那麼如果大家看完了上面一篇長篇大論,這個問題就很好回答了。
它儲存在兩個位置:
1. ActivityThread的本地map快取中
2. ActivityManagerService的遠端服務map快取中

(四) ContentProvider的方法實現要注意執行緒安全嗎
從上面一段描述來看,我們可以發現一個問題,ContentProvider在某種程度上是單例的,比如我們第一次從本地map快取裡面得到ContentProvider物件,第二次我們在同一個應用程式請求的時候,拿到的肯定是同一個快取物件。
所以,ContentProvider只能配置程序之間是否是單例,同一個程序裡面是不能配置是否是單例的,因為它在同一個程序裡面肯定是單例。
配置程序之間是否是單例: 複製內容到剪貼簿
程式碼:
android:multiprocess="true" 所以我們的ContentProvider的程式碼,比如查詢,更新,刪除等等,必須注意執行緒安全的問題。
那麼單例下,我們怎麼注意執行緒安全問題呢?
1. ContentProvider儘量少用成員變數,因為我們用的是單例,所以成員變數是共享的。
2. 所以真的用到了共享資源,建議用synchronized或者TheadLocal來解決。至於synchronized和TheadLocal的區別,這篇文章就不討論了,下次有機會再寫吧。。

一點點小小分析,僅供參考~~