主頁 > 移動端開發 > Android四大組件系列9 深入理解Context

Android四大組件系列9 深入理解Context

2021-01-03 10:26:49 移動端開發

一 概述

Context 相信幾乎所有的 Android 開發人員基本上都非常熟悉,因為它太常見了,有大量的場景都離不開 Context,下面列舉部分常見場景:

啟動 Activity (startActivity)
啟動服務 (startService)
發送廣播 (sendBroadcast),注冊廣播接收者 (registerReceiver)
獲取 ContentResolver (getContentResolver)
獲取類加載器 (getClassLoader)
打開或創建資料庫 (openOrCreateDatabase)
獲取資源 (getResources)

Context 是 Android 中用的非常多的一種概念,常被翻譯成背景關系,這種概念在其他的技術中也有所使用,Android 官方對它的解釋,可以理解為應用程式環境中全域資訊的介面,它整合了許多系統級的服務,可以用來獲取應用中的類、資源,以及可以進行應用程式級的調起操作,比如啟動 Activity、Service 等等,而且 Context 這個類是 abstract 的,不包含具體的函式實作,

1.1 Context結構

Context 是維持 Android 程式中各組件能夠正常作業的一個核心功能類,

Context 本身是一個抽象類,其主要實作類為 ContextImpl,另外有直系子類兩個:

  • ContextWrapper
  • ContextThemeWrapper

這兩個子類都是 Context 的代理類,它們繼承關系如下:
在這里插入圖片描述

  • ContextImpl
    ContextImpl 是 Context API 的常見實作,它為 Activity 和其他應用程式組件提供基本背景關系物件,說的通俗一點就是 ContextImpl 實作了抽象類的方法,我們在使用 Context 的時候的方法就是它實作的
  • ContextWrapper
    ContextWrapper 類代理 Context 的實作,將其所有呼叫簡單地委托給另一個 Context 物件(ContextImpl),可以被分類為修飾行為而不更改原始 Context 的類,其實就 Context 類的修飾類,真正的實作類是 ContextImpl,ContextWrapper 里面的方法呼叫也是呼叫 ContextImpl 里面的方法
  • ContextThemeWrapper
    就是一個帶有主題的封裝類,比 ContextWrapper 多了主題,它的一個直接子類就是 Activity,

通過 Context 的繼承關系圖并結合我們開發中比較熟悉的類:Activity、Service、Application,所以我們可以認為 Context 一共有三種型別,分別是 Application、Activity 和 Service,他們分別承擔不同的作用,但是都屬于 Context,而他們具有 Context 的功能則是由 ContextImpl 類實作的,

簡單流程是:Application,Activity 或 Service 通過 attach() 呼叫父類 ContextWrapper 的 attachBaseContext(),從而設定父類成員變數 mBase 為 ContextImpl 物件,從而核心的作業都交給 ContextImpl 來處理,

1.2 Context數量

根據上面的 Context 型別我們可以知道,Context 一共有 Application、Activity 和 Service 三種型別,因此在一個應用程式中 Context 數量的計算公式可以這樣寫:

  • Context 數量 = Activity 數量 + Service 數量 + 1

上面的1代表著 Application 的數量,因為一個應用程式中可以有多個 Activity 和多個 Service,但是只能有一個 Application,

二 組件初始化

要想深入理解 Context,需要依次來看看四大組件的初始化程序,

2.1 performLaunchActivity

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

private Activity performLaunchActivity(ActivityClientRecord r,
      Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            //step 1: 創建LoadedApk物件
            r.packageInfo = getPackageInfo(aInfo.applicationInfo,
                  r.compatInfo, Context.CONTEXT_INCLUDE_CODE);
        }
        ...... //component初始化程序
        //step 2:創建ContextImpl物件
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //step 3: 創建Activity物件
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(
                     activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            ......
        }

        try {
            //step 4: 創建Application物件
            Application app = 
            r.packageInfo.makeApplication(false, mInstrumentation);
            ......
            if (activity != null) {
                ......
              //step 5:將Application/ContextImpl都attach到Activity物件
                activity.attach(appContext, this, getInstrumentation(),
                    r.token, r.ident, app, ......);
                ......
                activity.mCalled = false;
                if (r.isPersistable()) {
                    //step 6: 執行回呼onCreate
                    mInstrumentation.callActivityOnCreate(activity,
                         r.state, r.persistentState);
                } else {
              mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    ......
                }
                r.activity = activity;
            }
            r.setState(ON_CREATE);
            .....

        } catch (SuperNotCalledException e) {
            ......
        } catch (Exception e) {
            ......
        }
        return activity;
    }

