探究Android中的Application類

一個Android App在運行時會自動建立全局惟一的Application對象,是用來維護應用全局狀態的基類,它的生命週期等於應用的生命週期。java

1. 主要方法

(1) 構造函數

public class Application extends ContextWrapper implements ComponentCallbacks2 {

    public LoadedApk mLoadedApk;
    public Application() {
        super(null);
    }
}
複製代碼

Application擁有一個LoadedApk類型的成員變量,這個對象其實就是APK文件在內存中的表示。APK文件的相關信息,諸如其中的代碼和資源,甚至Activity、Service等四大組件的信息均可以經過此對象獲取。繼續看構造函數,它直接調用了父類ContextWrapper的構造方法:android

public class ContextWrapper extends Context {

    Context mBase;
    public ContextWrapper(Context base) {
        mBase = base;
    }
}
複製代碼

ContextWrapper是Context的包裝類,mBase就是它的成員變量,除了經過構造函數初始化,也能夠經過attachBaseContext方法初始化:git

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

上述方法被Applicationattach方法用來實現實現瞭如下兩個功能:①將新建立的ContextImpl對象賦給Application的父類成員變量mBase;②將新建立的LoadedApk對象賦給Application的成員變量mLoadedApk。緩存

public class Application extends ContextWrapper implements ComponentCallbacks2 {
    final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }
}
複製代碼

這個方法由於是被系統調用的,因此是一個隱藏的final方法,咱們沒法重寫。具體來講,它被系統的Instrumentation類所調用:app

public class Instrumentation {
  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;
  }

  static public Application newApplication(Class<?> clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    Application app = (Application)clazz.newInstance();
    app.attach(context);
    return app;
  }
}
複製代碼

newApplication方法被LoadedApk類所調用:ide

public final LoadedApk{

  public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
    //保證了一個LoadedApk對象只建立一個對應的Application對象
    if (mApplication != null) {
        return mApplication;
    }

    ...

    Application app = null;

    ...

    try {
        //獲取類加載器
        java.lang.ClassLoader cl = getClassLoader();
        if (!mPackageName.equals("android")) {
            initializeJavaContextClassLoader();
        }
        //建立ContextImpl對象
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        //建立Application對象
        app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
        appContext.setOuterContext(app);
    } catch (Exception e) {
        ...
    }
    mActivityThread.mAllApplications.add(app);
    //將剛建立的Application對象賦值給mApplication變量
    mApplication = app;

    ...

    return app;
  }
}
複製代碼

可見,Application的父類方法attachBaseContext(Context context)中傳入的實參實際上是經過ContextImpl.createAppContext(mActivityThread, this)這句話所建立的ContextImpl對象。並且,加載和構造出Application對象的類加載器實際上是由LoadedApk.getClassLoader()方法建立獲得的,這裏就不展開講述了。緊接着,ActivityThread類調用了LoadedApk的makeApplication方法來初始化Application信息:函數

public final class ActivityThread extends ClientTransactionHandler {

    private void attach(boolean system, long startSeq) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            ···
        } else { //system進程才執行該流程

            android.ddm.DdmHandleAppName.setAppName("system_process",
                    UserHandle.myUserId());
            try {
                //建立Instrumentation
                mInstrumentation = new Instrumentation();
                mInstrumentation.basicInit(this);
                //建立ContextImpl
                ContextImpl context = ContextImpl.createAppContext(
                        this, getSystemContext().mPackageInfo);
                //建立Application
                mInitialApplication = context.mPackageInfo.makeApplication(true, null);
                //回調Application的onCreate方法
                mInitialApplication.onCreate();
            } catch (Exception e) {
                throw new RuntimeException(
                        "Unable to instantiate Application():" + e.toString(), e);
            }
        }
    }

    private void handleBindApplication(AppBindData data) {
        ···
        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);

        //建立Instrumentation對象
        if (ii != null) {
            ···
            final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
                appContext.getClassLoader(), false, true, false);
            ···
            final ContextImpl instrContext = ContextImpl.createAppContext(this, pi,appContext.getOpPackageName());
            ···
            final ClassLoader cl = instrContext.getClassLoader();
            mInstrumentation = (Instrumentation)
                cl.loadClass(data.instrumentationName.getClassName()).newInstance();
            ···
        } else {
            mInstrumentation = new Instrumentation();
            mInstrumentation.basicInit(this);
        }

        Application app;
        // 此處data.info是指LoadedApk, 經過反射建立目標應用Application對象
        app = data.info.makeApplication(data.restrictedBackupMode, null);
        ···
        mInitialApplication = app;
        ···
        mInstrumentation.callApplicationOnCreate(app);
        ···
    }
}

public class Instrumentation {
    public void callApplicationOnCreate(Application app) {
        //回調Application的onCreate
        app.onCreate();
    }
}
複製代碼

由於system_server進程和app進程都運行着一個或多個app,每一個app都會有且僅有一個對應的Application對象(該對象和LoadedApk對象一一對應)。 system_server進程是由ActivityThread.attach()方法建立出Application對象的; 而普通app進程的則是由ActivityThread.handleBindApplication()方法建立獲得。性能

(2) onCreate()

從上述代碼調用流程能夠看到,Application初始化完成後系統就會回調其onCreate方法,該方法默認爲空實現,咱們能夠擴展Application類,能夠經過重寫該方法再進行一些應用程序級別的資源初始化或臨時全局共享數據的設置工做。學習

(3) registerComponentCallbacks()和unregisterComponentCallbacks()

註冊和註銷ComponentCallbacks2監聽器,下文將具體進行介紹。測試

(4) registerActivityLifecycleCallbacks()和unregisterActivityLifecycleCallbacks()

註冊和註銷對全部Activity生命週期的監聽器ActivityLifecycleCallbacks,每當應用程序內Activity的生命週期發生變化時,監聽器中相對應的接口方法就會被調用執行。

(5) onTerminate()

在應用程序結束時調用,但該方法只用於仿真機測試,真機上並不會調用。

2. ComponentCallbacks2監聽器

Application已經實現了該監聽器接口,如前文所述,能夠經過registerComponentCallbacks()方法註冊該監聽器,也可使用unregisterComponentCallbacks()註銷。這個監聽器提供瞭如下三個接口方法:

(1) onTrimMemory(@TrimMemoryLevel int level)

  • 做用:指導應用程序根據當前系統內存使用狀況釋放自身資源(如圖片或文件等緩存、動態生成和添加的View等),以免被系統清除,提升用戶體驗。

這是由於系統在內存不足時會從LRU Cache中按照從低到高的順序殺死進程,可是那些高內存佔用的應用也會被優先殺死,以此來讓系統更快地獲取更多可用內存。因此若是可以及時下降應用的內存佔用,就能夠下降它在後臺被殺掉的機率,用戶返回應用時就能快速恢復。

  • 調用時刻:當系統檢測到當前進程適合進行無用內存的釋放操做時。例如系統卻已經沒有足夠的內存來維持目前全部的後臺進程,而咱們程序正好處於後臺狀態。

  • TrimMemoryLevel:代表了系統在回調onTrimMemory方法時的內存狀況等級:

    TRIM_MEMORY_RUNNING_MODERATE:級別爲5,應用程序處於前臺運行,但系統已經進入了低內存的狀態。

    TRIM_MEMORY_RUNNING_LOW:級別爲10,應用程序處於前臺運行,雖然不會被殺死,可是因爲系統當前可用內存很低,系統開始準備殺死其餘後臺程序,咱們應該釋放沒必要要的資源來提供系統性能,不然會影響用戶體驗。

    TRIM_MEMORY_RUNNING_CRITICAL:級別爲15,應用程序處於前臺運行,大部分後臺程序都已被殺死,此時咱們應該儘量地去釋聽任何沒必要要的資源。

    TRIM_MEMORY_UI_HIDDEN:級別爲20,應用程序的全部UI界面已經不可見,很是適合釋放UI相關的資源。

    TRIM_MEMORY_BACKGROUND:級別爲40,應用程序處於後臺,且在LRU緩存列表頭部,不會被優先殺死,可是系統將開始根據LRU緩存來依次清理進程。此時應該釋放掉一些比較容易恢復的資源提升系統的可用內存,讓程序可以繼續保留在緩存中。

    TRIM_MEMORY_MODERATE:級別爲60,應用程序處於後臺,且在LRU緩存列表的中部,若是系統可用內存進一步減小,程序就會有被殺掉的風險。

    TRIM_MEMORY_COMPLETE:級別爲80,應用程序處於後臺,且在LRU緩存列表的尾部,隨時會被系統殺死,此時應該儘量地把一切能夠釋放的資源釋放掉。

  • 除了Application,能夠實現onTrimMemory回調的組件還有Activity、Fragement、Service、ContentProvider。

