Android中Context用法詳解

2014-08-09    分類:Android開發、程式設計開發、首頁精華2人評論發表於2014-08-09

本文我們一起來探討一下關於Android中Context的作用以及Context的詳細用法,這對我們學習Android的資源訪問有很大的幫助,文章中也貼出了一些關於Android Context使用的示例程式碼,非常不錯,以下是原文:

Context基本概念

Context是什麼?

1) Context是一個抽象類,其通用實現在ContextImpl類中。

2) Context:是一個訪問application環境全域性資訊的介面,通過它可以訪問application的資源和相關的類,其主要功能如下:

啟動Activity
啟動和停止Service
傳送廣播訊息(Intent)
註冊廣播訊息(Intent)接收者
可以訪問APK中各種資源(如Resources和AssetManager等)
可以訪問Package的相關資訊
APK的各種許可權管理

從以上分析可以看出,Context就是一個對APK包無所不知的大管家,大家需要什麼,直接問它就可以了。

Context與View的關係

View與Context(或Activity)的關係類似於明星與經紀人的關係,所以建立View時,必須明確指定其Context(即經紀人或大管家),否則View就成不了明星。

Context家族關係

Context關鍵函式

public abstract class Context {

// 獲取應用程式包的AssetManager例項
public abstract AssetManager getAssets();

// 獲取應用程式包的Resources例項
public abstract Resources getResources();

// 獲取PackageManager例項,以檢視全域性package資訊
public abstract PackageManager getPackageManager();

// 獲取應用程式包的ContentResolver例項
public abstract ContentResolver getContentResolver();

// 它返回當前程式的主執行緒的Looper,此執行緒分發呼叫給應用元件(activities, services等)
public abstract Looper getMainLooper();

// 返回當前程式的單例項全域性Application物件的Context
public abstract Context getApplicationContext();

// 從string表中獲取本地化的、格式化的字元序列
public final CharSequence getText(int resId) {
return getResources().getText(resId);
}

// 從string表中獲取本地化的字串
public final String getString(int resId) {
return getResources().getString(resId);
}

public final String getString(int resId, Object... formatArgs) {
return getResources().getString(resId, formatArgs);
}

// 返回一個可用於獲取包中類資訊的class loader
public abstract ClassLoader getClassLoader();

// 返回應用程式包名
public abstract String getPackageName();

// 返回應用程式資訊
public abstract ApplicationInfo getApplicationInfo();

// 根據檔名獲取SharedPreferences
public abstract SharedPreferences getSharedPreferences(String name,
int mode);

// 其根目錄為: Environment.getExternalStorageDirectory()
/*
* @param type The type of files directory to return.  May be null for
* the root of the files directory or one of
* the following Environment constants for a subdirectory:
* {@link android.os.Environment#DIRECTORY_MUSIC},
* {@link android.os.Environment#DIRECTORY_PODCASTS},
* {@link android.os.Environment#DIRECTORY_RINGTONES},
* {@link android.os.Environment#DIRECTORY_ALARMS},
* {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
* {@link android.os.Environment#DIRECTORY_PICTURES}, or
* {@link android.os.Environment#DIRECTORY_MOVIES}.
*/
public abstract File getExternalFilesDir(String type);

// 返回應用程式obb檔案路徑
public abstract File getObbDir();

// 啟動一個新的activity
public abstract void startActivity(Intent intent);

// 啟動一個新的activity
public void startActivityAsUser(Intent intent, UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}

// 啟動一個新的activity
// intent: 將被啟動的activity的描述資訊
// options: 描述activity將如何被啟動
public abstract void startActivity(Intent intent, Bundle options);

// 啟動多個新的activity
public abstract void startActivities(Intent[] intents);

// 啟動多個新的activity
public abstract void startActivities(Intent[] intents, Bundle options);

// 廣播一個intent給所有感興趣的接收者,非同步機制
public abstract void sendBroadcast(Intent intent);

// 廣播一個intent給所有感興趣的接收者,非同步機制
public abstract void sendBroadcast(Intent intent,String receiverPermission);

public abstract void sendOrderedBroadcast(Intent intent,String receiverPermission);

public abstract void sendOrderedBroadcast(Intent intent,
String receiverPermission, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
Bundle initialExtras);

public abstract void sendBroadcastAsUser(Intent intent, UserHandle user);

public abstract void sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission);

