国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

深入解析Android App開發中Context的用法

2020-01-02 07:03:31
字體:
來源:轉載
供稿:網友

Context在開發Android應用的過程中扮演著非常重要的角色,比如啟動一個Activity需要使用context.startActivity方法,將一個xml文件轉換為一個View對象也需要使用Context對象,可以這么說,離開了這個類,Android開發寸步難行,對于這樣一個類,我們又對他了解多少呢。我就說說我的感受吧,在剛開始學習Android開發時,感覺使用Context的地方一直就是傳入一個Activity對象,久而久之感覺只要是Context的地方就傳入一個Activity就行了,那么我們現在就來詳細的分析一下Context和Activity的關系吧!
在開始本文之前我們先放置一個問題在這里:
我們平時在獲取項目資源時使用context.getResources()的時候為什么放回的是同一個值,明明是使用不同的Activity調用getResources返回結果卻是一樣的。

2016227165415276.jpg (541×283)

Context本身是一個純的abstract類,ContextWrapper是對Context的一個包裝而已,它的內部包含了一個Context對象,其實對ContextWrapper的方法調用最終都是調用其中的Context對象完成的,至于ContextThremeWrapper,很明顯和Theme有關,所以Activity從ContextThemmWrapper繼承,而Service從ContextWrapper繼承,ContextImpl是唯一一個真正實現了Context中方法的類。
 
從上面的繼承關系來看,每一個Activity就是一個Context,每一個Service就是一個Context,這也就是為什么使用Context的地方可以被Activity或者Service替換了。
 

創建Context
根據前面所說,由于實現了Context的只有ContextImpl類,Activity和Service本沒有真正的實現,他們只是內部包含了一個真實的Context對象而已,也就是在在創建Activity或者Service的時候肯定要創建愛你一個ContextImpl對象,并賦值到Activity中的Context類型變量中。那我們就來看看Andorid源碼中有哪些地方創建了ContextImpl.
據統計Android中創建ContextImpl的地方一共有7處:

  • 在PackageInfo.makeApplication()中
  • 在performLaunchActivity()中
  • 在handleCreateBackupAgent()中
  • 在handleCreateService()中
  • 2次在hanldBinderAppplication()中
  • 在attach()方法中


由于創建ContextImpl的基本原理類似,所以這里只會分析幾個比較有代表性的地方:
1、  Application對應的Context
在應用程序啟動時,都會創建一個Application對象,所以輾轉調用到handleBindApplication()方法。

private final void handleBindApplication(AppBindData data) {     mBoundApplication = data;     mConfiguration = new Configuration(data.config);      ....     data.info = getPackageInfoNoCheck(data.appInfo);        ...          Application app = data.info.makeApplication(data.restrictedBackupMode, null);     mInitialApplication = app;     ....  } 

其中data.info是LoadedApk類型的,到getPackageInfoNoCheck中看看源碼

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

里面其實調用的是getPackageInfo,繼續跟進:

if (includeCode) {         ref = mPackages.get(aInfo.packageName);       } else {         ref = mResourcePackages.get(aInfo.packageName);       }       LoadedApk packageInfo = ref != null ? ref.get() : null;       if (packageInfo == null || (packageInfo.mResources != null           && !packageInfo.mResources.getAssets().isUpToDate())) {         if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "             : "Loading resource-only package ") + aInfo.packageName             + " (in " + (mBoundApplication != null                 ? mBoundApplication.processName : null)             + ")");         packageInfo =           new LoadedApk(this, aInfo, this, baseLoader,               securityViolation, includeCode &&               (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0); if (includeCode) {           mPackages.put(aInfo.packageName,               new WeakReference<LoadedApk>(packageInfo));         } else {           mResourcePackages.put(aInfo.packageName,               new WeakReference<LoadedApk>(packageInfo));         } 

由于includeCode傳入的是true,所以首先從mPackages中獲取,如果沒有,則new一個出來,并放入mPackages里面去,注意,這里的mPackages是ActivityThread中的屬性。
下面繼續分析一下LoadedApk這個類中的makeApplication函數

try {       java.lang.ClassLoader cl = getClassLoader();       //創建一個ContextImpl對象       ContextImpl appContext = new ContextImpl();       appContext.init(this, null, mActivityThread);       app = mActivityThread.mInstrumentation.newApplication(           cl, appClass, appContext);       appContext.setOuterContext(app);     } catch (Exception e) {       if (!mActivityThread.mInstrumentation.onException(app, e)) {         throw new RuntimeException(           "Unable to instantiate application " + appClass           + ": " + e.toString(), e);       }     } 

這里創建了一個ContextImpl對象,并調用了它的init方法,現在進入init方法。 

mPackageInfo = packageInfo; mResources = mPackageInfo.getResources(mainThread); 

對mPackageInof和mResources兩個變量初始化
回到makeApplication中,創建了一個Application對象,并將appContext傳進去,其實就是將appContext傳遞給ContextWrapper中的Context類型變量(Application也是繼承ContextWrapper)
2、Activity中的Context
在創建一個Activity時,經過輾轉調用,會執行handleLaunchActivity(),然后調用performLaunchActivity(),該方法創建ContextImpl代碼如下:

r.packageInfo= getPackageInfo(aInfo.applicationInfo,          Context.CONTEXT_INCLUDE_CODE); ContextImplappContext = new ContextImpl();        appContext.init(r.packageInfo,r.token, this);        appContext.setOuterContext(activity); activity.attach(appContext,this, getInstrumentation(), r.token,            r.ident, app, r.intent,r.activityInfo, title, r.parent,            r.embeddedID,r.lastNonConfigurationInstance,            r.lastNonConfigurationChildInstances, config);

由于getPackageInfo函數之前已經分析過了,稍微有點區別,但是大致流程是差不多的,所以此處的appContext執行init之后,其中的mPackages變量和mResources變量時一樣的,activity通過attach函數將該appContext賦值到ContextWrapper中的Context類型變量。

3、Service中的Context
同樣 在創建一個Service時,經過輾轉調用會調用到scheduleCreateService方法,之后會巧用handleCreateService

LoadedApkpackageInfo = getPackageInfoNoCheck(        data.info.applicationInfo); ContextImplcontext = new ContextImpl();      context.init(packageInfo, null,this);       Application app =packageInfo.makeApplication(false, mInstrumentation);      context.setOuterContext(service);      service.attach(context, this,data.info.name, data.token, app,          ActivityManagerNative.getDefault());

其思路和上面兩個基本一樣,在此就不再詳述。

Context對資源的訪問
很明確,不同的Context得到的都是同一份資源。這是很好理解的,請看下面的分析

得到資源的方式為context.getResources,而真正的實現位于ContextImpl中的getResources方法,在ContextImpl中有一個成員 private Resources mResources,它就是getResources方法返回的結果,mResources的賦值代碼為:

mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),          Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);

下面看一下ResourcesManager的getTopLevelResources方法,這個方法的思想是這樣的:在ResourcesManager中,所有的資源對象都被存儲在ArrayMap中,首先根據當前的請求參數去查找資源,如果找到了就返回,否則就創建一個資源對象放到ArrayMap中。有一點需要說明的是為什么會有多個資源對象,原因很簡單,因為res下可能存在多個適配不同設備、不同分辨率、不同系統版本的目錄,按照android系統的設計,不同設備在訪問同一個應用的時候訪問的資源可以不同,比如drawable-hdpi和drawable-xhdpi就是典型的例子。

