Android深入四大元件(五)Content Provider的啟動過程

劉望舒發表於2017-05-23

相關文章
Android深入理解四大元件系列

前言

Content Provider做為四大元件之一,通常情況下並沒有其他的元件使用頻繁,但這不能作為我們不去深入學習它的理由。關於Content Provider一篇文章是寫不完的,這一篇文章先來介紹它的啟動過程。

1.query方法到AMS的呼叫過程

Android IPC機制(四)用ContentProvider進行程式間通訊這篇文章我舉了一個Content Provider使用的例子,在Activity中我是使用如下程式碼呼叫Content Provider的:

public class ContentProviderActivity extends AppCompatActivity {
    private final static String TAG = "ContentProviderActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_provider);
        Uri uri = Uri.parse("content://com.example.liuwangshu.mooncontentprovide.GameProvider");
        ContentValues mContentValues = new ContentValues();
        mContentValues.put("_id", 2);
        mContentValues.put("name", "大航海時代ol");
        mContentValues.put("describe", "最好玩的航海網遊");
        getContentResolver().insert(uri, mContentValues);//1
        Cursor gameCursor = getContentResolver().query(uri, new String[]{"name", "describe"}, null, null, null);
     ...
    }
}複製程式碼

要想呼叫Content Provider,首先需要使用註釋1處的getContentResolver方法,如下所示。
frameworks/base/core/Java/android/content/ContextWrapper.java

@Override
public ContentResolver getContentResolver() {
    return mBase.getContentResolver();
}複製程式碼

這裡mBase指的是ContextImpl,ContextImpl的getContentResolver方法如下所示。

frameworks/base/core/java/android/app/ContextImpl.java

@Override
public ContentResolver getContentResolver() {
    return mContentResolver;
}複製程式碼

上面的程式碼return了ApplicationContentResolver型別的mContentResolver物件,ApplicationContentResolver是ContextImpl中的靜態內部類,繼承自ContentResolver,它在ContextImpl的構造方法中被建立。
當我們呼叫ContentResolver的insert、query、update等方法時就會啟動Content Provider,這裡拿query方法來進行舉例。
query方法的實現在ApplicationContentResolver的父類ContentResolver中,程式碼如下所示。
frameworks/base/core/java/android/content/ContentResolver.java

public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
            @Nullable String[] projection, @Nullable String selection,
            @Nullable String[] selectionArgs, @Nullable String sortOrder,
            @Nullable CancellationSignal cancellationSignal) {
        Preconditions.checkNotNull(uri, "uri");
        IContentProvider unstableProvider = acquireUnstableProvider(uri);//1
        ...
        try {
           ...
            try {
                qCursor = unstableProvider.query(mPackageName, uri, projection,
                        selection, selectionArgs, sortOrder, remoteCancellationSignal);//2
            } catch (DeadObjectException e) {
               ...
            }
    ...
   }複製程式碼

在註釋1處通過acquireUnstableProvider方法返回IContentProvider型別的unstableProvider物件,在註釋2處呼叫unstableProvider的query方法。我們檢視acquireUnstableProvider方法做了什麼,如下所示。
frameworks/base/core/java/android/content/ContentResolver.java

   public final IContentProvider acquireUnstableProvider(Uri uri) {
        if (!SCHEME_CONTENT.equals(uri.getScheme())) {//1
            return null;
        }
        String auth = uri.getAuthority();
        if (auth != null) {
            return acquireUnstableProvider(mContext, uri.getAuthority());//2
        }
        return null;
    }複製程式碼

註釋1處用來檢查Uri的scheme是否等於"content",如果不是則返回null。註釋2處呼叫了acquireUnstableProvider方法,這是個抽象方法,它的實現在ContentResolver的子類ApplicationContentResolver中:
frameworks/base/core/java/android/app/ContextImpl.java

    @Override
    protected IContentProvider acquireUnstableProvider(Context c, String auth) {
        return mMainThread.acquireProvider(c,
                ContentProvider.getAuthorityWithoutUserId(auth),
                resolveUserIdFromAuthority(auth), false);
    }複製程式碼

return了ActivityThread型別的mMainThread物件的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);//1
        if (provider != null) {
            return provider;
        }
        IActivityManager.ContentProviderHolder holder = null;
        try {
            holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), auth, userId, stable);//2
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        if (holder == null) {
            Slog.e(TAG, "Failed to find provider info for " + auth);
            return null;
        }
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);//3
        return holder.provider;
    }複製程式碼

