Android四大组件之ContentProvider 使用及原理
ContentProvider作为Android四大组件之一,平时写自己的ContentProvider比较少,但是用到ContentProvider地方还是有的,比如去获取通讯录信息,这其实就间接的使用到了通讯录程序的ContentProvider组件.
先简单说一下ContentProvider组件.后面重点分析源码了解ContentProvider运行的过程.
ContentProvider可以实现在应用程序之间共享数据.
Android为常见的一些数据提供了默认的ContentProvider(包括音频、视频、图片和通讯录等)。 所以我们可以在其他应用程通过那些ContentProvider获取这些数据.
下面看一个简单的demo,了解一下在程序中我们如何写自己的ContentProvider,以及其他程序如何通过我们提供的ContentProvider来访问我们应用程序的数据.
- package com.cj.providerdemo2;
- import android.content.ContentProvider;
- import android.content.ContentResolver;
- import android.content.ContentUris;
- import android.content.ContentValues;
- import android.content.Context;
- import android.content.UriMatcher;
- import android.database.Cursor;
- import android.database.sqlite.SQLiteDatabase;
- import android.database.sqlite.SQLiteOpenHelper;
- import android.net.Uri;
- public class MyContentProvider extends ContentProvider {
- private final static int CONTACT = 1;
- private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- static {
- uriMatcher.addURI("com.cj.mycontentprovider","contact",CONTACT);
- }
- private MyDBHelp dbHelp;
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- int id =0;
- if(uriMatcher.match(uri) == CONTACT){
- SQLiteDatabase database = dbHelp.getWritableDatabase();
- id= database.delete("contact", selection, selectionArgs);
- contentResolver.notifyChange(uri,null);
- }
- return id;
- }
- @Override
- public String getType(Uri uri) {
- // TODO: Implement this to handle requests for the MIME type of the data
- // at the given URI.
- throw new UnsupportedOperationException("Not yet implemented");
- }
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- Uri u = null;
- if(uriMatcher.match(uri) == CONTACT){
- SQLiteDatabase database = dbHelp.getWritableDatabase();
- long d = database.insert("contact", "_id", values);
- u = ContentUris.withAppendedId(uri,d);
- contentResolver.notifyChange(u,null);
- }
- return u;
- }
- private ContentResolver contentResolver;
- @Override
- public boolean onCreate() {
- Context context = getContext();
- contentResolver = context.getContentResolver();
- dbHelp = new MyDBHelp(context,"contact.db",null,1);
- return true;
- }
- @Override
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- Cursor cursor = null;
- if(uriMatcher.match(uri) == CONTACT){
- SQLiteDatabase database = dbHelp.getReadableDatabase();
- cursor = database.query("contact", projection, selection, selectionArgs, null, null, sortOrder);
- cursor.setNotificationUri(contentResolver,uri);
- }
- return cursor;
- }
- @Override
- public int update(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- // TODO: Implement this to handle requests to update one or more rows.
- throw new UnsupportedOperationException("Not yet implemented");
- }
- /**
- *
- */
- private static class MyDBHelp extends SQLiteOpenHelper{
- public MyDBHelp(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
- super(context, name, factory, version);
- }
- @Override
- public void onCreate(SQLiteDatabase db) {
- String sql = "create table contact(_id integer primary key autoincrement," +
- "name text not null,number text not null);";
- db.execSQL(sql);
- }
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- onCreate(db);
- }
- }
- }
这个程序有一个存储联系人信息的数据库,由ContentProvider组件管理.这个应用程序相当于一个平台,向其他应用程序提供了访问平台数据(联系人信息)的接口,其他应程序可以通过这些接口获取联系人的信息,也可以向这个数据库添加、更新或者删除联系人,当然前提是遵守该平台的一些规则.
第一:首先得知道该平台的地址.这里也就是ContentProvider的身份证,因为所有的操作都是经过ContentProvider的.
看上面程序,自定义一个MyContentProvider,它继承ContentProvider,在清单配置文件里,ContentProvider有一个属性authorities,这就是ContentProvider的身份证,所以一般为了保证唯一性,使用包名的方式命名.
ContentProvider提供几个重要的函数,MyContentProvider必须重载,这些函数(inset,query,update,delete)顾名思义就知道是对数据的操作.
这里用了Android自带的Sqlite数据库来存储平台的数据(当然也可以用其他的方式存储),所以MyContentProvider重载的函数其实是对sqlite数据库的操作.
为了方便操作数据库,这里用到android sdk提供SQLiteOpenHelper这个类.
第二:就知道ContentProvider身份还不行,你还需要告诉ContentProvider具体要操作什么数据.
当然前提是平台已经将数据类型公开了,比如这里只有联系人信息数据.
数据是以Uri的形式向外公开的.
这里简单介绍一下ContentProvider规定数据形式的URI组成:
主要包括三个部分
一:scheme
ContentProvider(内容提供者)的scheme已经由Android所规定, scheme为:content://
二:主机名
主机名(或叫Authority)用于唯一标识这个ContentProvider,比如这里com.cj.mycontentprovider,外部调用者可以根据这个标识来找到它。
三:path
路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如上面程序:
要操作contact表/contact
上面三个部分是主要的,还可以有更详细的,比如还包含数据id /contact/id。
如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
Uri uri = Uri.parse("content://com.cj.mycontentprovider/contact").
然后再介绍一下UriMatcher类使用:
因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher和ContentUris 。掌握它们的使用,会便于我们的开发工作。
UriMatcher类用于匹配Uri,它的用法如下:
if(uriMatcher.match(uri) == CONTACT)
首先第一步把你需要匹配Uri路径全部给注册上,如下:uriMatcher.addURI("com.cj.mycontentprovider","contact",CONTACT);
注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用 addURI()方法传入的第三个参数。
再看一下ContentUris
ContentUris类用于操作Uri路径后面的ID部分,它有个比较实用的方法:
withAppendedId(uri, id)用于为路径加上ID部分:
u = ContentUris.withAppendedId(uri,d);
- package com.cj.providerdemo1;
- import android.app.Activity;
- import android.content.ContentResolver;
- import android.content.Context;
- import android.content.Intent;
- import android.database.ContentObserver;
- import android.database.Cursor;
- import android.net.Uri;
- import android.os.Bundle;
- import android.os.Handler;
- import android.support.v7.app.AppCompatActivity;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.AdapterView;
- import android.widget.Button;
- import android.widget.CursorAdapter;
- import android.widget.ListView;
- import android.widget.TextView;
- public class MainActivity extends AppCompatActivity {
- private ListView lv;
- private Button btn;
- private ContentResolver resolver;
- private MyAdapter myAdapter;
- private Cursor cursor;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- lv = (ListView) findViewById(R.id.lv);
- btn = (Button) findViewById(R.id.btn);
- resolver = getContentResolver();
- cursor = resolver.query(Uri.parse("content://com.cj.mycontentprovider/contact"), null, null,
- null, null);
- myAdapter = new MyAdapter(this,cursor);
- lv.setAdapter(myAdapter);
- btn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- MainActivity.this.startActivityForResult(new Intent(MainActivity.this,AddActivity.class),
- 1);
- }
- });
- String[] s = new String[2];
- lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- resolver.delete(Uri.parse("content://com.cj.mycontentprovider/contact"),
- null,null);
- }
- });
- resolver.registerContentObserver(Uri.parse("content://com.cj.mycontentprovider/contact"),
- true,new MyContentObserver(new Handler()));
- }
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- }
- private class MyContentObserver extends ContentObserver{
- /**
- * Creates a content observer.
- *
- * @param handler The handler to run {@link #onChange} on, or null if none.
- */
- public MyContentObserver(Handler handler) {
- super(handler);
- }
- @Override
- public void onChange(boolean selfChange) {
- myAdapter.notifyDataSetChanged();
- }
- }
- private static class MyAdapter extends CursorAdapter{
- private Cursor cursor;
- private Context context;
- public MyAdapter(Context context, Cursor c) {
- super(context, c);
- this.context = context;
- this.cursor = c;
- }
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- LayoutInflater layoutInflater = LayoutInflater.from(context);
- View view = layoutInflater.inflate(R.layout.item, null);
- if(cursor !=null){
- view.setTag(cursor.getInt(cursor.getColumnIndex("_id")));
- }
- return view;
- }
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- TextView name = (TextView) view.findViewById(R.id.tv_name);
- TextView num = (TextView) view.findViewById(R.id.tv_num);
- if(cursor !=null){
- name.setText(cursor.getString(cursor.getColumnIndex("name")));
- num.setText(cursor.getString(cursor.getColumnIndex("number")));
- }
- }
- @Override
- public long getItemId(int position) {
- return super.getItemId(position);
- }
- }
- }
- package com.cj.providerdemo1;
- import android.app.Activity;
- import android.content.ContentValues;
- import android.net.Uri;
- import android.support.v7.app.AppCompatActivity;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.EditText;
- import android.widget.Toast;
- public class AddActivity extends AppCompatActivity {
- private EditText et_name;
- private EditText et_num;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_add);
- et_name = (EditText) findViewById(R.id.et_name);
- et_num = (EditText) findViewById(R.id.et_num);
- }
- public void add(View view){
- String name = et_name.getText().toString();
- String num = et_num.getText().toString();
- if(name == null || num == null){
- Toast.makeText(this,"姓名和号码不能为空",Toast.LENGTH_SHORT).show();
- }else {
- ContentValues contentValues = new ContentValues();
- contentValues.put("name",name);
- contentValues.put("number",num);
- getContentResolver().insert(Uri.parse("content://com.cj.mycontentprovider/contact"),contentValues);
- setResult(Activity.RESULT_OK);
- finish();
- }
- }
- }
接下主要通过源码来分析上面的过程.
从第二个应用程序的getContentResolver()函数开始:
第一步:getContentResolver()
在frameworks/base/core/Java/android/content/ContextWrapper.java文件中
这个方法是Activty的父类ContextWrapper中的.
- @Override
- public ContentResolver getContentResolver() {
- return mBase.getContentResolver();
- }
第二步:getContentResolver()
在frameworks/base/core/java/android/app/ContextImpl.java文件中
- @Override
- public ContentResolver getContentResolver() {
- return mContentResolver;
- }
回到分析点击android桌面app图标启动应用程序的过程这篇文章看第五十步
在frameworks/base/core/java/android/app/ActivityThread.java中
- private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
- ....
- Activity activity = null;
- try {
- java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
- activity = mInstrumentation.newActivity(
- cl, component.getClassName(), r.intent);
- StrictMode.incrementExpectedActivityCount(activity.getClass());
- r.intent.setExtrasClassLoader(cl);
- if (r.state != null) {
- r.state.setClassLoader(cl);
- }
- } catch (Exception e) {
- ...
- }
- try {
- Application app = r.packageInfo.makeApplication(false, mInstrumentation);
- ...
- if (activity != null) {
- Context appContext = createBaseContextForActivity(r, activity);//创建contextimpl的地方
- CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
- Configuration config = new Configuration(mCompatConfiguration);
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
- + r.activityInfo.name + " with config " + config);
- activity.attach(appContext, this, getInstrumentation(), r.token,
- r.ident, app, r.intent, r.activityInfo, title, r.parent,
- r.embeddedID, r.lastNonConfigurationInstances, config);//这里将创建的contextimpl对象传给activity内部mBase变量
- ....
- return activity;
- }
上面通过createBaseContextForActivity()函数创建contextimpl对象,然后通过attach()函数将创建的contextimpl对象赋值给activity的内部变量,对应第一步的mBase变量. 这里attach()函数就没去分析了.
我们看创建contextimpl对象的地方,看createBaseContextForActivity()函数
- private Context createBaseContextForActivity(ActivityClientRecord r,
- final Activity activity) {
- ContextImpl appContext = new ContextImpl();
- appContext.init(r.packageInfo, r.token, this);
- appContext.setOuterContext(activity);
- .....
- return baseContext;
- }
- ContextImpl() {
- mOuterContext = this;
- }
- final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) {
- init(packageInfo, activityToken, mainThread, null, null, Process.myUserHandle());
- }
- final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread,
- Resources container, String basePackageName, UserHandle user) {
- mPackageInfo = packageInfo;
- mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName;
- mResources = mPackageInfo.getResources(mainThread);
- ......
- mMainThread = mainThread;
- mActivityToken = activityToken;
- mContentResolver = new ApplicationContentResolver(this, mainThread, user);//在这里给mContentResoler赋值的
- mUser = user;
- }
来看看ApplicationContentResolver这个类的结构
这样获得ContentResolver对象.
接着就分析它query()函数.
第三步:query()
在frameworks/base/core/java/android/content/ContentResolver.java文件中
- 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) {
- ..
- }
- IContentProvider stableProvider = null;
- try {
- ...
- Cursor qCursor;
- try {
- qCursor = unstableProvider.query(uri, projection,
- selection, selectionArgs, sortOrder, remoteCancellationSignal);//使用IContentProvider对象的query方法,最终会调用ContentProvider对象的query()方法
- } catch (DeadObjectException e) {
- ...
- }
- if (qCursor == null) {
- return null;
- }
- // force query execution
- 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;
- return wrapper;
- } catch (RemoteException e) {
- ..
- } finally {
- ...
- }
- }
query()函数首先调用了acquireUnstableProvider()函数,我们就先分析此函数
第四步:acquireUnstableProvider()在frameworks/base/core/java/android/content/ContentResolver.java文件中
- public final IContentProvider acquireUnstableProvider(Uri uri) {
- if (!SCHEME_CONTENT.equals(uri.getScheme())) {
- return null;
- }
- String auth = uri.getAuthority();
- if (auth != null) {
- return acquireUnstableProvider(mContext, uri.getAuthority());
- }
- return null;
- }
该函数返回一个IContentProvider类型对象.是不是有点奇怪,理论上应该返回ContentProvider对象才对,因为我们目的是要调ContentProvider的方法呀.
先看一下类图:
一个IContentProvider对应一个ContentProvider对象.
调用IContentProvider对象的方法最终会调到ContentProvider对象的方法.这里也就是MyContentProvider.继续看上面的函数体
函数首先验证参数uri的scheme是否正确,即是否是以content://开头,然后取出它的authority部分,最后调用另外一个成员函数acquireUnstableProvider执行获取IContentProvider的操作。在我们这个情景中,参数uri的authority的内容便是“com.cj.mycontentprovider”了
acquireUnstableProvider(Context context, String auth) 这个函数在ApplicationContentResolver类中
第五步:acquireUnstableProvider()
ApplicationContentResolver类是ContextImpl类的一个内部类
在frameworks/base/core/java/android/app/ContextImpl.java文件
- @Override
- protected IContentProvider acquireUnstableProvider(Context c, String auth) {
- return mMainThread.acquireProvider(c, auth, mUser.getIdentifier(), false);
- }
第六步:acquireProvider()
在frameworks/base/core/java/android/app/ActivityThread.java中
- 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.
- IActivityManager.ContentProviderHolder holder = null;
- try {
- holder = ActivityManagerNative.getDefault().getContentProvider(
- getApplicationThread(), auth, userId, stable);
- } catch (RemoteException ex) {
- }
- 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;
- }
这个函数首先会通过getExistingProvider()函数来检查本地是否已经存在这个要获取的IContentProvider,如果存在,就直接返回了。本地已经存在的IContentProvider保存在ActivityThread类的mProviderMap成员变量中,以ContentProvider对应的URI的authority为键值保存。在我们这个情景中,因为是第一次调用MyContentProvider,因此,这时候通过getExistingProvider()函数得到的IContentProvider为null,于是下面就会调用ActivityManagerService服务的getContentProvider()函数来获取一个ContentProviderHolder对象holder,这个对象就包含了我们所要获取的MyContentProvider对应的IContentProvider,在将IContentProvider返回给调用者之前,还会调用installProvider函数来把这个IContentProvider保存在本地中,以便下次要使用这个IContentProvider时,直接就可以通过getExistingProvider()函数获取了。
我们先进入到ActivityManagerService服务的getContentProvider函数中看看它是如何获取我们所需要的MyContentProvider对应的ContentProviderHolder对象的,然后再返回来看看installProvider()函数的实现。
这其中又是经过Binder驱动进行进程间通信,这过程就不写了,反正知道会调用到ActivityManagerService中的getContentProvider()函数,所以直接到ActivityManagerService中去.
第七步:getContentProvider()
在frameworks/base/services/java/com/android/server/am/ActivityManagerService.java.文件中
- public final ContentProviderHolder getContentProvider(
- IApplicationThread caller, String name, int userId, boolean stable) {
- ...
- if (caller == null) {
- ....
- }
- ....
- return getContentProviderImpl(caller, name, null, stable, userId);
- }
第八步:getContentProviderImpl()
在frameworks/base/services/java/com/android/server/am/ActivityManagerService.java.文件
- private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
- String name, IBinder token, boolean stable, int userId) {
- ContentProviderRecord cpr;
- ContentProviderConnection conn = null;
- ProviderInfo cpi = null;
- synchronized(this) {
- ProcessRecord r = null;
- if (caller != null) {
- r = getRecordForAppLocked(caller);
- if (r == null) {
- ....
- }
- }
- // First check if this content provider has been published...
- cpr = mProviderMap.getProviderByName(name, userId);
- boolean providerRunning = cpr != null;
- if (providerRunning) {
- ....
- }
- boolean singleton;
- if (!providerRunning) {
- try {
- cpi = AppGlobals.getPackageManager().
- resolveContentProvider(name,
- STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
- } catch (RemoteException ex) {
- }
- if (cpi == null) {
- ...
- }
- ...
- ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
- cpr = mProviderMap.getProviderByClass(comp, userId);
- final boolean firstClass = cpr == null;
- if (firstClass) {
- try {
- ApplicationInfo ai =
- AppGlobals.getPackageManager().
- getApplicationInfo(
- cpi.applicationInfo.packageName,
- STOCK_PM_FLAGS, userId);
- if (ai == null) {
- ...
- }
- ai = getAppInfoForUser(ai, userId);
- cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
- } catch (RemoteException ex) {
- // pm is in same process, this will never happen.
- }
- }
- ...
- // This is single process, and our app is now connecting to it.
- // See if we are already in the process of launching this
- // provider.
- final int N = mLaunchingProviders.size();
- int i;
- for (i=0; i<N; i++) {
- if (mLaunchingProviders.get(i) == cpr) {
- break;
- }
- }
- // If the provider is not already being launched, then get it
- // started.
- if (i >= N) {
- final long origId = Binder.clearCallingIdentity();
- try {
- // Content provider is now in use, its package can't be stopped.
- try {
- AppGlobals.getPackageManager().setPackageStoppedState(
- cpr.appInfo.packageName, false, userId);
- } catch (RemoteException e) {
- } catch (IllegalArgumentException e) {
- ..
- }
- ProcessRecord proc = startProcessLocked(cpi.processName,
- cpr.appInfo, false, 0, "content provider",
- new ComponentName(cpi.applicationInfo.packageName,
- cpi.name), false, false);
- if (proc == null) {
- ...
- return null;
- }
- cpr.launchingApp = proc;
- mLaunchingProviders.add(cpr);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
- // Make sure the provider is published (the same provider class
- // may be published under multiple names).
- if (firstClass) {
- mProviderMap.putProviderByClass(comp, cpr);
- }
- mProviderMap.putProviderByName(name, cpr);
- conn = incProviderCountLocked(r, cpr, token, stable);
- if (conn != null) {
- conn.waiting = true;
- }
- }
- }
- // Wait for the provider to be published...
- synchronized (cpr) {
- while (cpr.provider == null) {
- if (cpr.launchingApp == null) {
- ...
- return null;
- }
- try {
- ...
- if (conn != null) {
- conn.waiting = true;
- }
- cpr.wait();
- } catch (InterruptedException ex) {
- } finally {
- ...
- }
- }
- }
- return cpr != null ? cpr.newHolder(conn) : null;
- }
这个函数比较长,我们一步一步地分析。
函数首先是获取调用者的进程记录块信息:
- ProcessRecord r = null;
- if (caller != null) {
- r = getRecordForAppLocked(caller);
- if (r == null) {
- ...
- }
- }
在我们这个情景中,要获取的就是第二个应用程序的进程记录块信息了,后面会用到。
在ActivityManagerService中,用mProviderMap保存系统中的ContentProvider信息的。这里ContentProviderRecord对象就封装了ContentProvider的相关信息,下面的代码就是用来检查要获取的ContentProvider是否已经加存在的了:
- cpr = mProviderMap.getProviderByName(name, userId);
- boolean providerRunning = cpr != null;
- if (providerRunning) {
- ....
- }
- boolean singleton;
- if (!providerRunning) {
- try {
- cpi = AppGlobals.getPackageManager().
- resolveContentProvider(name,
- STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
- } catch (RemoteException ex) {
- }
- if (cpi == null) {
- return null;
- }
- .
- cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
- ...
- ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
- cpr = mProviderMap.getProviderByClass(comp, userId);
- final boolean firstClass = cpr == null;
- if (firstClass) {
- try {
- ApplicationInfo ai =
- AppGlobals.getPackageManager().
- getApplicationInfo(
- cpi.applicationInfo.packageName,
- STOCK_PM_FLAGS, userId);
- if (ai == null) {
- ...
- }
- ai = getAppInfoForUser(ai, userId);
- cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
- } catch (RemoteException ex) {
- ..
- }
- }
在我们这个情景中,由于是第一次调用MyContentProvider,因此,在mProviderMap中不存在MyContentProvider的相关信息,因此,这里会通过AppGlobals.getPackageManager函数来获得PackageManagerService服务接口,然后分别通过它的resolveContentProvider和getApplicationInfo函数来分别获取MyContentProvider所在应用程序的相关信息,分别保存在cpi和cpr这两个本地变量中。这些信息都是在安装应用程序的过程中保存下来的.
- // This is single process, and our app is now connecting to it.
- // See if we are already in the process of launching this
- // provider.
- final int N = mLaunchingProviders.size();
- int i;
- for (i=0; i<N; i++) {
- if (mLaunchingProviders.get(i) == cpr) {
- break;
- }
- }
- // If the provider is not already being launched, then get it
- // started.
- if (i >= N) {
- try {
- // Content provider is now in use, its package can't be stopped.
- try {
- AppGlobals.getPackageManager().setPackageStoppedState(
- cpr.appInfo.packageName, false, userId);
- } catch (RemoteException e) {
- } catch (IllegalArgumentException e) {
- ....
- }
- ProcessRecord proc = startProcessLocked(cpi.processName,
- cpr.appInfo, false, 0, "content provider",
- new ComponentName(cpi.applicationInfo.packageName,
- cpi.name), false, false);
- if (proc == null) {
- ...
- }
- cpr.launchingApp = proc;
- mLaunchingProviders.add(cpr);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
- // Make sure the provider is published (the same provider class
- // may be published under multiple names).
- if (firstClass) {
- mProviderMap.putProviderByClass(comp, cpr);
- }
- mProviderMap.putProviderByName(name, cpr);
- conn = incProviderCountLocked(r, cpr, token, stable);
- if (conn != null) {
- conn.waiting = true;
- }
因为我们需要获取的ContentProvider是在新的进程中加载的,而getContentProviderImpl()这个函数是在系统进程中执行的,它必须要等到要获取的ContentProvider是在新的进程中加载完成后才能返回,这样就涉及到进程同步的问题了。这里使用的同步方法是不断地去检查变量cpr的provider域是否被设置了。当要获取的ContentProvider在新的进程加载完成之后,它会通过Binder进程间通信机制调用到系统进程中,把这个cpr变量的provider域设置为已经加载好的IContentProvider接口,这时候,函数getContentProviderImpl()就可以返回了。下面的代码就是用来等待要获取的ContentProvider,是在新的进程中加载完成的:
- // Wait for the provider to be published...
- synchronized (cpr) {
- while (cpr.provider == null) {
- if (cpr.launchingApp == null) {
- ...
- return null;
- }
- try {
- ..
- if (conn != null) {
- conn.waiting = true;
- }
- cpr.wait();
- } catch (InterruptedException ex) {
- } finally {
- ...
- }
- }
- }
- return cpr != null ? cpr.newHolder(conn) : null;
cpr就是ContentProviderRecord,它的provider域就是IContentProvider,看一下类图
下面我们再分析在新进程中加载MyContentProvider这个ContentProvider的过程。其实就是启动demo的第一个应用程序.
这里我们参考分析点击android桌面app图标启动应用程序的过程这篇文章,其中就是在新的进程启动第一个activity,与这里启动ContentProvider差不多.
所以参考文章中的第三十一步至第四十二步
首先看第三十七步
第九步:attachApplication()
在ActivityManagerService.java中
- 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;
- .....
- boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
- List providers = normalMode ? generateApplicationProvidersLocked(app) : null;
- ....
- thread.bindApplication(processName, appInfo, providers,
- app.instrumentationClass, profileFile, profileFd, profileAutoStop,
- app.instrumentationArguments, app.instrumentationWatcher, testMode,
- enableOpenGlTrace, isRestrictedBackupMode || !normalMode, app.persistent,
- new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
- mCoreSettingsObserver.getCoreSettingsLocked());
- updateLruProcessLocked(app, false);
- app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
- } catch (Exception e) {
- ...
- }
- ...
- ActivityRecord hr = mMainStack.topRunningActivityLocked(null);
- .....
- return true;
- }
去到第四十二步:
第十步:handleBindApplication()
在ActivityThread.java中
- private void handleBindApplication(AppBindData data) {
- ....
- try {
- .....
- if (!data.restrictedBackupMode) {
- List<ProviderInfo> providers = data.providers;
- if (providers != null) {
- installContentProviders(app, providers);//安装contentprovider
- .....
- }
- }
- // 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) {
- ..
- }
- try {
- mInstrumentation.callApplicationOnCreate(app);
- } catch (Exception e) {
- ...
- }
- } finally {
- StrictMode.setThreadPolicy(savedPolicy);
- }
- }
这个函数的内容比较多,我们忽略了其它无关的部分,只关注和ContentProvider有关的逻辑,这里主要就是调用installContentProviders函数来在本地安装ContentProviders信息。
第十一步:installContentProviders()
在ActivityThread.java中
- 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) {
- }
- }
第十二步:installProvider()
在ActivityThread.java中
- private IActivityManager.ContentProviderHolder installProvider(Context context,
- IActivityManager.ContentProviderHolder holder, ProviderInfo info,
- boolean noisy, boolean noReleaseNeeded, boolean stable) {
- ContentProvider localProvider = null;
- IContentProvider provider;
- if (holder == null || holder.provider == null) {
- ...
- Context c = null;
- ApplicationInfo ai = info.applicationInfo;
- if (context.getPackageName().equals(ai.packageName)) {
- c = context;
- } else if (mInitialApplication != null &&
- mInitialApplication.getPackageName().equals(ai.packageName)) {
- ..
- } else {
- ....
- }
- ...
- try {
- final java.lang.ClassLoader cl = c.getClassLoader();
- localProvider = (ContentProvider)cl.
- loadClass(info.name).newInstance();
- provider = localProvider.getIContentProvider();
- <span style="white-space:pre"> </span> localProvider.attachInfo(c, info);
- } catch (java.lang.Exception e) {
- ...
- }
- } else {
- ...
- }
- IActivityManager.ContentProviderHolder retHolder;
- synchronized (mProviderMap) {
- ...
- IBinder jBinder = provider.asBinder();
- if (localProvider != null) {
- ComponentName cname = new ComponentName(info.packageName, info.name);
- ProviderClientRecord pr = mLocalProvidersByName.get(cname);
- if (pr != null) {
- ...
- } else {
- holder = new IActivityManager.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 {
- ..
- }
- return retHolder;
这个函数的作用主要就是在应用程序进程中把相应的ContentProvider类加载进来了,在我们这个种情景中,就是要在MyContentProvider所在应用程序中把MyContentProvider这个ContentProvider类加载到内存中来了:
- final java.lang.ClassLoader cl = c.getClassLoader();
- localProvider = (ContentProvider)cl.
- loadClass(info.name).newInstance();
第十三步:getIContentProvider()
在ContentProvider.java中
- public IContentProvider getIContentProvider() {
- return mTransport;
- }
- private Transport mTransport = new Transport();
从这里我们可以看出,ContentProvider类和Transport类的关系就类似于ActivityThread和ApplicationThread的关系,其它应用程序不是直接调用ContentProvider接口来访问它的数据,而是通过调用它的内部对象mTransport来间接调用ContentProvider的接口.
回到前面的installProvider()函数中,它接下来调用下面接口来初始化刚刚加载好的ContentProvider:
- localProvider.attachInfo(c, info);
第十四步:attachInfo()
在ContentProvider.java中
- public void attachInfo(Context context, ProviderInfo info) {
- /*
- * We may be using AsyncTask from binder threads. Make it init here
- * so its static handler is on the main thread.
- */
- AsyncTask.init();
- /*
- * Only allow it to be set once, so after the content service gives
- * this to us clients can't change it.
- */
- if (mContext == null) {
- mContext = context;
- mMyUid = Process.myUid();
- if (info != null) {
- setReadPermission(info.readPermission);
- setWritePermission(info.writePermission);
- setPathPermissions(info.pathPermissions);
- mExported = info.exported;
- }
- ContentProvider.this.onCreate();
- }
- }
这个函数很简单,主要就是根据这个Content Provider的信息info来设置相应的读写权限,然后调用它的子类的onCreate函数来让子类执行一些初始化的工作。在我们这个情景中,这个子类就是MyContentProvider所在应用程序中的MyContentProvider类了。
回到前面第十二步的installProvider()函数中,它接下来就是把这些在本地中加载的ContentProvider信息保存下来了,以方便后面查询和使用,是在
installProviderAuthoritiesLocked()这个方法里面去执行的
- ...
- pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
- ..
- private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
- ContentProvider localProvider, IActivityManager.ContentProviderHolder holder) {
- final String auths[] = PATTERN_SEMICOLON.split(holder.info.authority);
- final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
- final ProviderClientRecord pcr = new ProviderClientRecord(
- auths, provider, localProvider, holder);
- for (String auth : auths) {
- final ProviderKey key = new ProviderKey(auth, userId);
- final ProviderClientRecord existing = mProviderMap.get(key);
- if (existing != null) {
- Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
- + " already published as " + auth);
- } else {
- mProviderMap.put(key, pcr);
- }
- }
- return pcr;
- }
函数installProvider()执行完成以后,返回到第十一步中的instalContentProviders()函数中,执行下面语句:
- ActivityManagerNative.getDefault().publishContentProviders(
- getApplicationThread(), results);
前面已经提到,这个函数调用的作用就是通知ActivityMangerService,需要在这个进程中加载的Content Provider已经完加载完成了,参数results就包含了这些已经加载好的ContentProvider接口。
第十五步:publishContentProviders()
在frameworks/base/services/java/com/android/server/am/ActivityManagerService.java文件中
- public final void publishContentProviders(IApplicationThread caller,
- List<ContentProviderHolder> providers) {
- if (providers == null) {
- ..
- }
- synchronized (this) {
- final ProcessRecord r = getRecordForAppLocked(caller);
- ..
- if (r == null) {
- ..
- }
- ...
- final int N = providers.size();
- for (int i=0; i<N; i++) {
- ContentProviderHolder src = providers.get(i);
- if (src == null || src.info == null || src.provider == null) {
- continue;
- }
- 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);
- }
- int NL = mLaunchingProviders.size();
- int j;
- for (j=0; j<NL; j++) {
- if (mLaunchingProviders.get(j) == dst) {
- mLaunchingProviders.remove(j);
- j--;
- NL--;
- }
- }
- synchronized (dst) {
- dst.provider = src.provider;
- dst.proc = r;
- dst.notifyAll();
- }
- updateOomAdjLocked(r);
- }
- }
- Binder.restoreCallingIdentity(origId);
- }
- }
在我们这个情景中,只有一个ContentProvider,因此,这里的N等于1。在中间的for循环里面,最重要的是下面这个语句:
- ContentProviderRecord dst = r.pubProviders.get(src.info.name);
- 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);
- }
因为这个Content Provider已经加载好了,因此,把它从mLaunchingProviders列表中删除:
- int NL = mLaunchingProviders.size();
- int j;
- for (j=0; j<NL; j++) {
- if (mLaunchingProviders.get(j) == dst) {
- mLaunchingProviders.remove(j);
- j--;
- NL--;
- }
- }
- synchronized (dst) {
- dst.provider = src.provider;
- dst.proc = r;
- dst.notifyAll();
- }
- holder = installProvider(c, holder, holder.info,
- true /*noisy*/, holder.noReleaseNeeded, stable);
第十六步:installProvider()
在frameworks/base/core/java/android/app/ActivityThread.java文件中:
- private IActivityManager.ContentProviderHolder installProvider(Context context,
- IActivityManager.ContentProviderHolder holder, ProviderInfo info,
- boolean noisy, boolean noReleaseNeeded, boolean stable) {
- ContentProvider localProvider = null;
- IContentProvider provider;
- if (holder == null || holder.provider == null) {
- ....
- } else {
- provider = holder.provider;
- ....
- }
- IActivityManager.ContentProviderHolder retHolder;
- synchronized (mProviderMap) {
- if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
- + " / " + info.name);
- IBinder jBinder = provider.asBinder();
- if (localProvider != null) {
- ...
- } else {
- ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
- if (prc != null) {
- ....
- } 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;
- }
上面过程就是与"com.cj.mycontentprovider"这个uri对应的ContentProvider(MyContentProvider)通信的过程,其他几个函数(inset(),delete()..)都是同样的过程.
上面的使用ContentProvider的过程中,还用到数据更新,例如我在第二个应用程序中添加到一个联系人后,回到前面一个界面,数据会刷新.- contentResolver.notifyChange(u,null);
- resolver.registerContentObserver(Uri.parse("content://com.cj.mycontentprovider/contact"),
- true,new MyContentObserver(new Handler()));