startActivity 的程序最侄訓在目標行程執行 performLaunchActivity() 方法,該方法主要功能:

  • 創建物件 LoadedApk
  • 創建物件 Activity
  • 創建物件 Application
  • 創建物件 ContextImpl
    Application,ContextImpl 都 attach 到 Activity 物件并執行 onCreate() 等回呼,

2.2 handleCreateService

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

private void handleCreateService(CreateServiceData data) {
        ......
        //step 1: 創建LoadedApk
        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        Service service = null;
        try {
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            //step 2: 創建Service物件
         service = packageInfo.getAppFactory()
                 .instantiateService(cl, data.info.name, data.intent);
        } catch (Exception e) {
            ......
        }
        try {
            //step 3: 創建ContextImpl物件
            ContextImpl context = ContextImpl.createAppContext(this,
                    packageInfo);
            context.setOuterContext(service);
            //step 4: 創建Application物件
            Application app = packageInfo.makeApplication(false,
                    mInstrumentation);
            //step 5: 將Application/ContextImpl都attach到service物件
            service.attach(context, this, data.info.name,
                data.token, app, ActivityManager.getService());
            service.onCreate();//step 6: 執行onCreate回呼
            mServices.put(data.token, service);
            try {
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } catch (Exception e) {
            ......
        }
    }

2.3 handleReceiver

private void handleReceiver(ReceiverData data) {
        ......
        //step 1: 創建LoadedApk物件
        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        IActivityManager mgr = ActivityManager.getService();
        Application app;
        BroadcastReceiver receiver;
        ContextImpl context;
        try {
            //step 2: 創建Application物件
          app = packageInfo.makeApplication(false, mInstrumentation);
            //step 3: 創建ContextImpl物件
            context = (ContextImpl) app.getBaseContext();
            if (data.info.splitName != null) {
                context = (ContextImpl)
                context.createContextForSplit(data.info.splitName);
            }
            java.lang.ClassLoader cl = context.getClassLoader();
            data.intent.setExtrasClassLoader(cl);
            data.intent.prepareToEnterProcess();
            data.setExtrasClassLoader(cl);
            //step 4: 創建BroadcastReceiver物件
            receiver = packageInfo.getAppFactory()
               .instantiateReceiver(cl, data.info.name, data.intent);
        } catch (Exception e) {
            ......
        }
        try {
            ......
            //step 5: 執行onReceive回呼
          receiver.onReceive(context.getReceiverRestrictedContext(),
                    data.intent);
        } catch (Exception e) {
            ......
        } finally {
            sCurrentBroadcastIntent.set(null);
        }
        if (receiver.getPendingResult() != null) {
            data.finish();
        }
    }
  • 以上程序是靜態廣播接收者,即通過 AndroidManifest.xml 的標簽來申明的 BroadcastReceiver
  • 如果是動態廣播接收者,則不需要再創建那么多物件,因為動態廣播在注冊時行程已創建,基本物件已創建完成,只需要回呼 BroadcastReceiver 的 onReceive() 方法即可

2.4 installProvider

private ContentProviderHolder installProvider(Context context,
            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 {
                //step 1 && 2: 創建LoadedApk和ContextImpl物件
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }
            if (c == null) {
                ......
                return null;
            }
            ......
            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
        LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
                if (packageInfo == null) {
                    // System startup case.
                    packageInfo = getSystemContext().mPackageInfo;
                }
                //step 3: 創建ContentProvider物件
                localProvider = packageInfo.getAppFactory()
                        .instantiateProvider(cl, info.name);
                provider = localProvider.getIContentProvider();
                ......
        //step 4: ContextImpl都attach到ContentProvider物件
        //step 5: 并執行回呼onCreate
                localProvider.attachInfo(c, info);
            } catch (java.lang.Exception e) {
                ......
            }
        } else {
            ......
        }
        ......
        return retHolder;
    }

2.5 handleBindApplication

