探究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类吧
相关文章
相关标签/搜索