數據庫是Android開發中最基本的數據保存方式,但因爲數據庫的私有性,咱們沒法對外提供或獲取信息,當兩個應用須要實現數據共享時,此時就須要本篇文章的主題——ContentProviderjava
在使用ContentProvider以前,先介紹下Uri基礎,Uri的對於開發者來講應該並不陌生,開發中使用Uri之處有不少,如:AppLink、FileProvider等,他們的做用相同都是定位資源位置,不一樣的是此處定義的是數據庫中的信息;android
Uri的匹配表示要查詢的數據,對於單個數據查詢,可直接使用Uri定位具體的資源位置,但當範圍查詢時就須要結合通配符的使用,Uri提供如下兩種通配符:數據庫
content://com.example.app.provider/table2/* //多數據查詢
content://com.example.app.provider/table3/#
content://com.example.app.provider/table3/6 //單數據查詢
複製代碼
Uri uri = Uri.parse(「content://contacts/people/5") 複製代碼
//經過將 ID 值追加到 URI 末尾來訪問表中的單個行
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
複製代碼
ContentProvider通常配合數據庫共同使用,實現對外共享數據的目的,因此它須要對數據庫的增刪改查操做,ContentProvider也爲咱們提供了相應的操做方法,使用時只需實現便可,下面按照使用步驟實現一個ContentProvider:數組
UriMatcher的做用是在使用Uri操做數據庫時,根據發起請求的Uri和配置好的uriMatcher肯定本次操做的數據表bash
static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH)
Static{
uriMatcher.addURI(AUTHORIY,」userinfo」,1) //添加userinfo表映射
uriMatcher.addURI(AUTHORIY,」userinfo/*」,2) //*表示匹配任意長度任意字符
uriMatcher.addURI(AUTHORIY,」userinfo/#」,3) //#匹配任意長度的的數字
}
複製代碼
public Uri insert(Uri uri,ContentValues contentValues){
long newId = 0;
Uri newUri = null;
switch (uriMatcher.match(uri)){
newId = dataBase.insert(…) //此處的newId表示插入數據的id
newUri = Uri.parse(content://authoriy/**table/newId)
}
return newUri;
}
複製代碼
使用細節:app
ContentProvider的查詢和數據庫查詢同樣,支持條件查詢和多數據查詢,返回結果爲查詢Cursor實例ide
//當查詢整個數據表時. Uri.parse(」content://com.book.jtm/userinfo") dataBase.query(….) //當查詢具體一個數據. Uri.parse(」content://com.book.jtm/userinfo/123456」) String id = uri.getPathSegments().get(1) //調價查詢時 dataBase.query(table, projection,」tel_number = ?」,new String[]{id},null, null,sortOrder) 複製代碼
提到ContentProvider的使用就會想到ContentObserver,這裏一塊兒介紹下ContentObserver,採用觀察者模式在存儲的數據發生修改時自動觸發回調,使用起來也很簡單建立ContentObserver的實例完成註冊便可:ui
val contentObservable = object : ContentObserver(handler){
override fun onChange(selfChange: Boolean, uri: Uri?) {
super.onChange(selfChange, uri)
val cursor = contentResolver.query(uri, arrayOf("_id","name"),null,null,null)
if (cursor != null && cursor.moveToFirst()) {
do {
Log.e("========", cursor.getInt(cursor.getColumnIndex("_id")).toString())
Log.e("========", cursor.getString(cursor.getColumnIndex("name")).toString())
}while (cursor.moveToNext())
}
}
}
複製代碼
當數據添加時自動查詢全部數據: this
<intent-filter>
<action android:name="com.alex.kotlin.job.JobScheduleActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain」/> //生明匹配的數據類型 </intent-filter> 複製代碼
val intent = Intent("com.alex.kotlin.job.JobScheduleActivity")
intent.type = "text/plain」 // 設置數據庫類型,只有與清單文件中設置的一致才能啓動 startActivity(intent) 複製代碼
<data android:mimeType="vnd.android.cursor.dir/test"/>
複製代碼
<provider
android:authorities="com.alex.kotlin.job.provider"
android:name=".Provider">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="com.alex.kotlin.job.provider"
android:pathPrefix="/path"
android:scheme="content" />
</intent-filter>
</provider>
複製代碼
usrSwitch.addURI(AUTHORITY, "path", 1)
const val data = "vnd.android.cursor.dir/test」 //聲明要返回的MIMEType類型 override fun getType(uri: Uri): String? { when(usrSwitch.match(uri)){ 1 -> { return data} } return null } 複製代碼
val intent = Intent("com.alex.kotlin.job.JobScheduleActivity」) //配置action intent.data = Uri.parse("content://com.alex.kotlin.job.provider/path」) //設置Uri
startActivity(intent)
複製代碼
有沒有想過爲何能夠啓動活動呢?靜態啓動Activity的兩個條件:必須匹配意圖過濾的action和mimeType;spa
由上面的代碼看出建立Intent時設置了意圖過濾action,那麼mimeType呢?其實在使用Uri.parse("content://com.alex.kotlin.job.provider/path」) 設置intent.data時會啓動上面配置的ContentProvider,在ContentProvider返回Uri模式"vnd.android.cursor.dir/test」正好匹配清單中的data數據類型,因此會直接啓動JobScheduleActivity
//該屬性的值決定可訪問的提供程序範圍,若是設置爲true,系統會像整個系統授予臨時權限,並替代其餘設置的權限
android:grantUriPermissions=「true" //設置爲false,則需添加<grant-uri-permission>並代表能夠受權臨時權限所對應的URI android:grantUriPermissions=「false"
<grant-uri-permission android:path=「string」 // path表示絕對路徑Uri
android:pathPattern=「string」 // 表示限定完整的路徑但可使用./*通配符匹配
android:pathPrefix="string" /> //限定路徑的初始部分後面能夠變化,只要初始部分符合便可受權
複製代碼
android:readPermission="com.alex.kotlin.job.provider.permission.READ_PERMISSION"
android:writePermission="com.alex.kotlin.job.provider.permission.WRITE_PERMISSION"
android:permission="com.alex.kotlin.job.provider.permission.PERMISSION"
複製代碼
使用一個實例驗證權限的使用,建立兩個程序A和B,在程序A中使用ContentProvider保存數據,在程序B中進行查詢,在開始A程序中不設置任何權限,B程序進行訪問數據,系統直接報錯:
報錯緣由也很直接,沒有權限訪問,此時是由於A程序中的Privider沒有支持其餘進程使用,修改A程序清單文件添加android:exported="true",再次訪問數據訪問成功: 從Log中能夠看出獲取的進程包爲「baselibrary",而提供數據的包爲「job.provider」,可見兩者並非同一個程序;android:writePermission="com.alex.kotlin.job.provider.WRITE"
android:readPermission="com.alex.kotlin.job.provider.READ"
複製代碼
在A程序的清單文件中,爲Provider添加兩個讀寫權限,添加完權限後再次在B程序中獲取數據,仍是會報錯,也很正常由於已經對數據的訪問設置了門檻,因此在B程序中聲明讀寫權限便可:
<uses-permission android:name="com.alex.kotlin.job.provider.READ"/>
<uses-permission android:name="com.alex.kotlin.job.provider.WRITE"/>
複製代碼
contentResolver.query(......)
複製代碼
ContentProvider的使用是經過ContentResolver實例進行操做的,因此工做原理分析從調用getContentResolver()獲取ContentResolver實例
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
複製代碼
getContentResolver() 獲取的是ContextImpl.ApplicationContentResolver()實例,而ApplicationContentResolver繼承了ContentResolver,本次對ContentProvider的分析以query()爲例,contentResolver.query(......)調用的是ContentResolver.query()
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri) {
IContentProvider unstableProvider = acquireUnstableProvider(uri);
qCursor = unstableProvider.query(mPackageName, uri, projection,
queryArgs, remoteCancellationSignal);
}
複製代碼
query()中首先調用acquireUnstableProvider(uri)獲取IContentProvider實例,acquireUnstableProvider中調用了ContentResolver.acquireUnstableProvider(),ApplicationContentResolver繼承了ContentResolver,此處實際執行的是ApplicationContentResolver.acquireUnstableProvider(),acquireUnstableProvider()中又調用ActivityThread.acquireProvider()
@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
return mMainThread.acquireProvider(c,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), false);
}
複製代碼
Context c, String auth, int userId, boolean stable) {
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
if (provider != null) {
return provider;
}
synchronized (getGetProviderLock(auth, userId)) {
holder = ActivityManager.getService().getContentProvider(
getApplicationThread(), auth, userId, stable);
}
複製代碼
acquireProvider中,首先從ArrayMap中獲取IContentProvider,若是獲取成功則直接返回,若ArrayMap中不存在則ActivityManagerService.getContentProvider啓動Provider,getContentProvide()中調用getContentProviderImpl()
if (proc != null && proc.thread != null && !proc.killed) {
if (!proc.pubProviders.containsKey(cpi.name)) {
proc.pubProviders.put(cpi.name, cpr);
try {
proc.thread.scheduleInstallProvider(cpi);
} catch (RemoteException e) {
}
}
} else {
proc = startProcessLocked(cpi.processName,
cpr.appInfo, false, 0, "content provider",
new ComponentName(cpi.applicationInfo.packageName,
cpi.name), false, false, false);
return null;
}
}
複製代碼
getContentProviderImpl()執行過程分兩步:
對於已啓動的進程直接調用Application.scheduleInstallProvider()啓動ContentProvider
public void scheduleInstallProvider(ProviderInfo provider) {
sendMessage(H.INSTALL_PROVIDER, provider); //發送Message信息,執行handleInstallProvider()
}
複製代碼
handleInstallProvider()中又調用installContentProviders()方法,對於應用進程已啓動的分析,先暫停此處,下面分析如下應用進程未啓動的情況,首先執行startProcessLocked(),啓動應用進程並初始化ContentProvider
應用進程啓動後調用ActivityThread.main(),初始化消息隊列,建立ActivityThread實例並調用attach()方法
ActivityThread thread = new ActivityThread();
thread.attach(false);
複製代碼
main方法中執行thread.attach()方法,attach()中又調用了IActivityManager.attachApplication(),ActivityManagerService 是IActivityManager的代理類,此處執行的ActivityManagerService.attachApplication(),attachApplication()中又調用attachApplicationLocked(),attachApplicationLocked中調用I Application.bindApplication()
thread.bindApplication(processName, appInfo, providers,
app.instr.mClass,
......
buildSerial, isAutofillCompatEnabled);
複製代碼
sendMessage(H.BIND_APPLICATION, data);
複製代碼
bindApplication()中發送Message消息,Handler接收消息後執行handleBindApplication()
private void handleBindApplication(AppBindData data) {
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);//1
try {
final ClassLoader cl = instrContext.getClassLoader();
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();//2
} catch (Exception e) {...}
final ComponentName component = new ComponentName(ii.packageName, ii.name);
mInstrumentation.init(this, instrContext, appContext, component,
data.instrumentationWatcher, data.instrumentationUiAutomationConnection);//3
Application app = data.info.makeApplication(data.restrictedBackupMode, null);//4
mInitialApplication = app;
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);//5
mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
}
}
mInstrumentation.callApplicationOnCreate(app);//6
}
複製代碼
執行工做過程:
上述過程執行啓動應用程序和初始化Application以後,調用 installContentProviders(),這裏和上面第一種狀況同樣都執行到installContentProviders方法,因此此處就接着第一種狀況一塊兒分析,在installContentProviders方法中回調用nstallProvider()
final java.lang.ClassLoader cl = c.getClassLoader(); //
LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
if (packageInfo == null) {
packageInfo = getSystemContext().mPackageInfo;
}
localProvider = packageInfo.getAppFactory()。//
.instantiateProvider(cl, info.name);
provider = localProvider.getIContentProvider();
localProvider.attachInfo(c, info); //
//localProvider.attachInfo
ContentProvider.this.onCreate(); //
複製代碼
執行過程:
到此ContentProvider的整個使用和工做過程就分析完了,較四大組件中其餘三個而言,ContentProvider的啓動狀況略微複雜,這也符合它跨進程跨程序的功能,以前很早就分析過它的工做過程,但沒有整理和輸出,經過此篇文章的編寫和分析,加深了對ContentProvider的使用和原理的理解。