private void handleBindApplication(AppBindData data) {
        ......
        //step 1: 創建LoadedApk物件
   data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
        ......
        //step 2: 創建ContextImpl物件
        final ContextImpl appContext = 
              ContextImpl.createAppContext(this, data.info);
        ......
            try {
                final ClassLoader cl = instrContext.getClassLoader();
                //step 3: 創建Instrumentation
                mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.
                    getClassName()).newInstance();
            } catch (Exception e) {
                ......
            }

            final ComponentName component = new ComponentName(ii.packageName, ii.name);
            mInstrumentation.init(this, instrContext, appContext, component,
                    data.instrumentationWatcher, data.instrumentationUiAutomationConnection);

            ......
            }
        } else {
            mInstrumentation = new Instrumentation();
            mInstrumentation.basicInit(this);
        }

        ......
        Application app;
        ......
        try {
            //step 4: 創建Application物件
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            ......     
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                //step 5: 安裝providers
                    installContentProviders(app, data.providers);
                }
            }            
            try {
                mInstrumentation.onCreate(data.instrumentationArgs);
            }
            catch (Exception e) {
                ......
            }
            try {
            //step 6: 執行Application.Create回呼
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                ......
            }
        } finally {
            ......
        }
        ......
    }

三 核心物件

上面介紹了4大組件以及 Application 的初始化程序,接下來再進一步說明其中 LoadedApk,ContextImpl,Application 的初始化程序,

3.1 創建LoadedApk

3.1.1 ActivityThread.getPackageInfo

public final LoadedApk getPackageInfo(ApplicationInfo ai,
        CompatibilityInfo compatInfo, int flags) {
      boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
        boolean securityViolation = includeCode && ai.uid != 0
                && ai.uid != Process.SYSTEM_UID && 
                (mBoundApplication != null
                ? !UserHandle.isSameApp(ai.uid, 
                mBoundApplication.appInfo.uid) : true);
        boolean registerPackage = includeCode && 
                (flags&Context.CONTEXT_REGISTER_PACKAGE) != 0;
        if ((flags&(Context.CONTEXT_INCLUDE_CODE
                |Context.CONTEXT_IGNORE_SECURITY))
                == Context.CONTEXT_INCLUDE_CODE) {
            if (securityViolation) {//違反隱私問題, 拋出SecurityException
                String msg = "Requesting code from " + ai.packageName
                        + " (with uid " + ai.uid + ")";
                if (mBoundApplication != null) {
                    msg = msg + " to be run in process "
                     + mBoundApplication.processName + " (with uid "
                     + mBoundApplication.appInfo.uid + ")";
                }
                throw new SecurityException(msg);
            }
        }
        return getPackageInfo(ai, compatInfo, null, 
        securityViolation, includeCode, registerPackage);
    }


final ArrayMap<String, WeakReference<LoadedApk>> mPackages =
        new ArrayMap<>();

private LoadedApk getPackageInfo(ApplicationInfo aInfo,
      CompatibilityInfo compatInfo, ClassLoader baseLoader, ......) {
        final boolean differentUser = (UserHandle.myUserId() !=
              UserHandle.getUserId(aInfo.uid));
        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref;
            if (differentUser) {
                // Caching not supported across users
                ref = null;
            } else if (includeCode) {
                ref = mPackages.get(aInfo.packageName);
            } else {
                ref = mResourcePackages.get(aInfo.packageName);
            }
            LoadedApk packageInfo = ref != null ? ref.get() : null;
            if (packageInfo != null) {
             if (!isLoadedApkResourceDirsUpToDate(packageInfo, aInfo)) {
                 packageInfo.updateApplicationInfo(aInfo, null);
             }
                return packageInfo;
            }
            ......
            packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                    securityViolation, includeCode
                 && (aInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0,
                    registerPackage);

            if (mSystemThread && "android".equals(aInfo.packageName)) {
                packageInfo.installSystemApplicationInfo(aInfo,
                     getSystemContext().mPackageInfo.getClassLoader());
            }

            if (differentUser) {
                // Caching not supported across users
            } else if (includeCode) {
                mPackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            } else {
                mResourcePackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            }
            return packageInfo;
        }
    }
  • mPackages 的資料型別為 ArrayMap<String, WeakReference>,記錄著每一個包名所對應的 LoadedApk 物件的弱參考
  • 當 mPackages 沒有找到相應的 LoadedApk 物件,則創建該物件并加入到 mPackages

3.1.2 ActivityThread.getPackageInfoNoCheck

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
            CompatibilityInfo compatInfo) {
        return getPackageInfo(ai, compatInfo, null, false, true, false);
    }

