android-discuss icon indicating copy to clipboard operation
android-discuss copied to clipboard

[问答]谈谈你对 Application 类的理解

Open tangqi92 opened this issue 10 years ago • 18 comments
trafficstars

如题,不知如何精确且全面的回答。

tangqi92 avatar May 04 '15 15:05 tangqi92

  1. 继承自 ContextWrapper,可用于保存应用全局变量,或者通过全局 Context 注册广播等等(生命周期不受 Activity 等影响);
  2. 实现了 ComponentCallback2 接口,可以在 onConfigurationChanged(屏幕方向改变等)、onLowMemory/onTrimMemory(内存不足)时调用组件的相关回调;
  3. 提供了注册 ActivityLifecycleCallbacksComponentCallbacks 的 public 方法,可以用于监控应用内组件的状态(比如实现类似 Umeng 的统计页面停留时长和访问路径等);

R1NC avatar May 05 '15 02:05 R1NC

接楼上的说下, 不应该在Application中保存全局变量, 因为当应用运行在后台时, Application可能会被销毁重建, 之前的 Application 保存的全局的值就不存在了

liuchenx avatar May 05 '15 02:05 liuchenx

是的,确实不建议直接在自定义 Application 内保存全局变量(虽然很多人这么做),源码中的表述是“维持全局应用状态”:

Base class for those who need to maintain global application state.

我上面表述不太准确。

R1NC avatar May 05 '15 02:05 R1NC

“不应该在Application中保存全局变量”的说法我觉得不是绝对的 我记得反编译QQ的源码里面Application保存了一大推的东西 关键问题在于要做好判断

yankai-victor avatar May 05 '15 02:05 yankai-victor

一个程序开了多个进程时,每一个进程都会跑一遍application,如果针对不同进程在application中有不同的初始化操作,可以通过获取当前进程的名称来分条件处理。

zmywly8866 avatar May 05 '15 07:05 zmywly8866

http://www.developerphil.com/dont-store-data-in-the-application-object/ 这篇文章介绍的很清楚~~~

cczscq avatar Jun 08 '15 14:06 cczscq

#谈谈你对Application类的理解

其实说对什么的理解,就是考察你对这个东西会不会用,重点是有没有什么坑!


首先,Application在一个Dalvik虚拟机里面只会存在一个实例,所以你不要傻傻的去弄什么单例模式,来静态获取Application了,你把Application构造函数设置成privete都不可能实现(我年轻的时候就这么傻傻的试过,想着如果可以通过Singleton.getInstance()就能获取到Application对象,多爽呀~)。

那么为什么强调说是一个Dalvik虚拟机,而不是说一个App呢?

因为一个App有可能有多个Dalvik虚拟机,也就是传说中的多进程模式。在这种模式下,每一个Dalvik都会存在一个Application实例,他们之间没有关系,在A进程Application里面保存的数据不能在B进程的Application获取,因为他们根本不是一个对象,而且被隔离在了两个进程里面,所以这里强调是一个Dalvik虚拟机,而不是一个App。


其次,Application的实质是一个Context,它继承自ContextWrapper。

    android.content.Context
       ↳  android.content.ContextWrapper
        ↳ android.app.Application     

ContextWrapper是什么玩意?就是对Context的一个包装而已。

Application有两个子类,一个是MultiDexApplication,如果你遇到了"65535"问题,可以选择继承自他,完成多Dex打包配置的相关工作。

另外一个是在TDD(测试用例驱动)开发模式中使用Moke进行测试的时候用到的,可以来代替Application的Moke类MockApplication。


在应用启动的时候,会首先调用Application.attach(),当然,这个方法是隐藏的,开发者能接触到的第一个被调用的方法其实是Application.onCreate(),我们通常会在这个方法里面完成各种初始化,比如图片加载库、Http请求库的默认配置初始化操作等等。但是最好别在这个方法里面进行太多耗时操作,因为这会影响App的启动速度,所以对于不必要的操作可以使用异步操作、懒加载、延时加载等策略来减少对UI线程的影响。


除此之外,由于在Context中可以通过getApplicationContext()获取到Application对象,或者是通过Activity.getApplication()Service.getApplication()获取到Application,所以可以在Application保存全局的数据,供所有的Activity或者是Service使用。

PS:使用上面的三种方法获取到的都是同一个Application对象,getApplicationContext()是在Context的实现类ContextImpl中具体实现的,而getApplication()则是在Activity和Service中单独实现的,所以他们的作用域不同,但是获取到的都是同一个Application对象,因为一个Dalvik虚拟机只有一个Application对象。

但是这里存在着一个坑,那就是在低内存情况下,Application有可能被销毁,从而导致保存在Application里面的数据信息丢失,最后程序错乱,甚至是Crash。

所以当你想在Application保存数据的时候,请做好为空判断,或者是选择其他方式保存你的数据信息。


另外,在Application中存在着几个有用的方法,比如onLowMemory()和onTrimMemory(),在这两个方法里面,我们可以实现自己的内存回收逻辑,比如关闭数据库连接、移除图片内存缓存等操作来降低内存消耗,从而降低被系统回收的风险。


最后,就是要注意Application的生命周期,他和Dalvik虚拟机生命周期一样长,所以在进行单例或者是静态变量的初始化操作时,一定要用Application作为Context进行初始化,否则会造成内存泄露的发生。使用Dialog的时候一般使用Activity作为Context,但是也可以使用Application作为上下文,前提是你必须设置Window类型为TYPE_SYSTEM_DIALOG,并且申请相关权限。这个时候弹出的Dialog是属于整个Application的,弹出这个Dialog的Activity销毁时也不会回收Dialog,只有在Application销毁时,这个Dialog才会自动消失。

#更多参考资料

ZhaoKaiQiang avatar Dec 04 '15 01:12 ZhaoKaiQiang

@ZhaoKaiQiang 感谢凯子哥的讲解,收获非常大!

tangqi92 avatar Dec 04 '15 01:12 tangqi92

@ZhaoKaiQiang 修正下:

    /**
     * @hide
     */
    /* package */ final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }

attachBaseContext()是可以重写的

ipcjs avatar Dec 05 '15 16:12 ipcjs

恩恩,是的,多Dex打包可以重写这个方法

ZhaoKaiQiang avatar Dec 05 '15 17:12 ZhaoKaiQiang

@ZhaoKaiQiang 讲得很好!有一个问题请教:

所以在进行单例或者是静态变量的初始化操作时,一定要用Application作为Context进行初始化,否则会造成内存泄露的发生

这句话是指当创建的变量需要一个Context参数时,要传入Application,否则传入一般的Context会导致该Context对应的对象无法回收从而造成内存泄露么?

KeepSilenceQP avatar Apr 23 '16 13:04 KeepSilenceQP

原来连Application也会在低内存的时候被回收...学习了

那么我这么设置全局变量可不可以?

  1. 创建Application时实例化一个单例的全局静态对象类Config
  2. 实例化Config,在对象的set方法中将对象持久化到磁盘
  3. 每次调用get方法时先检测对象是否为null,如果为null就从磁盘中获取对象并重新赋值给对象

对象为null时从磁盘中获取对象的方法可以将所有保存的对象全部获取,这样可以减少磁盘读写次数,提高效率

NianyiYang avatar Apr 23 '16 16:04 NianyiYang

mark

395808 avatar Apr 25 '16 10:04 395808

mark

Dong4Am avatar Jul 20 '17 15:07 Dong4Am

@ZhaoKaiQiang 那就是在低内存情况下,Application有可能被销毁

这里我不明白了。低内存下,android只会对进程回收,怎么会精确到一个class呢? 请kaizi哥指点

08carmelo avatar Feb 09 '18 06:02 08carmelo

Application并非普通的Java类,一个进程只有一个,回收进程也就销毁了Application。

2018-02-09 14:20 GMT+08:00 08carmelo [email protected]:

@ZhaoKaiQiang https://github.com/zhaokaiqiang 那就是在低内存情况下,Application有可能被销毁

这里我不明白了。低内存下,android只会对进程回收,怎么会精确到一个class呢? 请kaizi哥指点

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/android-cn/android-discuss/issues/154#issuecomment-364345924, or mute the thread https://github.com/notifications/unsubscribe-auth/AD6ViUh2mWUtKH8MvA8c8d8e7Tkwvp2Wks5tS-OWgaJpZM4EPawn .

KeepSilenceQP avatar Feb 28 '18 11:02 KeepSilenceQP

@08carmelo 覆巢之下无完卵,进程都被杀死了,并不是只有Application被销毁,而是包括Application在内的所有进程内对象都会被销毁

ZhaoKaiQiang avatar Feb 28 '18 13:02 ZhaoKaiQiang

cnnx - fcc - fcc eijd # xxxxr

BlookLiu avatar Mar 01 '18 12:03 BlookLiu