註釋1處檢查ActivityThread中的ArrayMap型別的mProviderMap中是否有目標ContentProvider存在,有則返回,沒有就會在註釋2處呼叫AMP的getContentProvider方法,最終會呼叫AMS的getContentProvider方法。註釋3處的installProvider方法用來將註釋2處返回的ContentProvider相關的資料儲存在mProviderMap中,起到快取的作用,這樣使用相同的Content Provider時,就不需要每次都要呼叫AMS的getContentProvider方法。使用我們接著檢視AMS的getContentProvider方法,程式碼如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    @Override
    public final ContentProviderHolder getContentProvider(
            IApplicationThread caller, String name, int userId, boolean stable) {
     ...
        return getContentProviderImpl(caller, name, null, stable, userId);
    }複製程式碼

getContentProvider方法return了getContentProviderImpl方法:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

   private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, boolean stable, int userId) {
...
       ProcessRecord proc = getProcessRecordLocked(
                                cpi.processName, cpr.appInfo.uid, false);//1
                        if (proc != null && proc.thread != null && !proc.killed) {
                            ...
                            if (!proc.pubProviders.containsKey(cpi.name)) {
                                checkTime(startTime, "getContentProviderImpl: scheduling install");
                                proc.pubProviders.put(cpi.name, cpr);
                                try {
                                    proc.thread.scheduleInstallProvider(cpi);//2
                                } catch (RemoteException e) {
                                }
                            }
                        } else {
                            checkTime(startTime, "getContentProviderImpl: before start process");
                            proc = startProcessLocked(cpi.processName,
                                    cpr.appInfo, false, 0, "content provider",
                                    new ComponentName(cpi.applicationInfo.packageName,
                                            cpi.name), false, false, false);//3
                            checkTime(startTime, "getContentProviderImpl: after start process");
                          ...
                        }
             ...           

}複製程式碼

getContentProviderImpl方法的程式碼很多,這裡擷取了關鍵的部分。註釋1處通過getProcessRecordLocked方法來獲取目標ContentProvider的應用程式程式資訊,這些資訊用ProcessRecord型別的proc來表示,如果該應用程式已經啟動就會呼叫註釋2處的程式碼,否則就會呼叫註釋3的startProcessLocked方法來啟動程式。這裡我們假設ContentProvider的應用程式還沒有啟動,關於應用程式啟動過程,我在Android應用程式程式啟動過程(前篇)已經講過,最終會呼叫ActivityThread的main方法,程式碼如下所示。
frameworks/base/core/java/android/app/ActivityThread.java

 public static void main(String[] args) {
      ...
        Looper.prepareMainLooper();//1
        ActivityThread thread = new ActivityThread();//2
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();//3
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }複製程式碼

註釋1處通過prepareMainLooper方法在ThreadLocal中獲取Looper,並在註釋3處開啟訊息迴圈。在註釋2處建立了ActivityThread並呼叫了它的attach方法:
frameworks/base/core/java/android/app/ActivityThread.java

  private void attach(boolean system) {
  ...
    final IActivityManager mgr = ActivityManagerNative.getDefault();//1
            try {
                mgr.attachApplication(mAppThread);//2
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
  ...          
}複製程式碼

註釋1處最終會得到AMS,在註釋2處呼叫AMS的attachApplication方法,並將ApplicationThread型別的mAppThread物件傳進去。
query方法到AMS的呼叫過程,如下面時序圖所示(省略應用程式程式啟動過程)。

Android深入四大元件(五)Content Provider的啟動過程

2.AMS啟動Content Provider的過程

我們接著來檢視AMS的attachApplication方法,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

@Override
public final void attachApplication(IApplicationThread thread) {
    synchronized (this) {
        int callingPid = Binder.getCallingPid();
        final long origId = Binder.clearCallingIdentity();
        attachApplicationLocked(thread, callingPid);
        Binder.restoreCallingIdentity(origId);
    }
}複製程式碼

attachApplication方法中又呼叫了attachApplicationLocked方法:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

   private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid) {
   ...
   thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                    profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                    app.instrumentationUiAutomationConnection, testMode,
                    mBinderTransactionTrackingEnabled, enableTrackAllocation,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    new Configuration(mConfiguration), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked());
...
}複製程式碼

attachApplicationLocked方法中呼叫了thread的bindApplication方法,thread是IApplicationThread型別的,從型別名字就可以看出來是用於程式間通訊,這裡實現bindApplication方法的是ApplicationThreadProxy類,它實現了IApplicationThread介面。
frameworks/base/core/java/android/app/ApplicationThreadNative.java

class ApplicationThreadProxy implements IApplicationThread {
...
    @Override
    public final void bindApplication(String packageName, ApplicationInfo info,
            List<ProviderInfo> providers, ComponentName testName, ProfilerInfo profilerInfo,
            Bundle testArgs, IInstrumentationWatcher testWatcher,
            IUiAutomationConnection uiAutomationConnection, int debugMode,
            boolean enableBinderTracking, boolean trackAllocation, boolean restrictedBackupMode,
            boolean persistent, Configuration config, CompatibilityInfo compatInfo,
            Map<String, IBinder> services, Bundle coreSettings) throws RemoteException {
      ...
        mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null,
                IBinder.FLAG_ONEWAY);
        data.recycle();
    }
...
}複製程式碼

到目前為止,上面的呼叫過程還是在AMS程式中執行的,因此,需要通過IBinder型別的mRemote物件向新建立的應用程式程式(目標Content Provider所在的程式)傳送BIND_APPLICATION_TRANSACTION型別的通訊請求。處理這個通訊請求的是在新建立的應用程式程式中執行的ApplicationThread的bindApplication方法,如下所示。
frameworks/base/core/java/android/app/ActivityThread.java

 public final void bindApplication(String processName, ApplicationInfo appInfo,
                List<ProviderInfo> providers, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableBinderTracking, boolean trackAllocation,
                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings) {
                ...
                sendMessage(H.BIND_APPLICATION, data);
        }複製程式碼

呼叫sendMessage方法像H傳送BIND_APPLICATION型別訊息,H的handleMessage方法如下所示。
frameworks/base/core/java/android/app/ActivityThread.java

   public void handleMessage(Message msg) {
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
            ...
            case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
  ...
  }
  ... 
}複製程式碼

我們接著檢視handleBindApplication方法:
frameworks/base/core/java/android/app/ActivityThread.java

  private void handleBindApplication(AppBindData data) {
   ...
        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);//1
         try {
                final ClassLoader cl = instrContext.getClassLoader();
                mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();//2
            } catch (Exception e) {
             ...
            }
            final ComponentName component = new ComponentName(ii.packageName, ii.name);
            mInstrumentation.init(this, instrContext, appContext, component,
                    data.instrumentationWatcher, data.instrumentationUiAutomationConnection);//3
           ...
            Application app = data.info.makeApplication(data.restrictedBackupMode, null);//4
            mInitialApplication = app;
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);//5
                    mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                }
            }
          ...
           mInstrumentation.callApplicationOnCreate(app);//6
          ... 
  }複製程式碼

handleBindApplication方法的程式碼很長,這裡擷取了主要的部分。註釋1處建立了ContextImpl 。註釋2處通過反射建立Instrumentation並在註釋3處初始化Instrumentation。註釋4處建立Application並且在註釋6處呼叫Application的onCreate方法,這意味著Content Provider所在的應用程式程式已經啟動完畢,在這之前,註釋5處呼叫installContentProviders方法來啟動Content Provider,程式碼如下所示。
frameworks/base/core/java/android/app/ActivityThread.java

private void installContentProviders(
        Context context, List<ProviderInfo> providers) {
    final ArrayList<IActivityManager.ContentProviderHolder> results =
        new ArrayList<IActivityManager.ContentProviderHolder>();

    for (ProviderInfo cpi : providers) {//1
        ...
        IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);//2
      ...
    }

    try {
        ActivityManagerNative.getDefault().publishContentProviders(
            getApplicationThread(), results);//3
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}複製程式碼

註釋1處遍歷當前應用程式程式的ProviderInfo列表,得到每個Content Provider的ProviderInfo(儲存Content Provider的資訊),並在註釋2處呼叫installProvider方法來啟動這些Content Provider。在註釋3處通過AMS的publishContentProviders方法將這些Content Provider儲存在AMS的mProviderMap中,這個mProviderMap在前面提到過,起到快取的作用,防止每次使用相同的Content Provider時都會呼叫AMS的getContentProvider方法。來檢視installProvider方法時如何啟動Content Provider的,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;
   ...
                final java.lang.ClassLoader cl = c.getClassLoader();
                localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();//1
                provider = localProvider.getIContentProvider();
                if (provider == null) {
                  ...
                    return null;
                }
                if (DEBUG_PROVIDER) Slog.v(
                    TAG, "Instantiating local provider " + info.name);
                localProvider.attachInfo(c, info);//2
            } catch (java.lang.Exception e) {
               ...
                }
                return null;
            }
        }
           ...
        return retHolder;
    }複製程式碼

在註釋1處通過反射來建立ContentProvider型別的localProvider物件,並在註釋2處呼叫了它的attachInfo方法:
frameworks/base/core/java/android/content/ContentProvider.java

  private void attachInfo(Context context, ProviderInfo info, boolean testing) {
       ...
            ContentProvider.this.onCreate();
        }
    }複製程式碼

在attachInfo方法中呼叫了onCreate方法,它是一個抽象方法。這樣Content Provider就啟動完畢。
最後給出AMS啟動Content Provider的時序圖。

Android深入四大元件(五)Content Provider的啟動過程


歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,即可關注。

Android深入四大元件(五)Content Provider的啟動過程

相關文章