除了 Activity 的初始化,其他組件的初始化都是采用該方法,有默認引數值,主要功能不變,

  • securityViolation = false,則不進行是否違反隱私的監測
  • registerPackage = false,則在獲取類加載器 (getClassLoader) 時,不會將該 package 添加到當前所在行程的成員變數 pkgDeps

3.2 創建Application

有了 LoadedApk 物件,接下來可以創建 Application 物件,該物件一個 Apk 只會創建一次,

3.2.1 LoadedApk.makeApplication

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

public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        //保證一個LoadedApk物件只創建一個對應的Application物件
        if (mApplication != null) {
            return mApplication;
        }
        ......
        Application app = null;
        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";//設定應用類名
        }
        try {
            java.lang.ClassLoader cl = getClassLoader();
            if (!mPackageName.equals("android")) {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        "initializeJavaContextClassLoader");
                initializeJavaContextClassLoader();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            }
            //創建ContextImpl物件
            ContextImpl appContext = 
            ContextImpl.createAppContext(mActivityThread, this);
      //創建Application物件, 并將appContext attach到新創建的Application
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {
            ......
        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app;//將剛創建的app賦值給mApplication

        if (instrumentation != null) {
            try {
                //呼叫application的OnCreate方法
                instrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                ......
            }
        }
        ......
        return app;
    }
  • 獲取當前應用的 ClassLoader 物件,根據是否為 ”android” 來決定呼叫 initializeJavaContextClassLoader()
  • 根據當前 ActivityThread 物件來創建相應的 ContextImpl 物件
  • 創建 Application 物件, 初始化其成員變數:
  1. mBase 指向新創建 ContextImpl
  2. mLoadedApk 指向當前所在的 LoadedApk 物件
  • 將新創建的 Application 物件保存到ContextImpl 的成員變數 mOuterContext

關于 initializeJavaContextClassLoader() 的程序,將會在Application創建中介紹,

關于應用類名采用的是 Apk 中宣告的應用類名,即 Manifest.xml 中定義的類名,有兩種特殊情況會強制設定應用類名為 ”android.app.Application”:

  • 當 forceDefaultAppClass = true,目前只有 system_server 行程初始化包名為 ”android” 的 apk 程序會呼叫
  • Apk 沒有自定義應用類名的情況

3.2.2 Instrumentation.newApplication

public Application newApplication(ClassLoader cl, String className,
 Context context) throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = getFactory(context.getPackageName())
                .instantiateApplication(cl, className);
        app.attach(context);
        return app;
    }

3.3 創建ContextImpl

創建 ContextImpl 的方式有多種,不同的組件初始化呼叫不同的方法,如下:

  • Activity:呼叫 createBaseContextForActivity 初始化
  • Service/Application:呼叫createAppContext 初始化
  • Provider:呼叫 createPackageContext 初始化
  • BroadcastReceiver:直接從 Application.getBaseContext() 來獲取 ContextImpl 物件

3.3.1 ActivityThread.createBaseContextForActivity

private ContextImpl createBaseContextForActivity(
        ActivityClientRecord r) {
        final int displayId;
        try {
        displayId = 
        ActivityTaskManager.getService().getActivityDisplayId(r.token);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
       //創建ContextImpl物件
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, 
                r.token, displayId, r.overrideConfig);

     final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
     String pkgName = SystemProperties.get("debug.second-display.pkg");
        if (pkgName != null && !pkgName.isEmpty()
                && r.packageInfo.mPackageName.contains(pkgName)) {
            for (int id : dm.getDisplayIds()) {
                if (id != Display.DEFAULT_DISPLAY) {
                 Display display =
                 dm.getCompatibleDisplay(id, appContext.getResources());
    appContext = (ContextImpl) appContext.createDisplayContext(display);
                    break;
                }
            }
        }
        return appContext;
    }

3.3.1.1 ContextImpl.createActivityContext