public Resources getTopLevelResources(String resDir, int displayId,     Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {   final float scale = compatInfo.applicationScale;   ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,       token);   Resources r;   synchronized (this) {     // Resources is app scale dependent.     if (false) {       Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);     }     WeakReference<Resources> wr = mActiveResources.get(key);     r = wr != null ? wr.get() : null;     //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());     if (r != null && r.getAssets().isUpToDate()) {       if (false) {         Slog.w(TAG, "Returning cached resources " + r + " " + resDir             + ": appScale=" + r.getCompatibilityInfo().applicationScale);       }       return r;     }   }    //if (r != null) {   //  Slog.w(TAG, "Throwing away out-of-date resources!!!! "   //      + r + " " + resDir);   //}    AssetManager assets = new AssetManager();   if (assets.addAssetPath(resDir) == 0) {     return null;   }    //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);   DisplayMetrics dm = getDisplayMetricsLocked(displayId);   Configuration config;   boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);   final boolean hasOverrideConfig = key.hasOverrideConfiguration();   if (!isDefaultDisplay || hasOverrideConfig) {     config = new Configuration(getConfiguration());     if (!isDefaultDisplay) {       applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);     }     if (hasOverrideConfig) {       config.updateFrom(key.mOverrideConfiguration);     }   } else {     config = getConfiguration();   }   r = new Resources(assets, dm, config, compatInfo, token);   if (false) {     Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "         + r.getConfiguration() + " appScale="         + r.getCompatibilityInfo().applicationScale);   }    synchronized (this) {     WeakReference<Resources> wr = mActiveResources.get(key);     Resources existing = wr != null ? wr.get() : null;     if (existing != null && existing.getAssets().isUpToDate()) {       // Someone else already created the resources while we were       // unlocked; go ahead and use theirs.       r.getAssets().close();       return existing;     }      // XXX need to remove entries when weak references go away     mActiveResources.put(key, new WeakReference<Resources>(r));     return r;   } } 

根據上述代碼中資源的請求機制,再加上ResourcesManager采用單例模式,這樣就保證了不同的ContextImpl訪問的是同一套資源,注意,這里說的同一套資源未必是同一個資源,因為資源可能位于不同的目錄,但它一定是我們的應用的資源,或許這樣來描述更準確,在設備參數和顯示參數不變的情況下,不同的ContextImpl訪問到的是同一份資源。設備參數不變是指手機的屏幕和android版本不變,顯示參數不變是指手機的分辨率和橫豎屏狀態。也就是說,盡管Application、Activity、Service都有自己的ContextImpl,并且每個ContextImpl都有自己的mResources成員,但是由于它們的mResources成員都來自于唯一的ResourcesManager實例,所以它們看似不同的mResources其實都指向的是同一塊內存(C語言的概念),因此,它們的mResources都是同一個對象(在設備參數和顯示參數不變的情況下)。在橫豎屏切換的情況下且應用中為橫豎屏狀態提供了不同的資源,處在橫屏狀態下的ContextImpl和處在豎屏狀態下的ContextImpl訪問的資源不是同一個資源對象。

 

代碼:單例模式的ResourcesManager類

public static ResourcesManager getInstance() {   synchronized (ResourcesManager.class) {     if (sResourcesManager == null) {       sResourcesManager = new ResourcesManager();     }     return sResourcesManager;   } } 

getApplication和getApplicationContext的區別
 

getApplication返回結果為Application,且不同的Activity和Service返回的Application均為同一個全局對象,在ActivityThread內部有一個列表專門用于維護所有應用的application

 

  final ArrayList<Application> mAllApplications = new ArrayList<Application>();

getApplicationContext返回的也是Application對象,只不過返回類型為Context,看看它的實現

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

上面代碼中mPackageInfo是包含當前應用的包信息、比如包名、應用的安裝目錄等,原則上來說,作為第三方應用,包信息mPackageInfo不可能為空,在這種情況下,getApplicationContext返回的對象和getApplication是同一個。但是對于系統應用,包信息有可能為空,具體就不深入研究了。從這種角度來說,對于第三方應用,一個應用只存在一個Application對象,且通過getApplication和getApplicationContext得到的是同一個對象,兩者的區別僅僅是返回類型不同。


在此總結一下:
(1)Context是一個抽象類,ContextWrapper是對Context的封裝,它包含一個Context類型的變量,ContextWrapper的功能函數內部其實都是調用里面的Context類型變量完成的。Application,Service,Activity等都是直接或者間接繼承自ContextWrapper,但是并沒有真正的實現其中的功能,Application,Service,Activity中關于Context的功能都是通過其內部的Context類型變量完成的,而這個變量的真實對象必定是ContextImpl,所以沒創建一個Application,Activity,Servcice便會創建一個ContextImpl,并且這些ContextImpl中的mPackages和mResources變量都是一樣的,所以不管使用Acitivty還是Service調用getResources得到相同的結果
(2)在一個apk中,Context的數量等于Activity個數+Service個數+1.

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 临沂市| 海宁市| 定远县| 友谊县| 玛纳斯县| 萨迦县| 绿春县| 星子县| 辽阳市| 贵德县| 浦东新区| 策勒县| 望城县| 贺兰县| 鄱阳县| 渭源县| 方正县| 阿拉善左旗| 巧家县| 花垣县| 临邑县| 江阴市| 东城区| 天柱县| 邵阳县| 怀来县| 巍山| 抚州市| 广平县| 沾化县| 确山县| 炉霍县| 阿荣旗| 建德市| 武穴市| 太仆寺旗| 临潭县| 嘉荫县| 麟游县| 衡水市| 阿拉善右旗|