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);
关于第一个应用程序就介绍到这里了,关于Sqlite数据库创建表那些就没介绍,自己百度吧。
第二个应用程序.
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(); } } }
这个应用程序相当于客户端程序,可以有很多这样的客户端程序去访问上面那个应用程序提供的联系人数据.
看上面贴的代码:
这里主要用到ContentResolver对象去访问数据
程序功能简单介绍一下
首先启动程序就去获取第一个程序里面所有联系人的信息,用一个listview显示,点击添加button进到另一个界面去添加联系人.添加完成后回到主界面就可以看到刚刚新加联系人,此时联系人信息已经存到第一个程序里面的数据库里面去了。
关于如何写自己的ContentProvider和使用ContentProvider上面两个应用程序已经简单的说明了.
接下主要通过源码来分析上面的过程.
从第二个应用程序的getContentResolver()函数开始:
第一步:getContentResolver()
在frameworks/base/core/Java/android/content/ContextWrapper.java文件中
这个方法是Activty的父类ContextWrapper中的.
@Override public ContentResolver getContentResolver() { return mBase.getContentResolver(); }
这里mBase变量是ContextImpl类型,是在创建activity的时候,new 一个ContextImpl对象,赋值给activity的.
第二步:getContentResolver()
在frameworks/base/core/java/android/app/ContextImpl.java文件中
@Override public ContentResolver getContentResolver() { return mContentResolver; }
这里直接返回了ContextImpl对象的成员变量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类的无参构造函数
ContextImpl() { mOuterContext = this; }
继续看它的init()函数
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; }
上面函数给mContentResolver赋值了.
来看看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); }
这里mMainThread是ActivityThread类型,在第二步中创建ApplicationContentResolver对象时作为它的构造参数传进去的.
第六步: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; } }
系统中所有正在加载的ContentProvider都保存在mLaunchingProviders成员变量中。在加载相应的ContentProvider之前,首先要判断一下它是可否正在被其它应用程序加载,如果是的话,就不用重复加载了。在我们这个情景中,没有其它应用程序也正在加载MyContentProvider这个Content
Provider,继续往前执行:
// 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); } }
这里的条件i >= N为true,就表明没有其它应用程序正在加载这个ContentProvider,因此,就要调用startProcessLocked函数来启动一个新的进程来加载这个ContentProvider对应的类了,然后把这个正在加载的信息增加到mLaunchingProviders中去。我们先接着分析这个函数,然后再来看在新进程中加载ContentProvider的过程,继续往下执行:
// 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的信息保存到mProviderMap中去,以方便后续查询。
因为我们需要获取的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; }
这个函数首先是根据传进来的进程ID找到相应的进程记录块,注意,这个进程ID是MyContentProvider所在程序的ID,然后对这个进程记录块做一些初倾始化的工作。再接下来通过调用generateApplicationProvidersLocked()获得需要在这个过程中加载的Content
Provider列表,在我们这个情景中,就只有MyContentProvider这个ContentProvider了。最后调用从参数传进来的IApplicationThread对象thread的bindApplication函数来执行一些应用程序初始化工作。
去到第四十二步:
第十步: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()来在本地安装每一个ContentProivder的信息,并且为每一个ContentProvider创建一个ContentProviderHolder对象来保存相关的信息。ContentProviderHolder对象是一个Binder对象,是用来把ContentProvider的信息传递给ActivityManagerService服务的。当这些ContentProvider都处理好了以后,还要调用ActivityManagerService服务的publishContentProviders()函数来通知ActivityManagerService服务这个进程中所要加载的ContentProvider都已经准备完毕了,而ActivityManagerService服务的publishContentProviders()函数的作用就是用来唤醒在前面第八步等待的线程的了。我们先来看installProvider()的实现,然后再来看ActivityManagerService服务的publishContentProviders()函数的实现。
第十二步: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();
接着通过调用localProvider(ContentProvider类型)的getIContentProvider()函数来获得一个Binder对象(IContentProvider类型),将这个Binder对象赋值给ContentProviderHolder对象的内部变量provider,将ContentProviderHolder返回,传到ActivityManagerService中去,后续其他应用程序就是通过获得这个ContentProviderHolder对象的内部IContentProvider对象来和相应的ContentProvider进行通信的了。我们先看一下这个函数的实现,然后再回到installProvider()函数中继续分析。
第十三步: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);
同样,我们先进入到ContentProvider类的attachInfo()函数去看看它的实现,然后再回到installProvider()函数来。
第十四步: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);
从这里得到的ContentProviderRecord对象dst,就是在前面第八步中创建的ContentProviderRecord对象cpr了。在for循环中,首先是把这个ContentProvider信息保存好在mProviderMap中:
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--; } }
最后,设置这个ContentProviderRecord对象dst的provider域为从参数传进来的IContentProvider远程接口:
synchronized (dst) { dst.provider = src.provider; dst.proc = r; dst.notifyAll(); }
执行了dst.notiryAll语句后,在第八步中等待要获取的ContentProvider接口加载完毕的线程就被唤醒了。唤醒之后,它检查本地ContentProviderRecord变量cpr的provider域不为null,于是就返回了。它最终返回到第六步中的ActivityThread类的acquireProvider()函数中,继续往下执行:
holder = installProvider(c, holder, holder.info, true /*noisy*/, holder.noReleaseNeeded, stable);
注意,这里是在第二个应用程序中进程中执行installProvider()函数的,而前面的第十二步的installProvider()函数是在第一个应用程序进程中执行的。
第十六步: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; }
同样是执行installProvider函数,与第十二步不同,这里传进来的参数provider是不为null的,因此,它不需要执行在本地加载ContentProvider的工作,只需要把从ActivityMangerService中获得的ContentProviderHolder对象中IContentProvider的保存在成员变量mProviderMap中就可以了。然后将ContentProviderHolder对象返回,回到第六步中的ActivityThread类的acquireProvider()函数中,将IContentProvider返回到第三步query()函数中,然后调用IContentProvider的query()函数,其实最终调用到了MyContentProvider类里面的query()函数了.
上面过程就是与"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()));
关于数据更新通知,下篇文章再细说.