static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo,
            IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {
        String[] splitDirs = packageInfo.getSplitResDirs();
        ClassLoader classLoader = packageInfo.getClassLoader();

        if (packageInfo.getApplicationInfo().
        requestsIsolatedSplitLoading()) {
            try {
           classLoader = 
           packageInfo.getSplitClassLoader(activityInfo.splitName);
           splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
            } catch (NameNotFoundException e) {
                ......
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
            }
        }

        ContextImpl context = new ContextImpl(null, mainThread,
        packageInfo, activityInfo.splitName,
        activityToken, null, 0, classLoader, null);

        // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
        displayId = (displayId != Display.INVALID_DISPLAY) ?
        displayId : Display.DEFAULT_DISPLAY;

        final CompatibilityInfo compatInfo = 
                (displayId == Display.DEFAULT_DISPLAY)
                ? packageInfo.getCompatibilityInfo()
                : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;

        final ResourcesManager resourcesManager = 
        ResourcesManager.getInstance();
        context.setResources(resourcesManager.
        createBaseActivityResources(activityToken,
                packageInfo.getResDir(),
                splitDirs,
                packageInfo.getOverlayDirs(),
                packageInfo.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfiguration,
                compatInfo,
                classLoader));
     context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
                context.getResources());
        return context;
    }

Activity 采用該方法來初始化 ContextImpl 物件

3.3.2 ContextImpl.createAppContext

static ContextImpl createAppContext(ActivityThread mainThread,
        LoadedApk packageInfo) {
        return createAppContext(mainThread, packageInfo, null);
    }
    

static ContextImpl createAppContext(ActivityThread mainThread,
          LoadedApk packageInfo, String opPackageName) {
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo,
        null, null, null, 0, null, opPackageName);
        context.setResources(packageInfo.getResources());
        return context;
}

Service/Application 采用該方法來初始化 ContextImpl 物件

3.3.3 ContextImpl.createPackageContext

public Context createPackageContext(String packageName, int flags)
            throws NameNotFoundException {
        return createPackageContextAsUser(packageName, flags, mUser);
    }
    

@Override
public Context createPackageContextAsUser(String packageName,
   int flags, UserHandle user) throws NameNotFoundException {
        if (packageName.equals("system") ||
              packageName.equals("android")) {
            // The system resources are loaded in every application, 
    // so we can safely copy the context without reloading Resources.
            return new ContextImpl(this, mMainThread, mPackageInfo, 
            null, mActivityToken, user, flags, null, null);
        }
        //創建LoadedApk
        LoadedApk pi = mMainThread.getPackageInfo(packageName,
        mResources.getCompatibilityInfo(),
        flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
        if (pi != null) {
            ContextImpl c = new ContextImpl(this, mMainThread, pi,
            null, mActivityToken, user, flags, null, null);
            final int displayId = getDisplayId();
            c.setResources(createResources(mActivityToken, pi, null,
            displayId, null,
            getDisplayAdjustments(displayId).getCompatibilityInfo()));
            if (c.mResources != null) {
                return c;
            }
        }
        // Should be a better exception.
        throw new PackageManager.NameNotFoundException(
                "Application package " + packageName + " not found");
}

provider 采用該方法來初始化 ContextImpl 物件

3.3.4 ContextImpl初始化

class ContextImpl extends Context {
    final ActivityThread mMainThread;
    final LoadedApk mPackageInfo;
    private final IBinder mActivityToken;
    private final String mBasePackageName;
    private Context mOuterContext;
    //快取Binder服務
    final Object[] mServiceCache = 
    SystemServiceRegistry.createServiceCache();

    private ContextImpl(@Nullable ContextImpl container, 
    @NonNull ActivityThread mainThread, @NonNull LoadedApk packageInfo,
    @Nullable String splitName, @Nullable IBinder activityToken,
    @Nullable UserHandle user, int flags,
    @Nullable ClassLoader classLoader,
    @Nullable String overrideOpPackageName) {
        mOuterContext = this; //ContextImpl物件
        mMainThread = mainThread; // ActivityThread賦值
        mActivityToken = activityToken;
        mPackageInfo = packageInfo; // LoadedApk賦值
        String opPackageName;

        if (container != null) {
            mBasePackageName = container.mBasePackageName;
            opPackageName = container.mOpPackageName;
            setResources(container.mResources);
            mDisplay = container.mDisplay;
        } else {
            mBasePackageName = packageInfo.mPackageName;
            ApplicationInfo ainfo = packageInfo.getApplicationInfo();
            if (ainfo.uid == Process.SYSTEM_UID &&
            ainfo.uid != Process.myUid()) {
                opPackageName = ActivityThread.currentPackageName();
            } else {
                opPackageName = mBasePackageName;
            }
        }
        mOpPackageName = overrideOpPackageName != null ?
        overrideOpPackageName : opPackageName;
        mContentResolver =
        new ApplicationContentResolver(this, mainThread);
    }
}

四 Context attach

4.1 Activity

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, ......) {
        attachBaseContext(context);//呼叫父類方法設定mBase.
        ......
}

