ContentProvider是Android4大組件之一,咱們平時使用的機會可能比較少。其底層經過Binder進行數據共享。若是咱們要對第三方應用提供數據,能夠考慮使用ContentProvider實現。html
要實現與其餘的ContentProvider通訊首先要查找到對應的ContentProvider進行匹配。android中ContenProvider藉助ContentResolver經過Uri與其餘的ContentProvider進行匹配通訊。java
URI爲系統中的每個資源賦予一個名字,比方說通話記錄。每個ContentProvider都擁有一個公共的URI,用於表示ContentProvider所提供的數據。 Android所提供的ContentProvider都位於android.provider包中, 能夠將URI分爲A、B、C、D 4個部分來理解。如對於content://com.wang.provider.myprovider/tablename/id:android
a、標準前綴——content://,用來講明一個Content Provider控制這些數據;數據庫
b、URI的標識——com.wang.provider.myprovider,用於惟一標識這個ContentProvider,外部調用者能夠根據這個標識來找到它。對於第三方應用程序,爲了保證URI標識的惟一性,它必須是一個完整的、小寫的類名。這個標識在元素的authorities屬性中說明,通常是定義該ContentProvider的包.類的名稱;markdown
c、路徑——tablename,通俗的講就是你要操做的數據庫中表的名字,或者你也能夠本身定義,記得在使用的時候保持一致就能夠了;網絡
d、記錄ID——id,若是URI中包含表示須要獲取的記錄的ID,則返回該id對應的數據,若是沒有ID,就表示返回所有;app
對於第三部分路徑(path)作進一步的解釋,用來表示要操做的數據,構建時應根據實際項目需求而定。如:框架
a、操做tablename表中id爲11的記錄,構建路徑:/tablename/11;ide
b、操做tablename表中id爲11的記錄的name字段:tablename/11/name;oop
c、操做tablename表中的全部記錄:/tablename;
d、操做來自文件、xml或網絡等其餘存儲方式的數據,如要操做xml文件中tablename節點下name字段:/ tablename/name;
e、若須要將一個字符串轉換成Uri,可使用Uri類中的parse()方法,如:
Uri uri = Uri.parse("content://com.wang.provider.myprovider/tablename"); 複製代碼
ContentResolver resolver = getContentResolver(); Cursor cursor = resolver.query(Uri.parse(""),null,null,null,null); if(cursor != null){ while (cursor.moveToNext()){ Log.d("tag","query result "+cursor.getColumnNames()); } cursor.close(); } 複製代碼
實現自的ContentProvider須要繼承Android系統的ContentProvider而後實現下面的幾個方法。
須要注意的是除了onCreate()其餘的方法都運行在binder線程池。
而後在Manifest中聲明對應的contentProvider便可。
contentProvider實現
class DataContentProvider : ContentProvider() { private val tag = "DataContentProvider" private var dbHelper:DBHelper? = null override fun insert(uri: Uri, values: ContentValues?): Uri? { return uri } override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? { val cursor = dbHelper?.readableDatabase?.query(DBHelper.USER_TABLE_NAME,projection,null,selectionArgs,null,null,sortOrder) Log.d(tag,"call query cursor is $cursor") return cursor } override fun onCreate(): Boolean { dbHelper = DBHelper(this.context) val db = dbHelper?.writableDatabase db?.execSQL("delete from ${DBHelper.USER_TABLE_NAME}"); db?.execSQL("insert into ${DBHelper.USER_TABLE_NAME} values(1,'XW');") db?.execSQL("insert into ${DBHelper.USER_TABLE_NAME} values(2,'XZ');") return true } override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int { return 0 } override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int { return 0 } override fun getType(uri: Uri): String? { return null } } 複製代碼
class DBHelper //數據庫版本號 (context: Context?) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { override fun onCreate(db: SQLiteDatabase) { // 建立兩個表格:用戶表 和職業表 db.execSQL("CREATE TABLE IF NOT EXISTS $USER_TABLE_NAME(_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)") } override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {} companion object { // 數據庫名 private const val DATABASE_NAME = "demo_provider.db" // 表名 const val USER_TABLE_NAME = "user" private const val DATABASE_VERSION = 1 } } 複製代碼
Manifest註冊以下:
<provider android:name=".contentprovider.DataContentProvider" android:authorities="com.txl.demo.content.provider" /> 複製代碼
這樣一個及其簡單的ContentProvider就實現了。裏面只實現了查詢功能。
當ContentProvider數據發生改變的時候,能夠經過ContentResolver的notifyChange()通知監聽者數據發生改變。而外部須要經過ContentResolver註冊監聽才能接收到數據變化通知。
要理解這個工做流程須要對Android的Binder通訊機制有較好的理解。能夠參考
咱們都知道ContentProvider經過binder向其餘組件或者應用程序提供數據。
當ContentProvider所在的進程啓動的時候,ContentProvider會同時啓動並被髮布到AMS中,須要注意的是ContentProvider的onCreate方法會先於Application的OnCreate調用。
應用程序啓動的時候會調用ActivityThread#main方法。在這裏會建立ActivityThread實例並初始化主線程的消息隊列(初始化主線程的消息Looper)。並在ActivityThread#attach方法中遠程調用AMS的attachApplication方法。而AMS又會遠程調用ActivityThread#ApplicationThread#bindApplication。在bindApplication方法中經過handler切換到ActivityThread#handleBindApplication這裏會建立Application和ContentProvider。
咱們在handleBindApplication找到了 下面的一段代碼:
app = data.info.makeApplication(data.restrictedBackupMode, null);// Propagate autofill compat state app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled); mInitialApplication = 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)) { //建立ContentProvider installContentProviders(app, data.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); } } // 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 { //調用Application的OnCreate mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { if (!mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to create application " + app.getClass().getName() + ": " + e.toString(), e); } } 複製代碼
上面的代碼能夠證實前面說的ContentProvider建立在Application#onCreate調用以前。
爲何分析query過程?
1.query是Content最多見的一個使用流程,具備表明性。
2.ContentProvider跨進程通訊返回了一個未通過Parcelable序列化的Cursor。這讓人不得很差奇這個過程經歷了什麼。
咱們經過Context#getContentResolver獲取 ContentResolver。Conetx獲取到的ContentResolver是ApplicationContentResolver對象。
ApplicationContentResolver的query方法在它的父類中實現ContentResolver中實現,在query中首先會獲取 一個IContentProvider對象,不論是經過 acquireUnstableProvider 方法仍是經過acquireProvider()方法其本質最終都是經過調研ActivityThread#acquireProvider方法來實現
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; } // 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. ContentProviderHolder holder = null; try { synchronized (getGetProviderLock(auth, userId)) { holder = ActivityManager.getService().getContentProvider( getApplicationThread(), auth, userId, stable); } } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); 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,若是不存在則經過ActivityManagerServer獲取對應的ContentProviderHolder,在ActivityManager獲取ContentProviderHolder的過程當中會判斷contentProvider所在的進程是否存在若是不存在的話會建立對應的進程並啓動ContentProvider。最後調用installProvider來獲取IContentProvider對象。
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)) { 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); 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); } 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); } ContentProviderHolder retHolder; synchronized (mProviderMap) { if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider + " / " + info.name); IBinder jBinder = provider.asBinder(); if (localProvider != null) { ComponentName cname = new ComponentName(info.packageName, info.name); ProviderClientRecord pr = mLocalProvidersByName.get(cname); if (pr != null) { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, " + "using existing local provider"); } provider = pr.mProvider; } else { holder = new ContentProviderHolder(info); holder.provider = provider; holder.noReleaseNeeded = true; pr = installProviderAuthoritiesLocked(provider, localProvider, holder); mLocalProviders.put(jBinder, pr); mLocalProvidersByName.put(cname, pr); } retHolder = pr.mHolder; } else { ProviderRefCount prc = mProviderRefCountMap.get(jBinder); if (prc != null) { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, updating ref count"); } // We need to transfer our new reference to the existing // ref count, releasing the old one... but only if // release is needed (that is, it is not running in the // system process). if (!noReleaseNeeded) { incProviderRefLocked(prc, stable); try { ActivityManager.getService().removeContentProvider( holder.connection, stable); } catch (RemoteException e) { //do nothing content provider object is dead any way } } } else { ProviderClientRecord client = installProviderAuthoritiesLocked( provider, localProvider, holder); if (noReleaseNeeded) { prc = new ProviderRefCount(holder, client, 1000, 1000); } else { prc = stable ? new ProviderRefCount(holder, client, 1, 0) : new ProviderRefCount(holder, client, 0, 1); } mProviderRefCountMap.put(jBinder, prc); } retHolder = prc.holder; } } return retHolder; } 複製代碼
整個installProvider方法的代碼比較長,核心思想是,若是傳入的holder爲空或者它的provider爲空,那麼會執行本地建立邏輯,調用建立的ContentProvider#getIContentProvider()獲取IContentProvider對象。而後放入對應的Holder。若是傳入的holder不爲空,直接獲取holder中的provider。也就是說咱們能夠簡單理解爲獲取到的IContentProvider爲getIContentProvider方法返回的對象。
public IContentProvider getIContentProvider() { return mTransport; } 複製代碼
能夠看到ContentProvider#getIContentProvider返回了mTransport,mTransport對應的類爲ContentProvider#Transport,Transport繼承了ContentProviderNative,而ContentProviderNative又繼承了BInder。這樣咱們就明白了Transport經過繼承Binder的方式實現了跨進程傳輸。咱們知道Binder通訊跨進程進行方法調用時經過onTransact才能處理到對應的方法。咱們直接看到ContentProviderNative#onTransact。
由於onTransact方法實現過長,這裏咱們只關心query的實現部分。
case QUERY_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); String callingPkg = data.readString(); Uri url = Uri.CREATOR.createFromParcel(data); // String[] projection int num = data.readInt(); String[] projection = null; if (num > 0) { projection = new String[num]; for (int i = 0; i < num; i++) { projection[i] = data.readString(); } } Bundle queryArgs = data.readBundle(); IContentObserver observer = IContentObserver.Stub.asInterface( data.readStrongBinder()); ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface( data.readStrongBinder()); Cursor cursor = query(callingPkg, url, projection, queryArgs, cancellationSignal); if (cursor != null) { CursorToBulkCursorAdaptor adaptor = null; try { adaptor = new CursorToBulkCursorAdaptor(cursor, observer, getProviderName()); cursor = null; BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor(); adaptor = null; reply.writeNoException(); reply.writeInt(1); d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } finally { // Close cursor if an exception was thrown while constructing the adaptor. if (adaptor != null) { adaptor.close(); } if (cursor != null) { cursor.close(); } } } else { reply.writeNoException(); reply.writeInt(0); } return true; } 複製代碼
在這個主要涉及到這兩個類CursorToBulkCursorAdaptor,BulkCursorDescriptor。BulkCursorDescriptor實現了Parcelable能夠跨進程進行傳輸,CursorToBulkCursorAdaptor間接繼承了Binder頁具有跨進程傳輸能力。在跨進程返回數據的時將BulkCursorDescriptor寫入到返回數據中。在ContentProviderProxy中進行反序列化獲得CursorToBulkCursorAdaptor類。這樣整個查詢過程就清晰了。
對於ContentProvider#getIContentProvider補充說明,前面咱們直接簡單理解成返回了mTransport這個是有點問題的。其實在哪一個位置獲取到的IContentProvider對象是ContentProviderProxy。咱們來看看緣由。
在應用程序啓動的時候會啓動ContentProvider,並將對用的ContentProvider發佈到ActivityManagerServer。
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 { //將ContentProvider相關信心發佈到ActivityManagerService ActivityManager.getService().publishContentProviders( getApplicationThread(), results); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } 複製代碼
能夠看到最終將results發佈到了ActivityManagerService。而這個result列表裏面裝的是ContentProviderHolder。它實現了Parcelable。咱們知道跨進程傳輸須要涉及到序列化與反序列化。
public class ContentProviderHolder implements Parcelable { public final ProviderInfo info; public IContentProvider provider; public IBinder connection; public boolean noReleaseNeeded; public ContentProviderHolder(ProviderInfo _info) { info = _info; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { info.writeToParcel(dest, 0); if (provider != null) { dest.writeStrongBinder(provider.asBinder()); } else { dest.writeStrongBinder(null); } dest.writeStrongBinder(connection); dest.writeInt(noReleaseNeeded ? 1 : 0); } public static final Parcelable.Creator<ContentProviderHolder> CREATOR = new Parcelable.Creator<ContentProviderHolder>() { @Override public ContentProviderHolder createFromParcel(Parcel source) { return new ContentProviderHolder(source); } @Override public ContentProviderHolder[] newArray(int size) { return new ContentProviderHolder[size]; } }; private ContentProviderHolder(Parcel source) { info = ProviderInfo.CREATOR.createFromParcel(source); provider = ContentProviderNative.asInterface( source.readStrongBinder()); connection = source.readStrongBinder(); noReleaseNeeded = source.readInt() != 0; } } 複製代碼
能夠看到 反序列化的時候經過ContentProviderNative#asInterface獲得了 ContentProviderProxy的實例對象。所以在跨進程是咱們 getIContentProvider時獲取到的是ContentProviderProxy實例對象。這是一個典型的Binder通訊過程。
AutoSize是一款優秀的基於今日頭條方案的屏幕適配框架,它的使用很是簡單。引入框架,而後在manifest中聲明對應設計圖的寬高便可使用。侵入性很是低。那麼開啓應用便可使用的呢?答案是利用ContentProvider會隨應用程序啓動而啓動的特性。而後在ContentProvider中作本身的初始化操做。這個是一個很是好的實現思路。咱們不只能夠用ContentProvider提供數據,也能夠利用它的特性初始化本身的特定邏輯。
如何經過ContentProvider查詢數據
經過ContentResolver 進行uri匹配
如何實現本身的ContentProvider
繼承ContentProvider,實現對應的方法。在manifest中聲明
ContentResolver如何返回Cursor對象
在跨進程的狀況下返回的是CursorToBulkCursorAdaptor對象,其實質是藉助Binder的跨進程傳輸能力,在ContentProvider進程中序列化,在調用程序中反序列化。