ContentService simple analysis

Posted by anoesis on Thu, 18 Jun 2020 18:22:30 +0200

ContentService uses the observer mode, and APP registers Uri messages of interest to ContentService through ContentResolver.When the corresponding data changes, ContentResolver calls the notifyChange function to notify ContentService of the corresponding Uri data changes, ContentService queries the corresponding Uri's set of ContentObserver proxy objects, and calls back the ContentObserver's notifyChange function through the IContentObserver proxy object

The simple communication model between 1 ContentService and ContentObserver is as follows:

The two types of diagrams are as follows:

3 ContentObserver registration process
Register an observer with ContentService by calling the registerContentObserver function of ContentResolver. The first parameter is the uri for listening, the second parameter matches the uri strictly, and the third parameter is the observer we need to register

mContext.getContentResolver().registerContentObserver(notifyUri, true, observer);

ContentResolver.java (d:\source\android\android8.0\android-8.0.0_r1\frameworks\base\core\java\android\content)
The ContentResolver's registerContentObserver function calls the overloaded registerContentObserver function, which registerContentObserver registers the ContentObserver to the ContentService through the ContentService proxy object.Observer.getContentObserver() The object is a Transport object, Transport is an internal class in ContentObserver, Transport class inherits IContentObserver.Stub So the object registered with ContentService is Transport

public final void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants,
            @NonNull ContentObserver observer) {
    Preconditions.checkNotNull(uri, "uri");
    Preconditions.checkNotNull(observer, "observer");
    registerContentObserver(
            ContentProvider.getUriWithoutUserId(uri),
            notifyForDescendants,
            observer,
            ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
}

/** @hide - designated user version */
public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
    ContentObserver observer, @UserIdInt int userHandle) {
    try {
        // Call the registerContentObserver function of ContentService through Binder
            // observer.getContentObserver() is a Transport object
        getContentService().registerContentObserver(uri, notifyForDescendents,
                observer.getContentObserver(), userHandle, mTargetSdkVersion);
    } catch (RemoteException e) {
    }
}

ContentObserver.java (d:\source\android\android8.0\android-8.0.0_r1\frameworks\base\core\java\android\database)

public IContentObserver getContentObserver() {
    synchronized (mLock) {
        if (mTransport == null) {
            mTransport = new Transport(this);
        }
        return mTransport;
    }
}

// Transport corresponds to the IContentObserver Binder local object
private static final class Transport extends IContentObserver.Stub {
    private ContentObserver mContentObserver;

    public Transport(ContentObserver contentObserver) {
        mContentObserver = contentObserver;
    }

    @Override
    public void onChange(boolean selfChange, Uri uri, int userId) {
        ContentObserver contentObserver = mContentObserver;
        if (contentObserver != null) {
            contentObserver.dispatchChange(selfChange, uri, userId);
        }
    }

    public void releaseContentObserver() {
        mContentObserver = null;
    }
}

ContentService.java (d:\source\android\android8.0\android-8.0.0_r1\frameworks\base\services\core\java\com\android\server\content)
Next, analyze the ContentService, call the registerContentObserver function that is ultimately called to ContentService through Binder, and use Binder-driven conversion to maintain the ContentObserver proxy object as a tree inside the IContentObserver proxy object ContentService.

Grouping by Uri's path example:
Uri.parse("content://" + "nice" + "/test");
Uri.parse("content://" + "nice" + "/test/#");
The path corresponding to the two Uri above is the same, #ObserverNode node will not be generated
 First, ObserverNode is generated by parsing nice, and test nodes are generated recursively.
Since test is a leaf node, the corresponding ObserverEntry object is added to the test node, and the next time it adds and
 Uri.parse("content://" + "nice" + "/test") When ContentObserver has the same path, a new ObserverEntry object is added under the test node
public void registerContentObserver(Uri uri, boolean notifyForDescendants,
                                        IContentObserver observer) {
    // Call overloaded registerContentObserver function
    registerContentObserver(uri, notifyForDescendants, observer,
        UserHandle.getCallingUserId(), Build.VERSION_CODES.CUR_DEVELOPMENT);
}

public void registerContentObserver(Uri uri, boolean notifyForDescendants,
    IContentObserver observer, int userHandle, int targetSdkVersion) {
    if (observer == null || uri == null) {
        throw new IllegalArgumentException("You must pass a valid uri and observer");
    }
    // mRootNode is the root node of the tree
   // At this point the observer gets the IContentObserver Binder proxy object
    synchronized (mRootNode) {
        mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
            uid, pid, userHandle);
    }
}
//  The countUriSegments function returns the corresponding value based on the'/'number in Uri, with the exception of /'#'
private int countUriSegments(Uri uri) {
    if (uri == null) {
        return 0;
    }
    return uri.getPathSegments().size() + 1;
}