將新創建的 ContextImpl 賦值到父類 ContextWrapper.mBase 變數

4.2 Service

public final void attach(
            Context context,
            ActivityThread thread, String className, IBinder token,
            Application application, Object activityManager) {
        attachBaseContext(context);//呼叫父類方法設定mBase
        mThread = thread;           // NOTE:  unused - remove?
        mClassName = className;
        mToken = token;
        mApplication = application;
        mActivityManager = (IActivityManager)activityManager;
        mStartCompatibility = getApplicationInfo().targetSdkVersion
                < Build.VERSION_CODES.ECLAIR;
}

4.3 BroadcastReceiver

final Context getReceiverRestrictedContext() {
        if (mReceiverRestrictedContext != null) {
            return mReceiverRestrictedContext;
        }
        return mReceiverRestrictedContext = 
        new ReceiverRestrictedContext(getOuterContext());
    }

對于廣播來說 Context 的傳遞程序,跟其他組件完全不同,廣播是在 onCreate 程序通過引數將 ReceiverRestrictedContext 傳遞過去的,此處 getOuterContext() 回傳的是 ContextImpl 物件

4.4 ContentProvider

framework/base/core/java/android/content/ContentProvider.java

public void attachInfo(Context context, ProviderInfo info) {
        attachInfo(context, info, false);
    }

private void attachInfo(Context context, ProviderInfo info,
      boolean testing) {
        mNoPerms = testing;
        mCallingPackage = new ThreadLocal<>();

        if (mContext == null) {
     //將新創建ContextImpl物件保存到ContentProvider物件的成員變數mContext
            mContext = context;
            if (context != null && mTransport != null) {
                mTransport.mAppOpsManager = 
                (AppOpsManager) context.getSystemService(
                        Context.APP_OPS_SERVICE);
            }
            mMyUid = Process.myUid();
            if (info != null) {
                setReadPermission(info.readPermission);
                setWritePermission(info.writePermission);
                setPathPermissions(info.pathPermissions);
                mExported = info.exported;
    mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
                setAuthorities(info.authority);
            }
            // 執行onCreate回呼
            ContentProvider.this.onCreate();
        }
}

4.5 Application

final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

4.6 Context核心方法

物件方法回傳值型別含義
ActivitygetApplication()Application獲取Activity所屬的mApplication
ServicegetApplication()Application獲取Service所屬的mApplication
ContextWrappergetBaseContextContextImpl獲取mBase,即ContextImpl
ContextWrappergetApplicationContextApplication見小節4.6.1
ContextImplgetApplicationContextApplication見小節4.6.1
ContextImplgetOuterContextContextImpl獲取mOuterContext
ContextImplgetApplicationInfoApplicationInfomPackageInfo.mApplicationInfo

關于 Application

  • Activity 的 mApplication 是通過 makeApplication() 程序創建
  • Service 的 mApplication 同樣是通過 makeApplication() 程序創建
  • Receiver 是通過其方法 onReceive() 的第一個引數指向當前所在 Application
  • provider 無法獲取 application,因為其所在 application 不一定初始化

關于 mOuterContext: ContextImpl 的 mOuterContext,默認值是由 ContextImpl 初始化程序創建,但往往通過呼叫 setOuterContext() 使其指向外部的 Context

  • makeApplication 程序,mOuterContext 指向 Application
  • handleCreateService() 程序,mOuterContext 指向 Service
  • performLaunchActivity 的 createBaseContextForActivity 程序,mOuterContext 指向 Activity
  • BroadcastReceiver/Provider 則采用默認值 ContextImpl

4.6.1 ContextImpl.getApplicationContext

@Override
    public Context getApplicationContext() {
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication() : 
                mMainThread.getApplication();
    }


//上述mPackageInfo的資料型別為LoadedApk
public final class LoadedApk {
    Application getApplication() {
        return mApplication;
    }
}


//上述mMainThread為ActivityThread
public final class ActivityThread {
    public Application getApplication() {
        return mInitialApplication;
    }
}
  • mPackageInfo.getApplication():回傳的是 LoadedApk.mApplication
  1. Activity/Servie/BroadcastReceiver/Application 初始化, 呼叫 makeApplication() 完成;但對于同一個 apk 只會執行一次
  • mMainThread.getApplication(): 回傳的是 ActivityThread.mInitialApplication
  1. ActivityThread.handleBindApplication() 賦值
  2. system_server 行程的 ActivityThread.attach() 賦值

