作者:兩日的blog
Context是什么郭计,有什么用
在Android開發(fā)中霞揉,Context是一個抽象類有额,它是Android應(yīng)用程序環(huán)境的一部分竹海。它提供了訪問應(yīng)用程序資源和執(zhí)行各種操作的接口慕蔚。可以說,Context是Android應(yīng)用程序與系統(tǒng)環(huán)境進行交互的橋梁斋配。
Context的作用包括:
- 訪問應(yīng)用程序資源:通過
Context
孔飒,可以獲取應(yīng)用程序的資源,如字符串许起、布局文件十偶、圖像等。這些資源可以在應(yīng)用程序的各個組件中使用园细,例如Activity
惦积、Service
、BroadcastReceiver
等猛频。 - 啟動組件:通過
Context
狮崩,可以啟動其他組件,如啟動Activity
鹿寻、啟動Service
睦柴、發(fā)送廣播等。它提供了訪問系統(tǒng)服務(wù)的能力毡熏,如啟動其他應(yīng)用程序坦敌、發(fā)送系統(tǒng)廣播等。 - 獲取應(yīng)用程序的上下文:通過
Context
,可以獲取應(yīng)用程序的上下文狱窘,如獲取ApplicationContext
杜顺,用于在整個應(yīng)用程序中共享數(shù)據(jù)或執(zhí)行全局操作。 - 訪問系統(tǒng)服務(wù):通過
Context
蘸炸,可以訪問各種系統(tǒng)服務(wù)躬络,如獲取系統(tǒng)級的服務(wù)(如傳感器服務(wù)、位置服務(wù))搭儒、訪問設(shè)備功能(如攝像頭穷当、存儲器)、執(zhí)行網(wǎng)絡(luò)操作等淹禾。 - 訪問應(yīng)用程序的文件:通過
Context
對象馁菜,可以獲取應(yīng)用程序的文件目錄,創(chuàng)建稀拐、讀取火邓、寫入和刪除文件等操作。 - 處理資源生命周期:通過
Context
德撬,可以管理應(yīng)用程序資源的生命周期铲咨,如創(chuàng)建、銷毀對象蜓洪、注冊和注銷監(jiān)聽器等纤勒。它提供了一種機制,確保資源的正確使用和釋放隆檀,避免內(nèi)存泄漏等問題摇天。
public abstract AssetManager getAssets();
/**
* Returns a Resources instance for the application's package.
* <p>
* <strong>Note:</strong> Implementations of this method should return
* a Resources instance that is consistent with the AssetManager instance
* returned by {@link #getAssets()}. For example, they should share the
* same {@link Configuration} object.
*
* @return a Resources instance for the application's package
* @see #getAssets()
*/
public abstract Resources getResources();
/** Return PackageManager instance to find global package information. */
public abstract PackageManager getPackageManager();
/** Return a ContentResolver instance for your application's package. */
public abstract ContentResolver getContentResolver();
/**
* Return the Looper for the main thread of the current process. This is
* the thread used to dispatch calls to application components (activities,
* services, etc).
* <p>
* By definition, this method returns the same result as would be obtained
* by calling {@link Looper#getMainLooper() Looper.getMainLooper()}.
* </p>
*
* @return The main looper.
*/
public abstract Looper getMainLooper();
/**
* Return an {@link Executor} that will run enqueued tasks on the main
* thread associated with this context. This is the thread used to dispatch
* calls to application components (activities, services, etc).
*/
public Executor getMainExecutor() {
// This is pretty inefficient, which is why ContextImpl overrides it
return new HandlerExecutor(new Handler(getMainLooper()));
}
public abstract Context getApplicationContext();
public final CharSequence getText(@StringRes int resId) {
return getResources().getText(resId);
}
/**
* Returns a localized string from the application's package's
* default string table.
*
* @param resId Resource id for the string
* @return The string data associated with the resource, stripped of styled
* text information.
*/
@NonNull
public final String getString(@StringRes int resId) {
return getResources().getString(resId);
}
/**
* Returns a localized formatted string from the application's package's
* default string table, substituting the format arguments as defined in
* {@link java.util.Formatter} and {@link java.lang.String#format}.
*
* @param resId Resource id for the format string
* @param formatArgs The format arguments that will be used for
* substitution.
* @return The string data associated with the resource, formatted and
* stripped of styled text information.
*/
@NonNull
public final String getString(@StringRes int resId, Object... formatArgs) {
return getResources().getString(resId, formatArgs);
}
/**
* Returns a color associated with a particular resource ID and styled for
* the current theme.
*
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
* @return A single color value in the form 0xAARRGGBB.
* @throws android.content.res.Resources.NotFoundException if the given ID
* does not exist.
*/
@ColorInt
public final int getColor(@ColorRes int id) {
return getResources().getColor(id, getTheme());
}
/**
* Returns a drawable object associated with a particular resource ID and
* styled for the current theme.
*
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
* @return An object that can be used to draw this resource.
* @throws android.content.res.Resources.NotFoundException if the given ID
* does not exist.
*/
@Nullable
public final Drawable getDrawable(@DrawableRes int id) {
return getResources().getDrawable(id, getTheme());
}
/**
* Returns a color state list associated with a particular resource ID and
* styled for the current theme.
*
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
* @return A color state list.
* @throws android.content.res.Resources.NotFoundException if the given ID
* does not exist.
*/
@NonNull
public final ColorStateList getColorStateList(@ColorRes int id) {
return getResources().getColorStateList(id, getTheme());
}
/**
* Set the base theme for this context. Note that this should be called
* before any views are instantiated in the Context (for example before
* calling {@link android.app.Activity#setContentView} or
* {@link android.view.LayoutInflater#inflate}).
*
* @param resid The style resource describing the theme.
*/
public abstract void setTheme(@StyleRes int resid);
/** @hide Needed for some internal implementation... not public because
* you can't assume this actually means anything. */
@UnsupportedAppUsage
public int getThemeResId() {
return 0;
}
/**
* Return the Theme object associated with this Context.
*/
@ViewDebug.ExportedProperty(deepExport = true)
public abstract Resources.Theme getTheme();
/**
* Retrieve styled attribute information in this Context's theme. See
* {@link android.content.res.Resources.Theme#obtainStyledAttributes(int[])}
* for more information.
*
* @see android.content.res.Resources.Theme#obtainStyledAttributes(int[])
*/
@NonNull
public final TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[] attrs) {
return getTheme().obtainStyledAttributes(attrs);
}
/**
* Retrieve styled attribute information in this Context's theme. See
* {@link android.content.res.Resources.Theme#obtainStyledAttributes(int, int[])}
* for more information.
*
* @see android.content.res.Resources.Theme#obtainStyledAttributes(int, int[])
*/
@NonNull
public final TypedArray obtainStyledAttributes(@StyleRes int resid,
@NonNull @StyleableRes int[] attrs) throws Resources.NotFoundException {
return getTheme().obtainStyledAttributes(resid, attrs);
}
/**
* Retrieve styled attribute information in this Context's theme. See
* {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
* for more information.
*
* @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
*/
@NonNull
public final TypedArray obtainStyledAttributes(
@Nullable AttributeSet set, @NonNull @StyleableRes int[] attrs) {
return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
}
/**
* Retrieve styled attribute information in this Context's theme. See
* {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
* for more information.
*
* @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
*/
@NonNull
public final TypedArray obtainStyledAttributes(@Nullable AttributeSet set,
@NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
return getTheme().obtainStyledAttributes(
set, attrs, defStyleAttr, defStyleRes);
}
總之,Context
在Android開發(fā)中具有重要的作用恐仑,它提供了訪問應(yīng)用程序資源泉坐、啟動組件、訪問系統(tǒng)服務(wù)以及處理資源生命周期的能力裳仆。開發(fā)者可以使用Context
來實現(xiàn)各種應(yīng)用程序功能和與系統(tǒng)環(huán)境的交互腕让。
Context有哪些
Context
本身是一個抽象類,主要實現(xiàn)類為 ContextImpl
歧斟,另外有子類 ContextWrapper
和 ContextThemeWrapper
纯丸,另外還有其他由上述三個類引申出來的Context
類,Application
/Service
/Activity
静袖,他們的繼承關(guān)系如下:
ContextImpl
/ContextWrapper
/ContextThemeWrapper
的區(qū)別
ContextImpl | ContextWrapper | ContextThemeWrapper |
---|---|---|
ContextImpl 是Context 的主要實現(xiàn)類觉鼻,它提供了大部分Context 的基本功能和行為。它是Android框架中真正的上下文實現(xiàn)類队橙,用于處理應(yīng)用程序的資源訪問坠陈、組件啟動萨惑、文件操作和系統(tǒng)服務(wù)等操作。 |
ContextWrapper 是一個包裝類仇矾,用于對現(xiàn)有的Context對象進行包裝或修改其功能咒钟。它是Context 的一個間接子類,可以通過繼承ContextWrapper 類來擴展Context 的功能若未,例如添加自定義的行為或修改Context 的行為。 |
ContextThemeWrapper :ContextThemeWrapper 是Context 的另一個包裝類倾鲫,它繼承自ContextWrapper 類粗合。與ContextWrapper 類似,ContextThemeWrapper 也是用于包裝現(xiàn)有的Context 對象乌昔,但它還提供了自己的主題資源隙疚。通過ContextThemeWrapper ,可以為特定的上下文設(shè)置不同的主題磕道,以實現(xiàn)界面的樣式和外觀的變化供屉。 |
ContextImpl
上文說到,Context
本身是一個抽象類溺蕉,主要的實現(xiàn)類就是ContextImpl
伶丐,即Context
的那些功能都是在ContexImpl
中實現(xiàn)的,即ContextImpl
實際承擔(dān)著提供應(yīng)用程序資源訪問疯特、組件啟動和系統(tǒng)服務(wù)等功能的責(zé)任哗魂。
public class ContextImpl extends Context {
private Resources mResources;
private Theme mTheme;
void setResources(Resources r) {
if (r instanceof CompatResources) {
((CompatResources) r).setContext(this);
}
mResources = r;
}
@Override
public Resources getResources() {
return mResources;
}
@Override
public void setTheme(int resId) {
synchronized (mSync) {
if (mThemeResource != resId) {
mThemeResource = resId;
initializeTheme();
}
}
}
public Resources.Theme getTheme() {
synchronized (mSync) {
if (mTheme != null) {
return mTheme;
}
mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getOuterContext().getApplicationInfo().targetSdkVersion);
initializeTheme();
return mTheme;
}
}
private void initializeTheme() {
if (mTheme == null) {
mTheme = mResources.newTheme();
}
mTheme.applyStyle(mThemeResource, true);
}
// 其他方法的實現(xiàn)省略...
}
在ContextImpl
,我們重點關(guān)注一下Resource
及Theme
的相關(guān)實現(xiàn)漓雅,ContextImpl
中提供了getResources/setResources
方法录别,用于獲取Resources
以及設(shè)置Resources
,以提供資源的訪問邻吞。
在getTheme/setTheme
用于獲取Theme
以及設(shè)置Theme
组题,以提供對主題的訪問.
重點看一下getTheme()
方法,該方法抱冷,會首先獲取mThemeResource
崔列,這里直接選擇的系統(tǒng)默認主題,系統(tǒng)會根據(jù)不同的sdk版本選擇不同的默認主題徘层。
@UnsupportedAppUsage
public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
return selectSystemTheme(curTheme, targetSdkVersion,
com.android.internal.R.style.Theme,
com.android.internal.R.style.Theme_Holo,
com.android.internal.R.style.Theme_DeviceDefault,
com.android.internal.R.style.Theme_DeviceDefault_Light_DarkActionBar);
}
/** @hide */
public static int selectSystemTheme(int curTheme, int targetSdkVersion, int orig, int holo,
int dark, int deviceDefault) {
if (curTheme != ID_NULL) {
return curTheme;
}
if (targetSdkVersion < Build.VERSION_CODES.HONEYCOMB) {
return orig;
}
if (targetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return holo;
}
if (targetSdkVersion < Build.VERSION_CODES.N) {
return dark;
}
return deviceDefault;
}
通過ContextImpl
的實例峻呕,應(yīng)用程序可以獲取到Resources
對象和Theme
對象,從而實現(xiàn)對資源和主題的訪問和處理趣效。需要注意的是瘦癌,這是一個簡化的,實際的ContextImpl
源碼非常復(fù)雜跷敬,還涉及到處理上下文的生命周期讯私、系統(tǒng)服務(wù)的獲取等方面的邏輯。
ContextWrapper
ContextWrapper
是一個包裝類,內(nèi)部包含一個mBase
成員變量斤寇,所有的實現(xiàn)都是調(diào)用mBase
的方法桶癣。
public class ContextWrapper extends Context {
@UnsupportedAppUsage
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
@Override
public void setTheme(int resid) {
mBase.setTheme(resid);
}
/** @hide */
@Override
@UnsupportedAppUsage
public int getThemeResId() {
return mBase.getThemeResId();
}
@Override
public Resources.Theme getTheme() {
return mBase.getTheme();
}
@Override
public ClassLoader getClassLoader() {
return mBase.getClassLoader();
}
@Override
public String getPackageName() {
return mBase.getPackageName();
}
}
ContextThemeWrapper
ContextThemeWrapper
繼承自ContextWrapper
,從名字中可以看出娘锁,該類主要是跟主題相關(guān)的包裝類:
public class ContextThemeWrapper extends ContextWrapper {
...
@Override
public Resources getResources() {
return getResourcesInternal();
}
private Resources getResourcesInternal() {
if (mResources == null) {
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else {
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
@Override
public void setTheme(int resid) {
if (mThemeResource != resid) {
mThemeResource = resid;
initializeTheme();
}
}
@Override
public Resources.Theme getTheme() {
if (mTheme != null) {
return mTheme;
}
mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getApplicationInfo().targetSdkVersion);
initializeTheme();
return mTheme;
}
@UnsupportedAppUsage
private void initializeTheme() {
final boolean first = mTheme == null;
if (first) {
mTheme = getResources().newTheme();
final Resources.Theme theme = getBaseContext().getTheme();
if (theme != null) {
mTheme.setTo(theme);
}
}
onApplyThemeResource(mTheme, mThemeResource, first);
}
...
}
和ContextImpl
相比較牙寞,ContextThemeWrapper
中獲取資源以及主題的代碼有所不同,多了一個Configuration
莫秆,其他行為大致一致间雀。
另外在AppCompat
中,默認的主題為Theme_AppCompat_Light
镊屎,
package androidx.appcompat.view;
public class ContextThemeWrapper extends ContextWrapper {
...
@Override
public Resources.Theme getTheme() {
if (mTheme != null) {
return mTheme;
}
if (mThemeResource == 0) {
mThemeResource = R.style.Theme_AppCompat_Light;
}
initializeTheme();
return mTheme;
}
}
App中不同Context對象的Theme
我們在開發(fā)中惹挟,經(jīng)常會用到各種Context,常用的有activity/application/applicationContext/baseContext缝驳,為了測試不同Context中Theme對象连锯,我們編寫如下代碼:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
printLog("baseContext is ${baseContext.themeResId} baseContext is $baseContext")
printLog("application is ${application.themeResId} application is $application")
printLog("applicationContext is ${applicationContext.themeResId} applicationContext is $applicationContext")
printLog("activity is ${this.themeResId}")
}
private fun printLog(msg: String) {
println("MainActivity themeResId in $msg")
}
}
我們分別獲取每個Context
對應(yīng)的themeResId
,即每個Context
中Theme
對應(yīng)的resId
:
對代碼運行結(jié)果我們有如下結(jié)論:
-
getApplication
和getApplicationContext
得到的是同一個Application
實例對象用狱; -
Application
對象中的themeResId
為0 运怖,Application
其實也有主題的應(yīng)用,畢竟主題樣式都是針對UI元素的齿拂; -
Activity
****中的主題和****getBaseContext
****中的主題是不一樣的驳规,具體對應(yīng)什么主題下文將進行探究 -
getBaseContext
中得到的是ContextThemeWrapper
,這點讓我有點意外署海,之前的理解都是Activity啟動時吗购,會新建一個ContextImpl
對象,在attachBaseContext
中賦予Activity
中的mBase
砸狞,于是仔細研究一下發(fā)現(xiàn)捻勉,其實是AppCompatActivity做了替換:
//androidx.appcompat.app.AppCompatActivity
// AppCompatActivity重寫了Activity中的attachBaseContext方法
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(getDelegate().attachBaseContext2(newBase));
}
我們看一下代理類AppCompatDelegateImpl
中attachBaseContext2
的實現(xiàn):
//androidx.appcompat.app.AppCompatDelegateImpl
@NonNull
@Override
@CallSuper
public Context attachBaseContext2(@NonNull final Context baseContext) {
mBaseContextAttached = true;
final int modeToApply = mapNightMode(baseContext, calculateNightMode());
// If the base context is a ContextThemeWrapper (thus not an Application context)
// and nobody's touched its Resources yet, we can shortcut and directly apply our
// override configuration.
if (sCanApplyOverrideConfiguration
&& baseContext instanceof android.view.ContextThemeWrapper) {
final Configuration config = createOverrideConfigurationForDayNight(
baseContext, modeToApply, null);
ContextThemeWrapperCompatApi17Impl.applyOverrideConfiguration(
(android.view.ContextThemeWrapper) baseContext, config);
return baseContext;
}
// Again, but using the AppCompat version of ContextThemeWrapper.
if (baseContext instanceof ContextThemeWrapper) {
final Configuration config = createOverrideConfigurationForDayNight(
baseContext, modeToApply, null);
((ContextThemeWrapper) baseContext).applyOverrideConfiguration(config);
return baseContext;
}
// We can't apply the configuration directly to the existing base context, so we need to
// wrap it. We can't create a new configuration context since the app may rely on method
// overrides or a specific theme -- neither of which are preserved when creating a
// configuration context. Instead, we'll make a best-effort at wrapping the context and
// rebasing the original theme.
if (!sCanReturnDifferentContext) {
return super.attachBaseContext2(baseContext);
}
Configuration configOverlay = null;
final Configuration config = createOverrideConfigurationForDayNight(
baseContext, modeToApply, configOverlay);
//重點1:新建ContextThemeWrapper對象將傳入的baseContext賦值給ContextWrapper中的mBase,
// 并且ContextThemeWrapper中的主題為Theme_AppCompat_Empty
// Next, we'll wrap the base context to ensure any method overrides or themes are left
// intact. Since ThemeOverlay.AppCompat theme is empty, we'll get the base context's theme.
final ContextThemeWrapper wrappedContext = new ContextThemeWrapper(baseContext,
R.style.Theme_AppCompat_Empty);
wrappedContext.applyOverrideConfiguration(config);
// Check whether the base context has an explicit theme or is able to obtain one
// from its outer context. If it throws an NPE because we're at an invalid point in app
// initialization, we don't need to worry about rebasing under the new configuration.
boolean needsThemeRebase;
try {
needsThemeRebase = baseContext.getTheme() != null;
} catch (NullPointerException e) {
needsThemeRebase = false;
}
if (needsThemeRebase) {
// Attempt to rebase the old theme within the new configuration. This will only
// work on SDK 23 and up, but it's unlikely that we're keeping the base theme
// anyway so maybe nobody will notice. Note that calling getTheme() will clone
// the base context's theme into the wrapped context's theme.
ResourcesCompat.ThemeCompat.rebase(wrappedContext.getTheme());
}
return super.attachBaseContext2(wrappedContext);
}
//androidx.appcompat.app.AppCompatDelegate
@NonNull
@CallSuper
public Context attachBaseContext2(@NonNull Context context) {
// 重點2刀森,將上一步包裝了baseContext的ContextThemeWrapper對象進一步賦值給Activity的mBase
attachBaseContext(context);
return context;
}
最終AppCompatActivity
中的mBase
是包裝了ContextImpl
的ContextThemeWrapper
對象踱启,并且其主題為Theme_AppCompat_Empty
關(guān)于第三點,getBaseActivity
和Activity
中的主題到底是哪一個研底,我們可以根據(jù)resId和resources索引表resource.arsc(直接將apk文件拖到AndroidStudio中就可以看到該文件)找到:
2131755410
和2131755474
對應(yīng)16進制為0x7f100192
與0x7f1001d2
可以看到埠偿,getBaseActivity
和Activity
中的主題分別對應(yīng)Theme_AppCompat_Empty
與我們在AndroidManifest.xml
中設(shè)置的應(yīng)用主題Theme.ThemeTest
總結(jié)
Context是Android應(yīng)用程序與系統(tǒng)環(huán)境進行交互的橋梁,主要實現(xiàn)類是ContextImpl, 可以訪問應(yīng)用程序資源/啟動組件/訪問系統(tǒng)服務(wù)/訪問應(yīng)用程序的文件等榜晦,而Context可以分為三種:ContextImpl
/ContextWrapper
/ContextThemeWrapper
冠蒋,不同ContextImpl
是Context的主要實現(xiàn)類,ContextWrapper
是簡單的包裝類乾胶,所有的實現(xiàn)都由其內(nèi)部的mBase
成員完成抖剿,ContextThemeWrapper繼承自ContextWrapper
朽寞,它的主要繼承者是Activity
,和其他兩個Context
不同的是斩郎,他內(nèi)部對應(yīng)用資源和主題有不同的行為脑融,在應(yīng)用中使用跟主題相關(guān)的Context
時,最好使用activity
缩宜,而不要使用getBaseContext
或者applictaion
.