Reprint address: http://blog.csdn.net/u010961631/article/details/14227421
I. Preface
ContentProvider, as one of the four components of Android, plays the role of data storage. This paper uses the most typical delete operation, according to Android source code, starting from the application layer getContentResolver(), step by step analysis to the content Provider, and finally to the operation of SQLite.
-
getContentResolver().delete();
This simple operation actually involves two steps:
1. Get the required ContentProvider object through getContentResolver();
2. Delete the data in ContentProvider by delete().
Next we will embark on this long journey.
getContentResolver
Look at the title, we want to get an object of ContentResolver, which raises two questions:
1. What is the relationship between ContentResolver object and ContentProvider?
2. How do we get the content in ContentProvider?
To answer these two questions, we need to start from the source of the code.
2.1. Relationship between ContentResolver and ContentProvider
Let's first look at the process of getting ContentResolver:
-
@ContextImpl.java
-
public ContentResolver getContentResolver() {
-
return mContentResolver;
-
}
The mContentResolver here is initialized in the init of ContextImpl:
-
final void init(Resources resources, ActivityThread mainThread, UserHandle user) {
-
mPackageInfo = null;
-
mBasePackageName = null;
-
mResources = resources;
-
mMainThread = mainThread;
-
-
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
-
mUser = user;
-
}
mContentResolver is initialized as an ApplicationContentResolver object. Let's look at the class ApplicationContentResolver, which is the internal class of ContextImpl:
-
private static final class ApplicationContentResolver extends ContentResolver {
-
public ApplicationContentResolver( Context context, ActivityThread mainThread, UserHandle user) { }
-
protected IContentProvider acquireProvider(Context context, String auth) { }
-
protected IContentProvider acquireExistingProvider(Context context, String auth) { }
-
public boolean releaseProvider(IContentProvider provider) { }
-
protected IContentProvider acquireUnstableProvider(Context c, String auth) { }
-
public boolean releaseUnstableProvider(IContentProvider icp) { }
-
public void unstableProviderDied(IContentProvider icp) { }
-
}
He provides various ways to get IContentProvider and inherits from the ContentResolver class. Let's look at the properties of this class:
-
@ContentResolver.java
-
public abstract class ContentResolver { }
As you can see, ContentResolver does not inherit any classes or interfaces, so we can assume that ContentResolver has nothing to do with ContentProvider in terms of inheritance.
How did we finally get the ContentProvider?
2.2. How to get ContentProvider through ContentResolver
Next, let's answer the second question, how do we query the content in the Content Provider through the Content Resolver?
Although we know that there is no connection between the two in terms of inheritance, there are some familiar methods within ContentResolver, including:
-
public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {}
-
public final int delete(Uri url, String where, String[] selectionArgs){}
-
public final int update(Uri uri, ContentValues values, String where, String[] selectionArgs) {}
-
public final Uri insert(Uri url, ContentValues values){}
This shows that there must be a functional connection between the two. Let's start with the delete operation and see what the relationship is.
Back to the original invocation location, when we need to delete a data through ContentProvider, we call the getContentResolver().delete() method. The above analysis shows that getContentResolver() gets the object of Application ContentResolver, and Application ContentResolver inherits from ContentResol. Ver. Therefore, the delete operation falls into ContentResolver:
-
@ContentResolver.java
-
public final int delete(Uri url, String where, String[] selectionArgs)
-
{
-
IContentProvider provider = acquireProvider(url);
-
try {
-
int rowsDeleted = provider.delete(url, where, selectionArgs);
-
} catch (RemoteException e) {
-
} finally {
-
}
-
}
In this step, we first get the IContentProvider object through acquireProvider(), and then call the delete method of the IContentProvider object for operation. So how do I get the IContentProvider object through acquireProvider()?
-
public final IContentProvider acquireProvider(Uri uri) {
-
-
if (!SCHEME_CONTENT.equals(uri.getScheme())) {
-
return null;
-
}
-
final String auth = uri.getAuthority();
-
if (auth != null) {
-
-
return acquireProvider(mContext, auth);
-
}
-
return null;
-
}
Continue to see:
-
protected abstract IContentProvider acquireProvider(Context c, String name);
The Abstract acquireProvider is encountered here, which indicates that this method needs to be implemented in subclasses, so let's go to Application Content Resolver and see:
-
@ContextImpl.java
-
private static final class ApplicationContentResolver extends ContentResolver {
-
-
protected IContentProvider acquireProvider(Context context, String auth) {
-
return mMainThread.acquireProvider(context, auth, mUser.getIdentifier(), true);
-
}
-
}
We did find the acquireProvider() method in Application Content Resolver, and found that the return value provided by the acquireProvider method was from the acquireProvider method of the mMainThread object.
So where did this mMainThread come from?
The process described in the following section is rather complicated, you have a good look.
2.3. Looking for the origin of mMainThread object
After creating an Activity in ActivityThread, the createBaseContextForActivity() method in ActivityThread is invoked to create Context objects and mMainThread objects for the current Activity:
-
@ActivityThread.java
-
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
-
-
ContextImpl appContext = new ContextImpl();
-
-
appContext.init(r.packageInfo, r.token, this);
-
}
At this point, the init method in ContextImpl will be invoked:
-
@ContextImpl.java
-
-
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){
-
-
mMainThread = mainThread;
-
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
-
}
In this process, we find that mMainThread is an ActivityThread object, so the mMainThread.acquireProvider() we introduced in 2.2 is equivalent to:
-
ActivityThread.acquireProvider();
Let's continue to see:
-
@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;
-
}
-
-
IActivityManager.ContentProviderHolder holder = null;
-
try {
-
holder = ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(), auth, userId, stable);
-
} catch (RemoteException ex) {
-
}
-
-
holder = installProvider(c, holder, holder.info, true , holder.noReleaseNeeded, stable);
-
return holder.provider;
-
}
As you can see here, there is a HashMap in ActivityThread that caches all providers. Each request to a provider will first query whether it has been cached by acquireExistingProvider(), and if there is no caching, create the ContentProvider Holder object of the provider, and cache it to facilitate the next call.
Assuming that there is no ContactsProvider cache at present, the provider will be created through ActivityManager Native. getDefault (). getContentProvider (). Let's analyze the process of creating this provider.
2.4. ActivityThread's process of creating ContentProvider.
As mentioned earlier, ActivityThread creates ContentProvider by:
-
ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(), auth, userId, stable);
The process can be divided into two steps:
1. Let's look at the objects obtained through ActivityManagerNative.getDefault().
2. Let's analyze the getContentProvider method of this object.
2.4.1, ActivityManagerNative object.
Let's first introduce the Activity Manager Service service.
When the system is started, some important Servers will be started in System Server, including Activity Manager Service:
-
@SystemServer.java
-
public void run() {
-
-
context = ActivityManagerService.main(factoryTest);
-
-
ActivityManagerService.setSystemProcess();
-
}
The result of the above call to the ActivityManagerService.main method is to start the ActivityManagerService and then call setSystemProcess to register the ActivityManagerService to the system:
-
@ActivityManagerService.java
-
public static void setSystemProcess() {
-
try {
-
ActivityManagerService m = mSelf;
-
-
ServiceManager.addService("activity", m, true);
-
ServiceManager.addService("meminfo", new MemBinder(m));
-
ServiceManager.addService("gfxinfo", new GraphicsBinder(m));
-
ServiceManager.addService("dbinfo", new DbBinder(m));
-
} catch (PackageManager.NameNotFoundException e) {
-
}
-
}
Let's look at the inheritance relationship of Activity Manager Service:
-
public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {}
Explain that the Server inherits from Activity Manager Native, and that the parent class mainly performs some Binder operations.
In this way, we have a general understanding of the structure of Activity Manager Native. According to the Binder communication mechanism, if we request the service of Activity Manager Service across processes, we will call his asBinder() method and return his IBinder object to the client for use.
Next, in our analysis above, we divide the creation of ContentProvider into two steps in Section 2.4. The first step is to get the ActivityManager Native. getDefault () object. Now let's look at the source code:
-
@ActivityManagerNative.java
-
static public IActivityManager getDefault() {
-
return gDefault.get();
-
}
Let's see, the result of calling the getDefault() method of ActivityManager Native is to get the data in the gDefault object, so what kind of data is there in the gDefault? Let's look at his definition:
-
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
-
protected IActivityManager create() {
-
IBinder b = ServiceManager.getService("activity");
-
IActivityManager am = asInterface(b);
-
return am;
-
}
-
};
Originally, in the process of initializing gDefault variables (create), two important operations were completed:
1. Get the remote Binder of Activity Manager Service through getService("activity");
2. The remote proxy of the service is obtained by using the obtained Biner object through the asInterface() method.
For getting Binder objects, this is determined by the Binder system. We don't need to look at it much. Let's see how to get a remote agent for services through Binder:
-
static public IActivityManager asInterface(IBinder obj) {
-
if (obj == null) {
-
return null;
-
}
-
IActivityManager in = (IActivityManager)obj.queryLocalInterface(descriptor);
-
if (in != null) {
-
return in;
-
}
-
return new ActivityManagerProxy(obj);
-
}
Through the details of asInterface(), we find that the remote proxy object we get is the ActivityManagerProxy object. That is to say, we originally:
-
ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(), auth, userId, stable);
In fact, it is equivalent to:
-
ActivityManagerProxy.getContentProvider(getApplicationThread(), auth, userId, stable);;
Now that we have figured out the first step mentioned in 2.4, let's analyze the second one.
2.4.2. getContentProvider operation of proxy object
This step, of course, occurs in the ActivityManagerProxy class:
-
@ActivityManagerNative.java
-
class ActivityManagerProxy implements IActivityManager{
-
public ContentProviderHolder getContentProvider(IApplicationThread caller,
-
String name, int userId, boolean stable) throws RemoteException {
-
Parcel data = Parcel.obtain();
-
Parcel reply = Parcel.obtain();
-
data.writeInterfaceToken(IActivityManager.descriptor);
-
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
-
data.writeString(name);
-
data.writeInt(userId);
-
data.writeInt(stable ? 1 : 0);
-
-
mRemote.transact(GET_CONTENT_PROVIDER_TRANSACTION, data, reply, 0);
-
reply.readException();
-
int res = reply.readInt();
-
ContentProviderHolder cph = null;
-
if (res != 0) {
-
cph = ContentProviderHolder.CREATOR.createFromParcel(reply);
-
}
-
data.recycle();
-
reply.recycle();
-
return cph;
-
}
-
}
We see that in the process of getContentProvider, Activity Manager Proxy sends requests to ActiveyManager Native at the far end through mRemote, and the request code is GET_CONTENT_PROVIDER_TRANSACTION.
Activity Manager Native needs to receive requests in onTransact():
-
@ActivityManagerNative.java
-
public abstract class ActivityManagerNative extends Binder implements IActivityManager{
-
-
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
-
switch (code) {
-
case GET_CONTENT_PROVIDER_TRANSACTION: {
-
data.enforceInterface(IActivityManager.descriptor);
-
IBinder b = data.readStrongBinder();
-
IApplicationThread app = ApplicationThreadNative.asInterface(b);
-
String name = data.readString();
-
int userId = data.readInt();
-
boolean stable = data.readInt() != 0;
-
-
ContentProviderHolder cph = getContentProvider(app, name, userId, stable);
-
reply.writeNoException();
-
if (cph != null) {
-
reply.writeInt(1);
-
cph.writeToParcel(reply, 0);
-
} else {
-
reply.writeInt(0);
-
}
-
return true;
-
}
-
}
-
}
-
}
In onTransact, the request is sent to the real server-side ActivityManagerService.getContentProvider():
-
@ActivityManagerService.java
-
public final ContentProviderHolder getContentProvider( IApplicationThread caller, String name, int userId, boolean stable) {
-
return getContentProviderImpl(caller, name, null, stable, userId);
-
}
Continue to see how the server operates:
-
private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller, String name, IBinder token, boolean stable, int userId) {
-
ContentProviderRecord cpr;
-
ContentProviderConnection conn = null;
-
ProviderInfo cpi = null;
-
-
synchronized(this) {
-
-
cpr = mProviderMap.getProviderByName(name, userId);
-
boolean providerRunning = cpr != null;
-
if (providerRunning) {
-
-
-
cpi = cpr.info;
-
if (r != null && cpr.canRunHere(r)) {
-
-
ContentProviderHolder holder = cpr.newHolder(null);
-
-
holder.provider = null;
-
return holder;
-
}
-
}
-
-
if (!providerRunning) {
-
-
-
cpi = AppGlobals.getPackageManager().resolveContentProvider(name, STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
-
-
-
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
-
cpr = mProviderMap.getProviderByClass(comp, userId);
-
-
-
mProviderMap.putProviderByName(name, cpr);
-
conn = incProviderCountLocked(r, cpr, token, stable);
-
}
-
}
-
-
return cpr != null ? cpr.newHolder(conn) : null;
-
}
In the above process, we found that there is a mProviderMap variable in the Activity Manager Service to save all the provider s in the current system, each of which is a ContentProviderRecord-type data, through which two important functions can be accomplished:
1. Details of the current provider can be obtained through ContentProviderRecord.info, including the authority, readPermission, writePermission, uriPermission Patterns and other important information of the provider.
2. ContentProviderRecord.newHolder() method can be used to generate a detailed information for the current provider, which we need to package as Holder and pass to ActivityThread for use.
Let's go back to acquireProvider() in ActivityThread:
-
public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) {
-
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
-
-
IActivityManager.ContentProviderHolder holder = null;
-
try {
-
-
holder = ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(), auth, userId, stable);
-
} catch (RemoteException ex) {
-
}
-
-
-
holder = installProvider(c, holder, holder.info, true , holder.noReleaseNeeded, stable);
-
-
return holder.provider;
-
}
In the previous process, we obtained the ContentProvider Holder object through Activity Manager Service, which is a detailed description of the provider. Next, we need to use these descriptions to open the provider in the installProvider() method and obtain the proxy object of the provider.
-
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)) {
-
c = mInitialApplication;
-
} else {
-
try {
-
-
c = context.createPackageContext(ai.packageName, Context.CONTEXT_INCLUDE_CODE);
-
} catch (PackageManager.NameNotFoundException e) {
-
}
-
}
-
try {
-
final java.lang.ClassLoader cl = c.getClassLoader();
-
-
localProvider = (ContentProvider)cl.loadClass(info.name).newInstance();
-
-
provider = localProvider.getIContentProvider();
-
localProvider.attachInfo(c, info);
-
} catch (java.lang.Exception e) {
-
}
-
} else {
-
-
provider = holder.provider;
-
}
-
-
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) {
-
provider = pr.mProvider;
-
} 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 {
-
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
-
if (prc != null) {
-
if (!noReleaseNeeded) {
-
incProviderRefLocked(prc, stable);
-
try {
-
ActivityManagerNative.getDefault().removeContentProvider( holder.connection, stable);
-
} catch (RemoteException e) {
-
}
-
}
-
} 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;
-
}
We clearly see that in the installProvider method, we use the ContentProvider information obtained in the Activity Manager Service to load the ContentProvider (local Provider) and call his getIContentProvider() method to get the proxy object of the provider.
Then we go back to acquireProvider in ActivityThread ():
-
public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) {
-
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
-
IActivityManager.ContentProviderHolder holder = null;
-
try {
-
-
holder = ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(), auth, userId, stable);
-
} catch (RemoteException ex) {
-
}
-
-
holder = installProvider(c, holder, holder.info, true , holder.noReleaseNeeded, stable);
-
-
return holder.provider;
-
}
After the above analysis, we get the proxy object of ContentProvider from the initial getContentResolver() step by step analysis, through ContextImpl, Activity Thread, Activity Manager Native, Activity Manager Service.
3. ContentProvider Call Analysis
In the previous section, we analyzed the process of getContentResolver() and found that the result of this operation can not only load the required ContentProvider (if the current ContentProvider has not been loaded), but also call the getIContentProvider() method of the ContentProvider. In this section, we will start with this method to see how to get the ContentProvider object through this method, and how to pass the operation of the database through this object.
Let's first look at what objects we get from the getIContentProvider() method.
-
@ContentProvider.java
-
public IContentProvider getIContentProvider() {
-
return mTransport;
-
}
What we get here is the mTransport variable of IContentProvider type, and the origin of this variable?
-
private Transport mTransport = new Transport();
This means that only the Transport object is obtained through the getIContentProvider() method, which is the internal class of ContentProvider:
-
class Transport extends ContentProviderNative {
-
ContentProvider getContentProvider() { }
-
public String getProviderName() { }
-
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal) { }
-
public Uri insert(Uri uri, ContentValues initialValues) { }
-
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { }
-
public int delete(Uri uri, String selection, String[] selectionArgs) { }
-
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { }
-
}
Furthermore, we find that this internal class provides various operations for querying the database. What about when we delete?
-
public int delete(Uri uri, String selection, String[] selectionArgs) {
-
enforceWritePermission(uri);
-
return ContentProvider.this.delete(uri, selection, selectionArgs);
-
}
Since within Transport the delete operation is passed to the ContentProvider to operate, let's look at the content Provider's own handling of delete():
-
public abstract int delete(Uri uri, String selection, String[] selectionArgs);
Here we encounter Abstract methods, that is to say, the details of delete operations need to be implemented in the subclasses of ContentProvider, which also conforms to the design concept of ContentProvider framework, that is, ContentProvider is responsible for the expression of abstract parts, and the specific operations need to be implemented by their own private.
Let's take a look at the important ways that ContentProvider itself provides:
-
public abstract class ContentProvider implements ComponentCallbacks2 {
-
private Transport mTransport = new Transport();
-
-
public ContentProvider() { }
-
-
-
protected final void setReadPermission(String permission) { }
-
public final String getReadPermission() { }
-
protected final void setWritePermission(String permission) { }
-
public final String getWritePermission() { }
-
-
-
public abstract Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
-
public abstract Uri insert(Uri uri, ContentValues values);
-
public abstract int delete(Uri uri, String selection, String[] selectionArgs);
-
public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);
-
-
public IContentProvider getIContentProvider() { }
-
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { }
-
}
From the methods provided by ContentProvider, we find that in addition to the methods of privilege management classes, there are some abstract methods similar to delete and related to database processing.
At this point, it is not difficult to find that this Transport object, acting as a proxy for ContentProvider, can initiate various requests for this object. When the agent receives the request, it "forwards" the request to the ContentProvider itself.
In conjunction with our analysis in the previous section, we can summarize as follows:
When we call the operation getContentResolver().delete() at the application layer, we call the delete operation of the ContentResolver object, which is equivalent to the Transport object obtained through getIContentProvider(), and the Transport object as the proxy of the ContentProvider will transfer the requests. To the ContentProvider, you call the delete method of the ContentProvider.
getContentResolver==>ContentResolver==>Transport==>ContentProvider
IV. ContentProvider to Contacts Provider
4.1. Contacts Provider Overview
After the above process, we transferred the operation to Content provider. Next, we need to use specific providers to continue the analysis. We choose Contacts provider as an example to study.
Contact database source code is located in packages providers Contacts Provider. Let's first look at his Android Manifest. XML file:
-
@AndroidManifest.xml
-
<provider android:name="ContactsProvider2"
-
android:authorities="contacts;com.android.contacts"
-
android:label="@string/provider_label"
-
android:multiprocess="false"
-
android:exported="true"
-
android:readPermission="android.permission.READ_CONTACTS"
-
android:writePermission="android.permission.WRITE_CONTACTS">
-
<path-permission
-
android:pathPrefix="/search_suggest_query"
-
android:readPermission="android.permission.GLOBAL_SEARCH" />
-
<path-permission
-
android:pathPrefix="/search_suggest_shortcut"
-
android:readPermission="android.permission.GLOBAL_SEARCH" />
-
<path-permission
-
android:pathPattern="/contacts/.*/photo"
-
android:readPermission="android.permission.GLOBAL_SEARCH" />
-
<grant-uri-permission android:pathPattern=".*" />
-
</provider>
This statement tells the system the name, authorities, permission and other information of the provider. Simply put, the name of the database is Contacts Provider 2, which can be matched by contacts or the prefix of com.android.contacts, and access to the database requires the corresponding permissions READ_CONTACTS, WRITE_CONT. ACTS, etc.
4.2. Contacts Provider Structure
ContactsProvider structure involves five important classes: ContentProvider, SQLiteTransactionListener, AbstractContactsProvider, ContactsProvider 2, ProfileProvider. Let's briefly talk about their relationship.
ContactsProvider combines the ContentProvider with the SQLiteTransactionListener to compose AbstractContactsProvider, which is the most basic class of ContactsProvider. On the basis of AbstractContactsProvider, ContactsProvider 2 and ProfileProvider are extended, as shown below. Show:
Next, we will introduce the roles of these five categories.
4.2.1,ContactsProvider
Needless to say, this is the Contacts Provider architecture provided by the system, which leaves the interface for database operation and needs subclasses to implement.
4.2.2,SQLiteTransactionListener
This is an interface, and the content is relatively simple. There are only three ways:
-
public interface SQLiteTransactionListener {
-
-
-
-
void onBegin();
-
-
-
-
-
void onCommit();
-
-
-
-
-
void onRollback();
-
}
As can be seen from the annotations, he mainly provides callback methods related to transaction processing, in which onBegin is a callback function called after transaction start processing, onCommit is a callback function after transaction commit, and onRollback is a callback function when transaction rollback, so it can be inferred that onBegin is used in ContactsProvider. Transaction operates on the database.
4.2.3,AbstractContactsProvider
This is the lowest class in the ContactsProvider structure. Let's take a look at his main internal methods:
-
public abstract class AbstractContactsProvider extends ContentProvider implements SQLiteTransactionListener {
-
public SQLiteOpenHelper getDatabaseHelper() { }
-
public Uri insert(Uri uri, ContentValues values) { }
-
public int delete(Uri uri, String selection, String[] selectionArgs) { }
-
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { }
-
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) { }
-
private ContactsTransaction startTransaction(boolean callerIsBatch) { }
-
private void endTransaction(boolean callerIsBatch) { }
-
protected abstract SQLiteOpenHelper getDatabaseHelper(Context context);
-
protected abstract ThreadLocal<ContactsTransaction> getTransactionHolder();
-
protected abstract Uri insertInTransaction(Uri uri, ContentValues values);
-
protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
-
protected abstract int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs);
-
protected abstract void notifyChange();
-
}
These methods are well understood and are all database operations. Remember when we analyzed the delete operation of ContentProvider, we mentioned that ContentProvider handles all operations of the database in subclasses. For the current environment, it needs to be handled in AbstractContacts Provider, so the delete operation should be called here:
-
public int delete(Uri uri, String selection, String[] selectionArgs) {
-
-
ContactsTransaction transaction = startTransaction(false);
-
try {
-
-
int deleted = deleteInTransaction(uri, selection, selectionArgs);
-
if (deleted > 0) {
-
transaction.markDirty();
-
}
-
transaction.markSuccessful(false);
-
return deleted;
-
} finally {
-
endTransaction(false);
-
}
-
}
Continue to look at the implementation of deleteInTransaction:
-
protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
This is still a virtual function, and specific definitions need to be left to subclasses to implement.
Here we find that in AbstractContacts Provider, insert, delete and update operations are transformed into transaction processing mode XXXInTransaction, and the specific implementation is left to subclasses, where the subclasses are Contacts Provider 2 and ProfileProvider. That is to say, the function of AbstractContacts Provider is to transform the operation of database into the way of transaction processing. As for the specific transaction operation, it needs to be implemented in its subclasses.
In addition, we noticed that only insert, delete, update operations were transformed, and there was no query operation, that is to say, query operations in ContentProvider were left directly to subclasses of subclasses to implement, and the transformation across AbstractContactsProvider was crossed. This means that query operations do not need to be translated into transaction processing.
4.2.4,ProfileProvider
Now let's look at the structure of ProfileProvider:
-
public class ProfileProvider extends AbstractContactsProvider {
-
public Cursor query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder){}
-
protected Uri insertInTransaction(Uri uri, ContentValues values){}
-
protected int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs) {}
-
protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs){}
-
public void onBegin() { }
-
public void onCommit() { }
-
public void onRollback() { }
-
}
From the structure of ProfileProvider, we can see that ProfileProvider has independent query operations, insert, delete, update transaction operations, and transaction-related callback functions. It can be said that he has complete data access and modification capabilities. But what about his role?
We'll see that later.
4.2.5,ContactsProvider2
This is the most important and top-level class in ContactsProvider, and we only registered him in Android Manifest.
Let's take a look at some of his important ways:
-
public class ContactsProvider2 extends AbstractContactsProvider implements OnAccountsUpdateListener {
-
public Cursor query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder) {}
-
public int delete(Uri uri, String selection, String[] selectionArgs) {}
-
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {}
-
public Uri insert(Uri uri, ContentValues values) {}
-
protected Uri insertInTransaction(Uri uri, ContentValues values) {}
-
protected int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs) {}
-
protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {}
-
}
Similar to ProfileProvider, it also provides complete data access and modification capabilities.
Contacts Provider and SQLite
We need to understand that the bottom layer of ContentProvider is still implemented by SQLite, which only encapsulates the operation of SQLite, making it easier to operate and understand.
On the bottom design of ContactsProvider, ContactsDatabaseHelper plays the role of communicating directly with SQLite, and on this basis, it differentiates and extends the class ProfileDatabaseHelper. So you can think of two helper classes with SQLite at the bottom of ContactsProvider. Let's first look at their structure:
-
public class ProfileDatabaseHelper extends ContactsDatabaseHelper {}
-
public class ContactsDatabaseHelper extends SQLiteOpenHelper {}
Inheritance relationships are as follows:
Both SQLiteOpenHelper classes are initialized at onCreate() of ContactsProvider2:
-
public boolean onCreate() {
-
super.onCreate();
-
return initialize();
-
}
-
private boolean initialize() {
-
-
mContactsHelper = getDatabaseHelper(getContext());
-
mDbHelper.set(mContactsHelper);
-
-
mProfileHelper = mProfileProvider.getDatabaseHelper(getContext());
-
return true;
-
}
Let's first look at the Contacts Database Helper initialization process:
-
protected ContactsDatabaseHelper getDatabaseHelper(final Context context) {
-
return ContactsDatabaseHelper.getInstance(context);
-
}
-
@ContactsDatabaseHelper.java
-
public static synchronized ContactsDatabaseHelper getInstance(Context context) {
-
if (sSingleton == null) {
-
-
sSingleton = new ContactsDatabaseHelper(context, DATABASE_NAME, true);
-
}
-
return sSingleton;
-
}
Look again at the Profile Database Helper initialization process:
-
protected ProfileDatabaseHelper getDatabaseHelper(Context context) {
-
return ProfileDatabaseHelper.getInstance(context);
-
}
-
public static synchronized ProfileDatabaseHelper getInstance(Context context) {
-
if (sSingleton == null) {
-
-
sSingleton = new ProfileDatabaseHelper(context, DATABASE_NAME, true);
-
}
-
return sSingleton;
-
}
Because there are two "different" SQLLiteOpenHelper in ContactsProvider, it is necessary to switch to different SQLite OpenHelper according to different needs when operating a specific database. We'll talk about the process of switching later.
Since there are two kinds of SQLiteOpenHelper, there should be two ContentProvider s to operate these two SQLiteOpenHelpers. Indeed, on top of these two kinds of SQLite operation classes, ProfileProvider and ContactsProvider 2 are built respectively, that is to say, this is the case. The corresponding relationship between the four is similar to that shown in the following figure:
Next let's look at these two pairs with the aid of a delete() operation data base Operational flow direction.
5.1. Continue delete()
Let's review again that when we do delete, we call getContentResolver, which will eventually call the delete method in ContentProvider, and then the delete method in ContactsProvider 2:
-
@ContactsProvider2.java
-
public int delete(Uri uri, String selection, String[] selectionArgs) {
-
waitForAccess(mWriteAccessLatch);
-
-
enforceSocialStreamWritePermission(uri);
-
-
-
if (mapsToProfileDb(uri)) {
-
switchToProfileMode();
-
-
return mProfileProvider.delete(uri, selection, selectionArgs);
-
} else {
-
switchToContactMode();
-
-
return super.delete(uri, selection, selectionArgs);
-
}
-
}
In the process of delete, according to the judgment of mapsToProfileDb(), two modes need to be switched: Profile and Contact mode, which is what we just mentioned. We need to switch different SQLiteOpenHelper to operate the database according to different situations. Now let's look at the basis for distinguishing the two modes.
-
private boolean mapsToProfileDb(Uri uri) {
-
return sUriMatcher.mapsToProfile(uri);
-
}
This is based on the uri given to sUriMatcher to find whether there are matching items, if there are, that need to be converted to Profile mode to process, then what are the items in sUriMatcher?
-
@ContactsProvider2.java
-
static {
-
final UriMatcher matcher = sUriMatcher;
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts", CONTACTS);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/data", CONTACTS_ID_DATA);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/entities", CONTACTS_ID_ENTITIES);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/suggestions", AGGREGATION_SUGGESTIONS);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/suggestions/*", AGGREGATION_SUGGESTIONS);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/photo", CONTACTS_ID_PHOTO);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/display_photo", CONTACTS_ID_DISPLAY_PHOTO);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/stream_items", CONTACTS_ID_STREAM_ITEMS);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/filter", CONTACTS_FILTER);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/filter/*", CONTACTS_FILTER);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*", CONTACTS_LOOKUP);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/data", CONTACTS_LOOKUP_DATA);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/photo", CONTACTS_LOOKUP_PHOTO);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#", CONTACTS_LOOKUP_ID);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#/data", CONTACTS_LOOKUP_ID_DATA);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#/photo", CONTACTS_LOOKUP_ID_PHOTO);
-
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/display_photo", CONTACTS_LOOKUP_DISPLAY_PHOTO);
-
-
}
We found that the URIs defined in Contacts Control are all in this MAP, that is to say, when we use the URIs in Contacts Control to operate the database, we should satisfy the conditions of mapsToProfileDb.
Next, let's look at the specific operation of these two situations.
5.2. Operation of Contacts Provider 2
Let's first analyze the data flow operation and then the ProfileProvider operation. That is to say, we need switchToContactMode():
-
private void switchToContactMode() {
-
-
mDbHelper.set(mContactsHelper);
-
mTransactionContext.set(mContactTransactionContext);
-
mAggregator.set(mContactAggregator);
-
mPhotoStore.set(mContactsPhotoStore);
-
mInProfileMode.set(false);
-
}
The most important operation here is to replace the content in mDbHelper with mContacts Helper, or Contacts Database Helper. Next is the operation of super.delete(), where super is AbstractContacts Provider for Contacts Provider 2:
-
@AbstractContactsProvider.java
-
public int delete(Uri uri, String selection, String[] selectionArgs) {
-
ContactsTransaction transaction = startTransaction(false);
-
try {
-
int deleted = deleteInTransaction(uri, selection, selectionArgs);
-
transaction.markSuccessful(false);
-
return deleted;
-
} finally {
-
endTransaction(false);
-
}
-
}
-
protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
This "transformation" operation, which we have analyzed before, is to transform delete operation into transaction processing. The specific operation needs to be implemented in subclasses, so we go back to deleteInTransaction in ContactsProvider 2:
-
protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
-
-
final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
-
-
final int match = sUriMatcher.match(uri);
-
switch (match) {
-
case CONTACTS: {
-
invalidateFastScrollingIndexCache();
-
return 0;
-
}
-
case CONTACTS_ID: {
-
invalidateFastScrollingIndexCache();
-
long contactId = ContentUris.parseId(uri);
-
return deleteContact(contactId, callerIsSyncAdapter);
-
}
-
default: {
-
mSyncToNetwork = true;
-
return mLegacyApiSupport.delete(uri, selection, selectionArgs);
-
}
-
}
-
}
Here we assume that the match is CONTACTS_ID:
-
private int deleteContact(long contactId, boolean callerIsSyncAdapter) {
-
-
final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
-
mSelectionArgs1[0] = Long.toString(contactId);
-
-
Cursor c = db.query(Tables.RAW_CONTACTS, new String[]{RawContacts._ID}, RawContacts.CONTACT_ID + "=?", mSelectionArgs1, null, null, null);
-
try {
-
while (c.moveToNext()) {
-
long rawContactId = c.getLong(0);
-
markRawContactAsDeleted(db, rawContactId, callerIsSyncAdapter);
-
}
-
} finally {
-
c.close();
-
}
-
-
mProviderStatusUpdateNeeded = true;
-
-
return db.delete(Tables.CONTACTS, Contacts._ID + "=" + contactId, null);
-
}
In the process of deleting the Contact, we need to get the available SQLLiteDatabase first. Currently, we need to get the SQLLiteDatabase of the Contacts Database Helper, then query the database to get the Cursor, and traverse each tag to delete it. Finally, we call the delete() method of the SQLiteDatabase to delete the record.
Let's look at the delete() process of SQLiteDatabase in detail:
-
@SQLiteDatabase.java
-
public int delete(String table, String whereClause, String[] whereArgs) {
-
acquireReference();
-
try {
-
-
SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table + (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
-
try {
-
-
return statement.executeUpdateDelete();
-
} finally {
-
statement.close();
-
}
-
} finally {
-
releaseReference();
-
}
-
}
We see that in the delete operation, we first generate the SQL statement (DELETE FROM XXX WHERE XXX) through the SQLiteStatement, and then apply the SQL operation through the executeUpdate Delete () method to complete the delete operation.
At this point, we have finally completed the "journey" from Content Provider to SQLite.
5.3. Operation of ProfileProvider
The first step is to switch the database to Profile Database Helper:
-
private void switchToProfileMode() {
-
-
mDbHelper.set(mProfileHelper);
-
mTransactionContext.set(mProfileTransactionContext);
-
mAggregator.set(mProfileAggregator);
-
mPhotoStore.set(mProfilePhotoStore);
-
mInProfileMode.set(true);
-
}
Then delete the action:
-
mProfileProvider.delete(uri, selection, selectionArgs);
The delete method in ProfileProvider is called, but the delete method is not found in ProfileProvider. We can only call delete() in its parent class AbstractContactsProvider:
-
@AbstractContactsProvider.java
-
public int delete(Uri uri, String selection, String[] selectionArgs) {
-
ContactsTransaction transaction = startTransaction(false);
-
try {
-
-
int deleted = deleteInTransaction(uri, selection, selectionArgs);
-
if (deleted > 0) {
-
transaction.markDirty();
-
}
-
transaction.markSuccessful(false);
-
return deleted;
-
} finally {
-
endTransaction(false);
-
}
-
}
-
protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
After the "transformation" of AbstractContacts Provider, the delete operation is converted to deleteInTransaction():
-
@ProfileProvider.java
-
protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
-
enforceWritePermission();
-
useProfileDbForTransaction();
-
return mDelegate.deleteInTransaction(uri, selection, selectionArgs);
-
}
mDelegate here is Contacts Provider 2 itself, so it disturbs the ProfileProvider and goes back to deleteInTransaction() in Contacts Provider 2, which we have described in detail in Section 5.2.
VI. SUMMARY
Now let's review the whole process:
1. When we call getContentResolver().delete(), we get the ContentResolver object through getContentResolver(), and then call the delete() method of the object.
2. In Content Resolver, the IContentProvider object is acquired by acquiProvider (), and the acquisition of this object first requires the ContentProvider Holder applied to the Activity Manager Service in ActivityThread, and then the Holder is used to obtain the IContentProvider object. TProvider) The remote proxy object (Transport object) of ContentProvider.
3. When you get this object, call its delete method, which will be passed by ContentProvider to its subclass (AbstractContacts Provider), and convert delete operation into transaction processing (deleteInTransaction) in the subclass, although in AbstractContacts Provider, the delete method will be transformed into a transaction. The way transactions are done, but there is no specific implementation. Instead, specific operations are left to their subclasses (ContactsProvider2).
4. ContactsProvider2 will use the corresponding SQLiteOpenHelper to translate operations into specific SQLite statements and execute them in the implementation process.
Next, let's finish the study with a picture.