五 總結

5.1 組件初始化

下面用圖表來看看核心組件的初始化程序會創建哪些物件

型別LoadedApkContextImplApplication創建相應物件回呼方法
ActivityActivityonCreate
ServiceServiceonCreate
ReceiverBroadcastReceiveronReceive
ProviderContentProvideronCreate
ApplicationApplicationonCreate

每個 Apk 都對應唯一的 application 物件和 LoadedApk 物件,當 Apk 中任意組件的創建程序中,當其所對應的的 LoadedApk 和 Application 沒有初始化則會創建,且只會創建一次,

另外大家會注意到唯有 Provider 在初始化程序并不會去創建所相應的 Application 物件,也就意味著當有多個 Apk 運行在同一個行程的情況下,第二個 apk 通過 Provider 初始化程序再呼叫 getContext().getApplicationContext() 回傳的并非 Application 物件,而是 NULL,這里要注意會拋出空指標例外,

5.2 Context attach程序

  • Application
    呼叫 attachBaseContext() 將新創建 ContextImpl 賦值到父類 ContextWrapper.mBase 變數
    可通過 getBaseContext() 獲取該 ContextImpl
  • Activity/Service
    呼叫 attachBaseContext() 將新創建 ContextImpl 賦值到父類 ContextWrapper.mBase 變數
    可通過 getBaseContext() 獲取該 ContextImpl
    可通過 getApplication() 獲取其所在的 Application 物件
  • ContentProvider
    呼叫 attachInfo() 將新創建 ContextImpl 保存到 ContentProvider.mContext 變數
    可通過 getContext() 獲取該 ContextImpl
  • BroadcastReceiver
    在 onCreate 程序通過引數將 ReceiverRestrictedContext 傳遞過去的
  • ContextImpl
    可通過 getApplicationContext() 獲取 Application

5.3 Context使用場景

型別startActivitystartServicebindServicesendBroadcastregisterReceiver
Activity
Service-
Receiver-×-
Provider-
Application-

說明: (圖中第一列代表不同的 Context,“是” 代表允許在該 Context 執行相應的操作;“×” 代表不允許; “-” 代表分情況討論)

  • 當 Context 為 Receiver 的情況下
  1. 不允許執行 bindService() 操作, 由于限制性背景關系 (ReceiverRestrictedContext) 所決定的,會直接拋出例外.
  2. registerReceiver 是否允許取決于 receiver,當 receiver == null 用于獲取 sticky 廣播,允許使用;否則不允許使用 registerReceiver
  • 縱向來看 startActivity 操作
  1. 當為 Activity Context 則可直接使用
  2. 當為其他 Context, 則必須帶上 FLAG_ACTIVITY_NEW_TASK flags 才能使用
  3. 另外 UI 相關要 Activity 中使用
  • 除了以上情況, 其他的操作都是被允許執行

5.4 getApplicationContext

絕大多數情況下,getApplication() 和 getApplicationContext() 這兩個方法完全一致,回傳值也相同;那么兩者到底有什么區別呢?我們來討論下這個問題:

getApplicationContext() 存在是 Android 歷史原因,我們都知道 getApplication() 只存在于 Activity 和 Service 物件;那么對于 BroadcastReceiver 和 ContentProvider 卻無法獲取 Application,這時就需要一個能在 Context 背景關系直接使用的方法,那便是 getApplicationContext(),

兩者對比:

  • 對于 Activity/Service 來說,getApplication() 和 getApplicationContext() 的回傳值完全相同;除非廠商修改過介面
  • BroadcastReceiver 在 onReceive 的程序, 能使用 getBaseContext().getApplicationContext 獲取所在 Application, 而無法使用 getApplication
  • ContentProvider 能使用 getContext().getApplicationContext() 獲取所在Application,絕大多數情況下沒有問題,但是有可能會出現空指標的問題,情況如下:

當同一個行程有多個 apk 的情況下, 對于第二個 apk 是由 provider 方式拉起的, 前面介紹過 provider 創建程序并不會初始化所在 application,此時執行 getContext().getApplicationContext() 回傳的結果便是 NULL,所以對于這種情況要做好判空,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/243868.html

標籤:其他

上一篇:安卓學習筆記40:基于套接字網路編程

下一篇:原來這么簡單!使用PopupWindow制作微信頂部選單項,Android經典應用實體集錦1000例之(第3例)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more