(2) onLowMemory()

該接口在Android4.0之後被上述方法替代,因此做用和上述方法相似。 若應用想兼容Android4.0之前的系統就使用OnLowMemory,不然直接使用OnTrimMemory便可。須要注意的是,onLowMemory至關於level級別爲TRIM_MEMORY_COMPLETEOnTrimMemory

(3) onConfigurationChanged(@NonNull Configuration newConfig)

  • 做用:監聽應用程序的一些配置信息的改變事件(好比屏幕旋轉)
  • 調用時刻:當配置信息發生改變時。所謂的配置信息也就是咱們在AndroidManifest.xml文件 中爲Activity標籤配置的android:configChanges屬性值,例如android:configChanges="keyboardHidden|orientation|screenSize可使該Activity在屏幕旋轉時不重啓,而是執行onConfigurationChanged方法。

3. 自定義Application類

  • 新建自定義Application子類,繼承Application類,選擇重寫相應的方法,如onCreate()方法;
  • AndroidManifest.xml文件中配置<application>標籤的android:name屬性,例如android:name=".MyApplication",MyApplicaiton就是自定義的Application類名。

4. 比較getApplication()getApplicationContext()方法

  • getApplication()方法只存在於ActivityService對象中,該方法可主動獲取當前所在mApplication,這是由LoadedApk.makeApplication()進行初始化的;

  • getApplicationContext()是Context類的方法,因此Context的子類均可以調用該方法。先來看一下該方法的執行邏輯:

    public abstract class Context {
      public abstract Context getApplicationContext();
    }
    
    class ContextImpl extends Context {
        public Context getApplicationContext() {
            return (mPackageInfo != null) ?
                  mPackageInfo.getApplication() : mMainThread.getApplication();
        }
    }
    
    //上述mPackageInfo的數據類型爲LoadedApk
    public final class LoadedApk {
        Application getApplication() {
            return mApplication;
        }
    }
    
    //上述mMainThread爲ActivityThread
    public final class ActivityThread {
        public Application getApplication() {
            return mInitialApplication;
        }
    }
    複製代碼
  • 從上述代碼能夠看出若是LoadedApk非空,getApplicationContext()方法返回的就是LoadedApk的成員變量mApplication,因此對於Activity或Service組件來講, getApplication()getApplicationContext()沒有差異,由於它們的返回值徹底相同。

  • BroadcastReceiver和ContentProvider沒法使用getApplication(),可是可使用getBaseContext().getApplicationContext()獲取所在的Application,可是ContentProvider使用該方法有可能會出現空指針的問題, 狀況以下: 當同一個進程有多個apk的狀況下, 若是第二個apk是由provider方式拉起,那麼 因爲provider建立過程並不會初始化相應的Application,此時執行getContext().getApplicationContext()就會返回空,因此對於這種狀況須要作好判空處理。

  • 在Context對象的attachBaseContext()中調用getApplicationContext()方法也會返回空,這是由於從前文對Application構造函數的分析可知,LoadedApk.mApplication是在attachBaseContext()方法執行以後才被賦值的。

參考文章:

  1. 理解Application建立過程
  2. 一塊兒來學習那個熟悉又陌生的Application類吧
相關文章
相關標籤/搜索