欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入解析Android App開發(fā)中Context的用法

 更新時(shí)間:2016年02月27日 17:08:21   作者:安卓吧  
這篇文章主要介紹了深入解析Android App開發(fā)中Context的用法,包括Context的創(chuàng)建場(chǎng)景和Context對(duì)資源的訪問等內(nèi)容,需要的朋友可以參考下

Context在開發(fā)Android應(yīng)用的過程中扮演著非常重要的角色,比如啟動(dòng)一個(gè)Activity需要使用context.startActivity方法,將一個(gè)xml文件轉(zhuǎn)換為一個(gè)View對(duì)象也需要使用Context對(duì)象,可以這么說,離開了這個(gè)類,Android開發(fā)寸步難行,對(duì)于這樣一個(gè)類,我們又對(duì)他了解多少呢。我就說說我的感受吧,在剛開始學(xué)習(xí)Android開發(fā)時(shí),感覺使用Context的地方一直就是傳入一個(gè)Activity對(duì)象,久而久之感覺只要是Context的地方就傳入一個(gè)Activity就行了,那么我們現(xiàn)在就來詳細(xì)的分析一下Context和Activity的關(guān)系吧!
在開始本文之前我們先放置一個(gè)問題在這里:
我們平時(shí)在獲取項(xiàng)目資源時(shí)使用context.getResources()的時(shí)候?yàn)槭裁捶呕氐氖峭粋€(gè)值,明明是使用不同的Activity調(diào)用getResources返回結(jié)果卻是一樣的。

2016227165415276.jpg (541×283)

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

創(chuàng)建Context
根據(jù)前面所說,由于實(shí)現(xiàn)了Context的只有ContextImpl類,Activity和Service本沒有真正的實(shí)現(xiàn),他們只是內(nèi)部包含了一個(gè)真實(shí)的Context對(duì)象而已,也就是在在創(chuàng)建Activity或者Service的時(shí)候肯定要?jiǎng)?chuàng)建愛你一個(gè)ContextImpl對(duì)象,并賦值到Activity中的Context類型變量中。那我們就來看看Andorid源碼中有哪些地方創(chuàng)建了ContextImpl.
據(jù)統(tǒng)計(jì)Android中創(chuàng)建ContextImpl的地方一共有7處:

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


由于創(chuàng)建ContextImpl的基本原理類似,所以這里只會(huì)分析幾個(gè)比較有代表性的地方:
1、  Application對(duì)應(yīng)的Context
在應(yīng)用程序啟動(dòng)時(shí),都會(huì)創(chuàng)建一個(gè)Application對(duì)象,所以輾轉(zhuǎn)調(diào)用到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); 
} 

里面其實(shí)調(diào)用的是getPackageInfo,繼續(xù)跟進(jìn):

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一個(gè)出來,并放入mPackages里面去,注意,這里的mPackages是ActivityThread中的屬性。
下面繼續(xù)分析一下LoadedApk這個(gè)類中的makeApplication函數(shù)

try { 
      java.lang.ClassLoader cl = getClassLoader(); 
      //創(chuàng)建一個(gè)ContextImpl對(duì)象 
      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); 
      } 
    } 

這里創(chuàng)建了一個(gè)ContextImpl對(duì)象,并調(diào)用了它的init方法,現(xiàn)在進(jìn)入init方法。 

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

對(duì)mPackageInof和mResources兩個(gè)變量初始化
回到makeApplication中,創(chuàng)建了一個(gè)Application對(duì)象,并將appContext傳進(jìn)去,其實(shí)就是將appContext傳遞給ContextWrapper中的Context類型變量(Application也是繼承ContextWrapper)
2、Activity中的Context
在創(chuàng)建一個(gè)Activity時(shí),經(jīng)過輾轉(zhuǎn)調(diào)用,會(huì)執(zhí)行handleLaunchActivity(),然后調(diào)用performLaunchActivity(),該方法創(chuàng)建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函數(shù)之前已經(jīng)分析過了,稍微有點(diǎn)區(qū)別,但是大致流程是差不多的,所以此處的appContext執(zhí)行init之后,其中的mPackages變量和mResources變量時(shí)一樣的,activity通過attach函數(shù)將該appContext賦值到ContextWrapper中的Context類型變量。

3、Service中的Context
同樣 在創(chuàng)建一個(gè)Service時(shí),經(jīng)過輾轉(zhuǎn)調(diào)用會(huì)調(diào)用到scheduleCreateService方法,之后會(huì)巧用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());

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

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

得到資源的方式為context.getResources,而真正的實(shí)現(xiàn)位于ContextImpl中的getResources方法,在ContextImpl中有一個(gè)成員 private Resources mResources,它就是getResources方法返回的結(jié)果,mResources的賦值代碼為:

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

下面看一下ResourcesManager的getTopLevelResources方法,這個(gè)方法的思想是這樣的:在ResourcesManager中,所有的資源對(duì)象都被存儲(chǔ)在ArrayMap中,首先根據(jù)當(dāng)前的請(qǐng)求參數(shù)去查找資源,如果找到了就返回,否則就創(chuàng)建一個(gè)資源對(duì)象放到ArrayMap中。有一點(diǎn)需要說明的是為什么會(huì)有多個(gè)資源對(duì)象,原因很簡(jiǎn)單,因?yàn)閞es下可能存在多個(gè)適配不同設(shè)備、不同分辨率、不同系統(tǒng)版本的目錄,按照android系統(tǒng)的設(shè)計(jì),不同設(shè)備在訪問同一個(gè)應(yīng)用的時(shí)候訪問的資源可以不同,比如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; 
  } 
} 

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

 

代碼:?jiǎn)卫J降腞esourcesManager類

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

getApplication和getApplicationContext的區(qū)別
 

getApplication返回結(jié)果為Application,且不同的Activity和Service返回的Application均為同一個(gè)全局對(duì)象,在ActivityThread內(nèi)部有一個(gè)列表專門用于維護(hù)所有應(yīng)用的application

 

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

getApplicationContext返回的也是Application對(duì)象,只不過返回類型為Context,看看它的實(shí)現(xiàn)

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

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


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

相關(guān)文章

最新評(píng)論