public void addObserverLocked(Uri uri, IContentObserver observer,
    boolean notifyForDescendants, Object observersLock,
    int uid, int pid, int userHandle) {
    addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock,
        uid, pid, userHandle);
}

private void addObserverLocked(Uri uri, int index, IContentObserver observer,
    boolean notifyForDescendants, Object observersLock,
    int uid, int pid, int userHandle) {
    // If this is the leaf node add the observer
    // If it is the last Segment, create a new ObserverEntry and add the new ObserverEntry to mObservers
    if (index == countUriSegments(uri)) {
        mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
            uid, pid, userHandle));
        return;
    }
    // Look to see if the proper child already exists
    String segment = getUriSegment(uri, index);
    if (segment == null) {
        throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
    }
    // Search the entire number for uri with the same path
    int N = mChildren.size();
    for (int i = 0; i < N; i++) {
        ObserverNode node = mChildren.get(i);
        if (node.mName.equals(segment)) {
            // Child Node Recursive Find
            node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
                observersLock, uid, pid, userHandle);
             return;
        }
    }

     // No child found, create one
   //If not found, create a new ObserverNode node
    ObserverNode node = new ObserverNode(segment);
    mChildren.add(node);
    node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
        observersLock, uid, pid, userHandle);
}

The diagram of mRootNode node composition is as follows:

4 Sending process of notification message
ContentResolver.java (d:\source\android\android8.0\android-8.0.0_r1\frameworks\base\core\java\android\content)
APP sends matching uri path data changes to ContentService through the notifyChange function of getContentResolver and notifyChange function through the proxy object of ContentService

getContext().getContentResolver().notifyChange(url, null);
public void notifyChange(@NonNull Uri uri, ContentObserver observer, boolean syncToNetwork,
    @UserIdInt int userHandle) {
    try {
        // Get the proxy object of ContentService through getContentService
        getContentService().notifyChange(
            uri, observer == null ? null : observer.getContentObserver(),
            observer != null && observer.deliverSelfNotifications(),
            syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0,
            userHandle, mTargetSdkVersion);
    } catch (RemoteException e) {
    }
}

ContentService.java (d:\source\android\android8.0\android-8.0.0_r1\frameworks\base\services\core\java\com\android\server\content)
Next, we analyze the notifyChange function of ContentService, collect a collection of IContentObserver proxy object objects that are interested in matching uri, collectObserversLocked function is collected, stored in calls list, traverse calls list, and then call back the onChange function of IContentObserver proxy object in turn. Next, we analyze the collectObserversLocked function

public void notifyChange(Uri uri, IContentObserver observer,
    boolean observerWantsSelfNotifications, int flags, int userHandle,
    int targetSdkVersion) {
    if (DEBUG) Slog.d(TAG, "Notifying update of " + uri + " for user " + userHandle
        + " from observer " + observer + ", flags " + Integer.toHexString(flags));

    if (uri == null) {
        throw new NullPointerException("Uri must not be null");
    }

    // This makes it so that future permission checks will be in the context of this
    // process rather than the caller's process. We will restore this before returning.
    long identityToken = clearCallingIdentity();
    try {
        ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
        synchronized (mRootNode) {
            mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
                flags, userHandle, calls);
        }
        final int numCalls = calls.size();
        for (int i=0; i<numCalls; i++) {
            ObserverCall oc = calls.get(i);
            try {
                // Call the onChange function of the IContentObserver proxy object, and eventually call the onChange function of the ContentObserver when the registerContentObserver is reached
                oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);
                if (DEBUG) Slog.d(TAG, "Notified " + oc.mObserver + " of " + "update at "
                    + uri);
            } catch (RemoteException ex) {
            }
        }
    } finally {
        restoreCallingIdentity(identityToken);
    }
}

CollectObserversLocked first obtains the corresponding path length, matches the current node, and then recursively matches the child nodes. Call collectMyObserversLocked in the collectObserversLocked function to add the appropriate IContentObserver proxy object to the calls list. Next, analyzeOc.mObserver.onChangefunction

