ContentProvider的使用以及基本工做原理

什麼是ContentProvider?

ContentProvider是Android4大組件之一,咱們平時使用的機會可能比較少。其底層經過Binder進行數據共享。若是咱們要對第三方應用提供數據,能夠考慮使用ContentProvider實現。html

如何使用

與其餘的ContentProvider通訊。

要實現與其餘的ContentProvider通訊首先要查找到對應的ContentProvider進行匹配。android中ContenProvider藉助ContentResolver經過Uri與其餘的ContentProvider進行匹配通訊。java

認識Uri 來自:www.cnblogs.com/tgyf/p/4696…

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");
複製代碼

最簡單的查詢ContentProvider

  1. 經過Context獲取ContentResolver
  2. 調用它的query方法
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

實現自的ContentProvider須要繼承Android系統的ContentProvider而後實現下面的幾個方法。

  • onCreate()
  • query()
  • getType()
  • insert()
  • delete()
  • update()

須要注意的是除了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的數據變化

當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調用以前。

ContentProvider的query過程

爲何分析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的發佈過程

對於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中ContentProvider

AutoSize是一款優秀的基於今日頭條方案的屏幕適配框架,它的使用很是簡單。引入框架,而後在manifest中聲明對應設計圖的寬高便可使用。侵入性很是低。那麼開啓應用便可使用的呢?答案是利用ContentProvider會隨應用程序啓動而啓動的特性。而後在ContentProvider中作本身的初始化操做。這個是一個很是好的實現思路。咱們不只能夠用ContentProvider提供數據,也能夠利用它的特性初始化本身的特定邏輯。

總結:

  1. 如何經過ContentProvider查詢數據

    經過ContentResolver 進行uri匹配

  2. 如何實現本身的ContentProvider

    繼承ContentProvider,實現對應的方法。在manifest中聲明

  3. ContentResolver如何返回Cursor對象

    在跨進程的狀況下返回的是CursorToBulkCursorAdaptor對象,其實質是藉助Binder的跨進程傳輸能力,在ContentProvider進程中序列化,在調用程序中反序列化。

相關文章
相關標籤/搜索