本博客是一個純技術交流博客,寫出來的文章是幫大家解決一些問題,或讓大家有個參考和思路,更多技術分享請關注http://blog.36dr.net,有任何問題可與我郵件dr.kalen@yahoo.com。
1. 什么是Context
在Android中Context分為ActivityContext和Application Context,Context是一個抽象類是所有上下文的基類。
2. Activity Context 和Application Context 的區別
- application context生命周期比較長,伴隨應用程序的存在而存在與activity的生命周期無關,Activity Context 則只能存活于Activity 生命周期內
- 兩者的使用范圍和用于有部分差異,具體可以查看圖示
數字1:啟動Activity在這些類中是可以的,但是需要創建一個新的task。一般情況不推薦.
數字2:在這些類中去layout inflate是合法的,但是會使用系統默認的主題樣式,如果你自定義了某些樣式可能不會被使用。
數字3:在receiver為null時允許,在4.2或以上的版本中,用于獲取黏性廣播的當前值。(可以無視)
注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因為在其內部方法中都有一個context用于使用。
3. Context可以做什么
通過Context可以訪問App全局信息的接口,例如:
- 啟動Activity、Service、發送廣播
- 訪問APP中的資源和公開的方法
- 獲取assets中的資源
- 對APK進行管線管理
由以上來看,Context可以算是對APK無所不知,可以操作資源、代碼以及其他信息。
在創建Activity、Service、Application時都會自動創建Context,因此各自維護著自己的上下午,如果要統計一個app中的
Context數量=Activity數+Service數+application
如何使用Context做這些操作呢,我們舉例說明:
- 執行APK中某類的方法
Context c = createPackageContext("chroya.demo", Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
//載入這個類
Class clazz = c.getClassLoader().loadClass("chroya.demo.Main");
//新建一個實例
Object owner = clazz.newInstance();
//獲取print方法,傳入參數并執行
Object obj = clazz.getMethod("print", String.class).invoke(owner, "Hello”);
ok,這樣,我們就調用了chroya.demo包的Main類的print方法,執行結果。其中需要對createPackageContext的參數說明
1、packageName 包名,要得到Context的包名
2、flags 標志位,有CONTEXT_INCLUDE_CODE和CONTEXT_IGNORE_SECURITY兩個選項。CONTEXT_INCLUDE_CODE的意思是包括代碼,也就是說可以執行這個包里面的代碼。
CONTEXT_IGNORE_SECURITY的意思是忽略安全警告,如果不加這個標志的話,有些功能是用不了的,會出現安全警告。
- 訪問apk中的資源
在ContextImpl中存在一個mResources變量,變量值的來源如下代碼,它就是我們在通過Context.getResource()方法返回的結果,而 resource中存在一個ArrayMap用于存儲所有的資源對象,因為一個app針對不同分辨率、不同方向等擁有多套資源,因此ArrayMap中會是多個對象,又ResourceManager使用的單例模式,因此每個Activity使用的是同一套資源但不一定是同一個資源對象。
mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),Display.DEFAULT_DISPLAY, null, compatInfo,activityToken);
4. Context造成內存泄露
通常造成Context內存泄露的原因是因為,系統gc執行時無法銷毀Context,我們舉例說明:
我們都知道應用當屏幕旋轉時會銷毀當前Activity然后重新創建一個新的Activity,當我們activity中有圖片加載時而我們又希望保持這個圖片不重新加載,我們可能采用一個靜態變量來保持這個Drawable,此時當屏幕旋轉的時候由于Drawable與View有關聯,Drawable保存了view的引用,而view保存了Activity引用,因此導致在旋轉屏幕是無法將activity銷毀,而造成內存泄露,程序崩潰。
<font color="#FF1493">防止Context導致內存泄露,我們應該注意保存Activity中的對象與Activity是同一個生命周期,對已需要非常長的周期對象可以采用Application Context。</font>
5. Unable to add window — token null is not for an application
此錯誤一般是在彈出框時出現異常如:
Dialog dialog = new Dialog(getApplicationContext());
或者
Dialog dialog = new Dialog(getApplication());
由于dialog是一個窗口,因此需要一個擁有窗口令牌的Token,而在packageInfo存在的情況下getApplication與getApplication返回的是同一個application context,application context并沒有窗口令牌,因此會出現這種異常,正確的做法:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
[1] 具體創建時代碼:
創建Application時:創建Application的時機在創建handleBindApplication()方法
//創建Application時同時創建的ContextIml實例
2 private final void handleBindApplication(AppBindData data){
3 …
4 ///創建Application對象
5 Application app = data.info.makeApplication(data.restrictedBackupMode, null);
6 …
7 }
8 public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
9 …
10 try {
11 java.lang.ClassLoader cl = getClassLoader();
12 ContextImpl appContext = new ContextImpl(); //創建一個ContextImpl對象實例
13 appContext.init(this, null, mActivityThread); //初始化該ContextIml實例的相關屬性
14 ///新建一個Application對象
15 app = mActivityThread.mInstrumentation.newApplication(
16 cl, appClass, appContext);
17 appContext.setOuterContext(app); //將該Application實例傳遞給該ContextImpl實例
18 }
19 …
20 }
創建Activity時通過startActivity()或startActivityForResult()請求啟動一個Activity時,如果系統檢測需要新建一個Activity對象時回調handleLaunchActivity()方法。
//創建一個Activity實例時同時創建ContextIml實例
private final void handleLaunchActivity(ActivityRecord r, Intent customIntent) {
…
Activity a = performLaunchActivity(r, customIntent); //啟動一個Activity
}
private final Activity performLaunchActivity(ActivityRecord r, Intent customIntent) {
…
Activity activity = null;
try {
//創建一個Activity對象實例
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
}
if (activity != null) {
ContextImpl appContext = new ContextImpl(); //創建一個Activity實例
appContext.init(r.packageInfo, r.token, this); //初始化該ContextIml實例的相關屬性
appContext.setOuterContext(activity); //將該Activity信息傳遞給該ContextImpl實例
…
}
…
}
創建Service時通過startService或者bindService時,如果系統檢測到需要新創建一個Service實例,就會回調handleCreateService()方法
//創建一個Service實例時同時創建ContextIml實例
private final void handleCreateService(CreateServiceData data){
…
//創建一個Service實例
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
}
…
ContextImpl context = new ContextImpl(); //創建一個ContextImpl對象實例
context.init(packageInfo, null, this); //初始化該ContextIml實例的相關屬性
//獲得我們之前創建的Application對象信息
Application app = packageInfo.makeApplication(false, mInstrumentation);
//將該Service信息傳遞給該ContextImpl實例
context.setOuterContext(service);
…
}