// 註冊一個BroadcastReceiver,且它將在主activity執行緒中執行
public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter);

public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission, Handler scheduler);

public abstract void unregisterReceiver(BroadcastReceiver receiver);

// 請求啟動一個application service
public abstract ComponentName startService(Intent service);

// 請求停止一個application service
public abstract boolean stopService(Intent service);

// 連線一個應用服務,它定義了application和service間的依賴關係
public abstract boolean bindService(Intent service, ServiceConnection conn,
int flags);

// 斷開一個應用服務,當服務重新開始時,將不再接收到呼叫,
// 且服務允許隨時停止
public abstract void unbindService(ServiceConnection conn);

// 返回系統級service控制程式碼
/*
* @see #WINDOW_SERVICE
* @see android.view.WindowManager
* @see #LAYOUT_INFLATER_SERVICE
* @see android.view.LayoutInflater
* @see #ACTIVITY_SERVICE
* @see android.app.ActivityManager
* @see #POWER_SERVICE
* @see android.os.PowerManager
* @see #ALARM_SERVICE
* @see android.app.AlarmManager
* @see #NOTIFICATION_SERVICE
* @see android.app.NotificationManager
* @see #KEYGUARD_SERVICE
* @see android.app.KeyguardManager
* @see #LOCATION_SERVICE
* @see android.location.LocationManager
* @see #SEARCH_SERVICE
* @see android.app.SearchManager
* @see #SENSOR_SERVICE
* @see android.hardware.SensorManager
* @see #STORAGE_SERVICE
* @see android.os.storage.StorageManager
* @see #VIBRATOR_SERVICE
* @see android.os.Vibrator
* @see #CONNECTIVITY_SERVICE
* @see android.net.ConnectivityManager
* @see #WIFI_SERVICE
* @see android.net.wifi.WifiManager
* @see #AUDIO_SERVICE
* @see android.media.AudioManager
* @see #MEDIA_ROUTER_SERVICE
* @see android.media.MediaRouter
* @see #TELEPHONY_SERVICE
* @see android.telephony.TelephonyManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
* @see #UI_MODE_SERVICE
* @see android.app.UiModeManager
* @see #DOWNLOAD_SERVICE
* @see android.app.DownloadManager
*/
public abstract Object getSystemService(String name);

public abstract int checkPermission(String permission, int pid, int uid);

// 返回一個新的與application name對應的Context物件
public abstract Context createPackageContext(String packageName,
int flags) throws PackageManager.NameNotFoundException;

// 返回基於當前Context物件的新物件,其資源與display相匹配
public abstract Context createDisplayContext(Display display);
}

ContextImpl關鍵成員和函式

/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
class ContextImpl extends Context {
private final static String TAG = "ContextImpl";
private final static boolean DEBUG = false;

private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
new HashMap<String, SharedPreferencesImpl>();

/*package*/ LoadedApk mPackageInfo; // 關鍵資料成員
private String mBasePackageName;
private Resources mResources;
/*package*/ ActivityThread mMainThread; // 主執行緒

@Override
public AssetManager getAssets() {
return getResources().getAssets();
}

@Override
public Looper getMainLooper() {
return mMainThread.getLooper();
}

@Override
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}

@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intent, -1, options);
}
}

ContextWrapper

它只是對Context類的一種封裝,它的建構函式包含了一個真正的Context引用,即ContextImpl物件。

/**
* Proxying implementation of Context that simply delegates all of its calls to
* another Context.  Can be subclassed to modify behavior without changing
* the original Context.
*/
public class ContextWrapper extends Context {
Context mBase; //該屬性指向一個ContextIml例項

public ContextWrapper(Context base) {
mBase = base;
}

/**
* Set the base context for this ContextWrapper.  All calls will then be
* delegated to the base context.  Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
* 建立Application、Service、Activity,會呼叫該方法給mBase屬性賦值
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}

@Override
public Looper getMainLooper() {
return mBase.getMainLooper();
}

@Override
public Object getSystemService(String name) {
return mBase.getSystemService(name);
}

@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
}

ContextThemeWrapper

該類內部包含了主題(Theme)相關的介面,即android:theme屬性指定的。只有Activity需要主題,Service不需要主題,所以Service直接繼承於ContextWrapper類。

/**
* A ContextWrapper that allows you to modify the theme from what is in the
* wrapped context.
*/
public class ContextThemeWrapper extends ContextWrapper {
private Context mBase;
private int mThemeResource;
private Resources.Theme mTheme;
private LayoutInflater mInflater;
private Configuration mOverrideConfiguration;
private Resources mResources;

public ContextThemeWrapper() {
super(null);
}

public ContextThemeWrapper(Context base, int themeres) {
super(base);
mBase = base;
mThemeResource = themeres;
}

@Override protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
mBase = newBase;
}

@Override public void setTheme(int resid) {
mThemeResource = resid;
initializeTheme();
}

@Override public Resources.Theme getTheme() {
if (mTheme != null) {
return mTheme;
}

mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getApplicationInfo().targetSdkVersion);
initializeTheme();

return mTheme;
}
}

何時建立Context

應用程式在以下幾種情況下建立Context例項:

1) 建立Application 物件時, 而且整個App共一個Application物件

2) 建立Service物件時

3) 建立Activity物件時

因此應用程式App共有的Context數目公式為:

總Context例項個數 = Service個數 + Activity個數 + 1(Application對應的Context例項)

ActivityThread訊息處理函式與本節相關的內容如下:

public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: { // 建立Activity物件
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;

r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;

case BIND_APPLICATION: // 建立Application物件
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;

case CREATE_SERVICE: // 建立Service物件
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceCreate");
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;

case BIND_SERVICE:  // Bind Service物件
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
handleBindService((BindServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
}

建立Application物件時建立Context例項

每個應用程式在第一次啟動時,都會首先建立一個Application物件。從startActivity流程可知,建立Application的時機在handleBindApplication()方法中,該函式位於 ActivityThread.java類中 ,相關程式碼如下:

// ActivityThread.java
private void handleBindApplication(AppBindData data) {
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
...
} finally {
StrictMode.setThreadPolicy(savedPolicy);
}
}

// LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
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();
ContextImpl appContext = new ContextImpl(); // 建立ContextImpl例項
appContext.init(this, null, mActivityThread);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app); // 將Application例項傳遞給Context例項
} catch (Exception e) {
...
}
mActivityThread.mAllApplications.add(app);
mApplication = app;

return app;
}
private Context createBaseContextForActivity(ActivityClientRecord r,
final Activity activity) {
ContextImpl appContext = new ContextImpl();  // 建立ContextImpl例項
appContext.init(r.packageInfo, r.token, this);
appContext.setOuterContext(activity);

// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
// its content on a secondary display if there is one.
Context baseContext = appContext;
String pkgName = SystemProperties.get("debug.second-display.pkg");
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
for (int displayId : dm.getDisplayIds()) {
if (displayId != Display.DEFAULT_DISPLAY) {
Display display = dm.getRealDisplay(displayId);
baseContext = appContext.createDisplayContext(display);
break;
}
}
}
return baseContext;
}

建立Service物件時建立Context例項

通過startService或者bindService時,如果系統檢測到需要新建立一個Service例項,就會回撥handleCreateService()方法,完成相關資料操作。handleCreateService()函式位於 ActivityThread.java類,如下:

private void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();

LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to instantiate service " + data.info.name
+ ": " + e.toString(), e);
}
}

try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);

ContextImpl context = new ContextImpl(); // 建立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());
service.onCreate();
mServices.put(data.token, service);
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, 0, 0, 0);
} catch (RemoteException e) {
// nothing to do.
}
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to create service " + data.info.name
+ ": " + e.toString(), e);
}
}
}

小結

通過對ContextImp的分析可知,其方法的大多數操作都是直接呼叫其屬性mPackageInfo(該屬性型別為PackageInfo)的相關方法而來。這說明ContextImp是一種輕量級類,而PackageInfo才是真正重量級的類。而一個App裡的所有ContextImpl例項,都對應同一個packageInfo物件。

來自:部落格園

相關文章