Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 深入解析Android App開發中Context的用法

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

編輯:關於Android編程

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.

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved