你對Context了解多少呢

如需轉載請評論或簡信,并注明出處,未經允許不得轉載

目錄

前言

Android應用進程的創建 — Activity的啟動流程中我們發現,ApplicationActivity都是由系統創建的,它們并不能像其他java類一樣,由應用層通過new操作創建出對象,且它們都繼承自Context,那么大家有沒有想過,這個Context到底有什么作用呢?它在Android應用中扮演了一個怎么樣的角色呢?

Context繼承關系

這里整理了一張Context的繼承關系類圖,從這個圖中可以看出,Context是一個接口,ContextImpContextWrapper都是其實現類,我們常用的ActivityServiceApplication都直接或間接繼承自ContextWrapper

通過這張圖,我們可以整理出幾個問題:

  1. 一個應用程序有幾個Context

  2. 為什么ActivityServiceApplication都繼承自ContextContext的作用是什么呢?

  3. 為什么Activity需要繼承自ContextThemeWrapper,而ServiceApplication直接繼承自ContextWrapper呢?

  4. 為什么ContextWrapper中存在一個ContextImp類型的變量mBase,且同時它又實現了Context呢?

問題分析

問題一

一個應用程序有幾個Context?

ApplicationActivityService都繼承自Context,而應用有幾個進程,就會存在幾個Application對象

Context個數 = Activity個數 + Service個數 + 進程個數

問題二

為什么Activity、Service、Application都繼承自Context,Context的作用是什么呢?

Context是一個接口,所以要想知道Context的作用,其實就是看它有哪些接口,這些接口的功能是什么

/**
* Interface to global information about an application environment.  This is
* an abstract class whose implementation is provided by
* the Android system.  It
* allows access to application-specific resources and classes, as well as
* up-calls for application-level operations such as launching activities,
* broadcasting and receiving intents, etc.
*/
public abstract class Context {
    
    // 四大組件相關
    public abstract void startActivity(@RequiresPermission Intent intent);
    public abstract void sendBroadcast(@RequiresPermission Intent intent);
    public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
                                            IntentFilter filter);
    public abstract void unregisterReceiver(BroadcastReceiver receiver);
    public abstract ComponentName startService(Intent service);
    public abstract boolean stopService(Intent service);
    public abstract boolean bindService(@RequiresPermission Intent service,
            @NonNull ServiceConnection conn, @BindServiceFlags int flags);
    public abstract void unbindService(@NonNull ServiceConnection conn);
    public abstract ContentResolver getContentResolver();
    
    // 獲取系統/應用資源
    public abstract AssetManager getAssets();
    public abstract Resources getResources();
    public abstract PackageManager getPackageManager();
    public abstract Context getApplicationContext();
    public abstract ClassLoader getClassLoader();
    public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { ... }
    
    public final String getString(@StringRes int resId) { ... }
    public final int getColor(@ColorRes int id) { ... }
    public final Drawable getDrawable(@DrawableRes int id) { ... }
    public abstract Resources.Theme getTheme();
    public abstract void setTheme(@StyleRes int resid);
    public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { ... }
    
    // 獲取應用相關信息
    public abstract ApplicationInfo getApplicationInfo();
    public abstract String getPackageName();
    public abstract Looper getMainLooper();
    public abstract int checkPermission(@NonNull String permission, int pid, int uid);
    
    // 文件相關
    public abstract File getSharedPreferencesPath(String name);
    public abstract File getDataDir();
    public abstract boolean deleteFile(String name);
    public abstract File getExternalFilesDir(@Nullable String type);
    public abstract File getCacheDir();
    ...
    public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
    public abstract boolean deleteSharedPreferences(String name);
    
    // 數據庫相關
    public abstract SQLiteDatabase openOrCreateDatabase(...);
    public abstract boolean deleteDatabase(String name);
    public abstract File getDatabasePath(String name);
    ...
    
    // 其它
    public void registerComponentCallbacks(ComponentCallbacks callback) { ... }
    public void unregisterComponentCallbacks(ComponentCallbacks callback) { ... }
    ...
}

public interface ComponentCallbacks {
    void onConfigurationChanged(Configuration newConfig);
    void onLowMemory();
}

結合代碼可以看出,Context就像是應用的大管家,正是因為有了Context,各種應用組件才有意義,他們才能訪問系統服務,系統資源

Context的作用總結為如下幾個方面:

  1. 四大組件的交互,包括啟動 ActivityBroadcastService,獲取 ContentResolver
  2. 獲取系統/應用資源,包括 AssetManagerPackageManagerResourcesSystem Service 以及 colorstringdrawable
  3. 文件,包括獲取緩存文件夾、刪除文件、SharedPreference 相關等
  4. 數據庫(SQLite)相關,包括打開數據庫、刪除數據庫、獲取數據庫路徑等
  5. 其它輔助功能,比如設置 ComponentCallbacks,即監聽配置信息改變、內存不足等事件的發生

問題三

為什么Activity需要繼承自ContextThemeWrapper,而Service和Application直接繼承自ContextWrapper呢?

下面來看一下ContextThemeWrapper的源碼

public class ContextThemeWrapper extends ContextWrapper {
    private int mThemeResource;
    private Resources.Theme mTheme;
    private LayoutInflater mInflater;
    private Configuration mOverrideConfiguration;
    private Resources mResources;

    public ContextThemeWrapper() {
        super(null);
    }

    public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
        super(base);
        mThemeResource = themeResId;
    }

    public ContextThemeWrapper(Context base, Resources.Theme theme) {
        super(base);
        mTheme = theme;
    }

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

    public void applyOverrideConfiguration(Configuration overrideConfiguration) {...}

    public Configuration getOverrideConfiguration() {...}

    @Override
    public AssetManager getAssets() {...}

    @Override
    public Resources getResources() {...}

    private Resources getResourcesInternal() {...}

    @Override
    public void setTheme(int resid) {...}

    @Override
    public int getThemeResId() {...}

    @Override
    public Resources.Theme getTheme() {...}

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

    protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) {

    private void initializeTheme() {...}
}

ContextThemeWrapper類,從它的命名就可以看出,其內部包含了與Theme相關的接口,當然,只有Activity才需要主題,ServiceApplication是不需要主題的,因為Service是沒有界面的后臺場景,所以ServiceApplication直接繼承于ContextWrapper

問題四

為什么ContextWrapper中存在一個ContextImp類型的變量mBase,且同時它又實現了ContextWrapper呢?

下面來看ContextWrapper的代碼

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
    
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

    public Context getBaseContext() {
        return mBase;
    }

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

    @Override
    public Resources getResources() {
        return mBase.getResources();
    }

    @Override
    public PackageManager getPackageManager() {
        return mBase.getPackageManager();
    }

    @Override
    public ContentResolver getContentResolver() {
        return mBase.getContentResolver();
    }
  
    ....
}

很顯然,ContextWrapper只是一個Context靜態代理類,所有的操作都是通過內部成員 mBase 完成的,而mBase就是ContextImp對象。為什么要這么設計呢?如果Application直接繼承ContextImp會不會有什么問題呢?

一般情況下,使用代理而不直接使用某個對象,目的可能有兩個:

  1. 定制自己的行為
  2. 不影響原對象

其中 ServcieApplication 的父類 ContextWrapper 完全沒有自定義的行為,而 Activity 的父類 ContextThemeWrapper 則自定義了 Resource 以及 Theme 的相關行為,因此:

  1. 對于 ServiceApplication 而言,不直接繼承ContextImp,是擔心用戶修改了ContextImp而導致錯誤的發生
  2. 對于 Activity 而言,除了擔心用戶的修改之外,ContextImpActivity 本身對于 Reource 以及 Theme 的相關行為是不同的

其他使用Context注意點

  1. 在單例模式中使用Context要注意內存泄漏問題

我們知道,單例模式的生命周期是和應用的生命周期保持一致的,所以在單例模式中使用Context,不能使用Activity Context,需要使用Application Context

  1. 創建dialog需要使用Activtiy Context

果我們使用Application的Context,或者說Token可以不是Activity的Token,那么用戶可能已經跳轉到別的應用的Activity界面了,但我們卻可以在別人的界面上彈出我們的Dialog,想想就覺得很危險

具體可以參考:http://www.lxweimin.com/p/628ac6b68c15

  1. Activity的this和getBaseContext()有什么區別?

Activity就是繼承Context的,所以this是返回Activity自己

getBaseContext()返回的是ContextWrapper里面的mBase

  1. getApplication()和getApplicationContext()有什么區別?

都是返回Applicatoin對象,但是他們的作用域不一樣

getApplicatoin()ActivityService里面特有的,其他地方不能用

BroadcastReceiveronReceive()中的第一個參數,拿到的Context對象是不能調用getApplication()的只能調getApplicationContext()

  1. Application構造方法中調用父類方法會發生閃退(如getResource()等)

Application的創建過程主要是下面三步

//1.創建ContextImpl
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
//2.創建Application
Application app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
//3.調用Application#onCreate()
app.onCreate();

Instrumentation#newApplication()

public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    app.attach(context);
    return app;
}

Application#attach()

final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }

Application#attachBaseContext()

protected void attachBaseContext(Context base) {
    if (mBase != null) {
        throw new IllegalStateException("Base context already set");
    }
    mBase = base;
}

從上面的代碼中可以看出,在Application對象剛被創建的時候,其內部的mBase變量是空的,直到執行attachBaseContext()后,mBase才會有值,之后才會調用Application#onCreate()。所以在Application中使用Context接口中的相關方法,可以在onCreate()里面調用,而不能在構造方法中調用

總結

Context是一個抽象類,我們開發過程中幾乎每天都要和它打交道,但是我相信很多人都說不出他是個什么東西,很多只是知道它叫做上下文。其實當我們覺得一個東西很抽象很難理解的時候,無外乎就是看一下它的創建過程以及它具備哪些方法

這里做一個比喻,對于我們應用層開發來說,Activity就像是一個”皇帝“,Activity可以做很多很多的事情,但是Context就像是他手中的權利,如果沒有ContextActivity其實只是一個”普通人“(普通java類)而已

我們很多人往往把Activity理解成它繼承了Context,是的沒錯,它確實繼承自Context,但我認為,把Activity理解成它代理了Context,會更貼合實際意義一些

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯系作者。