public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
    boolean observerWantsSelfNotifications, int flags,
    int targetUserHandle, ArrayList<ObserverCall> calls) {
    String segment = null;
    //Get the corresponding path length from uri
    int segmentCount = countUriSegments(uri);
    //Match current node
    if (index >= segmentCount) {
        // This is the leaf node, notify all observers
        if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
            collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
                flags, targetUserHandle, calls);
    } else if (index < segmentCount) {
        segment = getUriSegment(uri, index);
        if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
            + segment);
        // Notify any observers at this level who are interested in descendants
        collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
            flags, targetUserHandle, calls);
    }
    //Recursive processing of child nodes under the current node
    int N = mChildren.size();
    for (int i = 0; i < N; i++) {
        ObserverNode node = mChildren.get(i);
        if (segment == null || node.mName.equals(segment)) {
            // We found the child,
            node.collectObserversLocked(uri, index + 1, observer,
            observerWantsSelfNotifications, flags, targetUserHandle, calls);
            if (segment != null) {
                break;
            }
        }
    }
}
private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
    boolean observerWantsSelfNotifications, int flags,
    int targetUserHandle, ArrayList<ObserverCall> calls) {
    int N = mObservers.size();
    IBinder observerBinder = observer == null ? null : observer.asBinder();
    for (int i = 0; i < N; i++) {
        ObserverEntry entry = mObservers.get(i);

        // Don't notify the observer if it sent the notification and isn't interested
        // in self notifications
        boolean selfChange = (entry.observer.asBinder() == observerBinder);
        if (selfChange && !observerWantsSelfNotifications) {
            continue;
        }

        // Does this observer match the target user?
        if (targetUserHandle == UserHandle.USER_ALL
            || entry.userHandle == UserHandle.USER_ALL
            || targetUserHandle == entry.userHandle) {
            // Make sure the observer is interested in the notification
            if (leaf) {
                // If we are at the leaf: we always report, unless the sender has asked
                // to skip observers that are notifying for descendants (since they will
                // be sending another more specific URI for them).
                if ((flags&ContentResolver.NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS) != 0
                    && entry.notifyForDescendants) {
                    if (DEBUG) Slog.d(TAG, "Skipping " + entry.observer
                        + ": skip notify for descendants");
                    continue;
                }
            } else {
                // If we are not at the leaf: we report if the observer says it wants
                // to be notified for all descendants.
                if (!entry.notifyForDescendants) {
                    if (DEBUG) Slog.d(TAG, "Skipping " + entry.observer
                        + ": not monitor descendants");
                    continue;
                }
           }

           if (DEBUG) Slog.d(TAG, "Reporting to " + entry.observer + ": leaf=" + leaf
                + " flags=" + Integer.toHexString(flags)
                + " desc=" + entry.notifyForDescendants);
            //Encapsulate the matching observer in ObserverCall and add it to the calls list
           calls.add(new ObserverCall(this, entry.observer, selfChange,
               UserHandle.getUserId(entry.uid)));
        }
    }
}

ContentObserver.java (d:\source\android\android8.0\android-8.0.0_r1\frameworks\base\core\java\android\database)
Oc.mObserver.onChange(Oc.mSelfChange(uri, userHandle) Calls back the IContentObserver Binder local object through the IContentObserver Binder proxy. The IContentObserver object registered with ContentService is Transport. The final call is the onChange function of Transport. The Transport class is the ContentObserver internal class, which handles communication between Binder processes.Transport's onChange calls ContentObserver's dispatchChange function directly. When mHandler is null, it calls ContentObserver's onChange function directly, eventually notifying the data update message. When mHandler is not null, it calls mHandlerPost a NotificationRunnable, let's see what's done in NotificationRunnable's run function, run function call ContentObserver.this.onChange(mSelfChange, mUri, mUserId).The onChange function is called the same as when hanlder is null.The sending process of the notification message is analyzed here, and we look back to find that there is no intrinsic connection between ContentObserver and ContententProvider

private static final class Transport extends IContentObserver.Stub {
    private ContentObserver mContentObserver;

    public Transport(ContentObserver contentObserver) {
        mContentObserver = contentObserver;
    }

    @Override
    public void onChange(boolean selfChange, Uri uri, int userId) {
        ContentObserver contentObserver = mContentObserver;
        if (contentObserver != null) {
            contentObserver.dispatchChange(selfChange, uri, userId);
        }
    }

    public void releaseContentObserver() {
        mContentObserver = null;
    }
}
private void dispatchChange(boolean selfChange, Uri uri, int userId) {
    if (mHandler == null) {
        onChange(selfChange, uri, userId);
    } else {
        mHandler.post(new NotificationRunnable(selfChange, uri, userId));
    }
}
private final class NotificationRunnable implements Runnable {
    private final boolean mSelfChange;
    private final Uri mUri;
    private final int mUserId;

    public NotificationRunnable(boolean selfChange, Uri uri, int userId) {
        mSelfChange = selfChange;
        mUri = uri;
        mUserId = userId;
    }

    @Override
    public void run() { 
        ContentObserver.this.onChange(mSelfChange, mUri, mUserId);
    }
}

Topics: Android Java Database