396 |
--------------------------------------------------------------------------------
/Android/五.ⅱ、内存优化.md:
--------------------------------------------------------------------------------
1 | 目录:
2 | - [基础知识](#基础知识)
3 | - [内存泄漏](#内存泄漏)
4 | - [内存优化方案](#内存优化方案)
5 |
6 | ### 基础知识
7 |
8 | #### [1、Android系统的进程管理。](http://gityuan.com/2015/10/01/process-lifecycle/)
9 |
10 | 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是清除重要性稍低一级的进程,依此类推,以回收系统资源。
11 |
12 | - 前台进程(Foreground process):正在与用户进行交互的进程,一般系统是不会杀死前台进程的,除非用户强制停止应用或者系统内存不足等极端情况会杀死。
13 | - 拥有用户正在交互的 Activity(已调用onResume())
14 | - 拥有某个 Service,后者绑定到用户正在交互的 Activity
15 | - 拥有正在“前台”运行的 Service(服务已调用 startForeground())
16 | - 拥有正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
17 | - 拥有正执行其 onReceive() 方法的 BroadcastReceiver
18 |
19 | - 可见进程(Visible process):没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
20 | - 拥有不在前台、但仍对用户可见的 Activity(已调用onPause())。
21 | - 拥有绑定到可见(或前台)Activity 的 Service
22 |
23 | - 服务进程(Service process):在内存不足以维持所有前台进程和可见进程同时运行的情况下,服务进程会被杀死。
24 | - 正在运行startService()方法启动的服务,且不属于上述两个更高类别进程的进程。
25 |
26 | - 后台进程(Background process):通常会有很多后台进程在运行,因此它们会保存在LRU列表中,以确保包含用户最近查看的Activity的进程最后一个被终止。
27 | - 对用户不可见的Activity的进程(已调用Activity的onStop()方法)。
28 |
29 | - 空进程(Empty process):保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
30 | - 不含任何活动应用组件的进程
31 |
32 |
33 | #### [2、Android内存管理机制(LowMemoryKiller)。](http://gityuan.com/2016/09/17/android-lowmemorykiller/)
34 |
35 | **Android中的内存回收主要依靠LowMemoryKiller来完成,一种根据阈值级别出发相应力度的内存回收的机制。**
36 |
37 | Android基于Linux的系统,其实Linux有类似的内存管理策略——OOM killer,全称(Out Of Memory Killer), OOM的策略更多的是用于分配内存不足时触发,将得分最高的进程杀掉。而LMK则会每隔一段时间检查一次,当系统剩余可用内存较低时,便会触发杀进程的策略,根据不同的剩余内存档位来来选择杀不同优先级的进程,而不是等到OOM时再来杀进程,真正OOM时系统可能已经处于异常状态,系统更希望的是未雨绸缪,在内存很低时来杀掉一些优先级较低的进程来保障后续操作的顺利进行。
38 |
39 |
40 | #### 3、Java的四种引用,强弱软虚,及其适用的场景。
41 | - 强引用:
42 | - 强引用可以直接访问目标对象。
43 | - 强引用所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。
44 | - 强引用可能导致内存泄露。
45 | - 软引用:
46 | - 在OutOfMemory异常发生之前,被占用的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。
47 | - 需要注意的是,在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现NullPointerException异常导致应用崩溃。
48 |
49 | - 如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。
50 | - 还有就是可以根据对象是否经常使用来判断。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。
51 |
52 | - 弱引用:在系统GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。但是,由于垃圾回收器的线程通常优先级很低,因此,并一不定能很快的发现持有弱引用的对象。这种情况下,弱引用对象可以存在较长的一段时间。一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册引用队列中。
53 |
54 | 实际应用:播放器的播放Panel,是一个View,就是在视频播放时,可以show、hide, 也可以拖拽进度条之类,还有上面的音量,亮度调节等。这样一个view,我们用弱引用,因为在视频播放过程中,不论硬解还是软解,都将占用大量内存。
55 |
56 | - 虚引用:
57 | - 虚引用是所有引用类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。
58 | - 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,销毁这个对象,奖这个虚引用加入引用队列。
59 |
60 |
61 | ### 内存泄漏
62 |
63 | #### [4、内存泄漏如何产生,常见的Android内存泄漏的场景,怎么解决。](https://yq.aliyun.com/articles/3009)
64 |
65 | - 单例造成的内存泄露。
66 | - 匿名内部类/非静态内部类和异步线程。
67 | ```
68 | public class MainActivity extends AppCompatActivity {
69 |
70 | private static TestResource mResource = null;
71 |
72 | @Override
73 | protected void onCreate(Bundle savedInstanceState) {
74 | super.onCreate(savedInstanceState);
75 | setContentView(R.layout.activity_main);
76 | if (mManager == null) {
77 | mManager = new TestResource();
78 | }
79 | //...
80 | }
81 |
82 | class TestResource {
83 | //...
84 | }
85 | }
86 | ```
87 | 每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
88 |
89 | 正确的做法为:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例。
90 |
91 | - 匿名内部类:android开发经常会继承实现Activity/Fragment/View,此时如果你使用了匿名类,并被异步线程持有了,那要小心了,如果没有任何措施这样一定会导致泄露。
92 | - Handler 造成的内存泄漏。
93 | - 集合类泄漏。
94 |
95 | #### 5、 怎么发现和分析内存泄漏。
96 |
97 | 监测工具:
98 | - LeakCanary:square出品的内存泄漏检测库,可以定位90%的内存泄漏问题
99 | - MemoryProfiler:AndroidStudio自带的内存分析工具。
100 | - AllocationTracer:AndroidStudio自带的查看内存分配的工具。
101 |
102 | #### 6、 OOM能不能用try catch捕获。
103 |
104 | OOM(OutOfMemeryError)属于Error,只有在一种情况下可以捕获OOM,只有在确认并OOM是由try语句中的对象声明导致的,那么在catch语句中,可以释放掉这些对象,解决OOM的问题,继续执行剩余语句。
105 |
106 | #### 7、 OOM遇到过哪些情况,如何解决的。
107 |
108 | [美团OOM案例详细分析](https://tech.meituan.com/oom_analysis.html)
109 | [Handler造成的OOM分析](http://www.chenwenguan.com/android-oom-analysis/)
110 |
111 | OOM类型:
112 | - Java堆溢出(java.lang.OutOfMemoryError: Java heap space):一般通过内存映像分析工具MAT(Eclipse Memory Analyzer)对dump出来的HPROF文件进行分析,确认是内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
113 |
114 | - 内存泄漏(Memory Leak):分析GC Roots引用链,定位出内存泄漏的代码。
115 | - 内存溢出(Memory Overflow):从代码上检查某些对象的生命周期过长、持有状态时间过长。
116 |
117 | - 虚拟机栈和本地方法栈溢出
118 |
119 | - 方法区和运行时常量池溢出
120 |
121 | - 本机直接内存溢出
122 |
123 | 常见的OOM场景:
124 | - Adapter没使用缓存的convertView。
125 | - Bitmap没有及时回收,调用recycle()函数并不能立即释放Bitmap,读取Bitmap到内存的时候没有做采样率的设置。
126 | - 线程数超限,proc/pid/status中记录的线程数超过proc/sys/kernel/threads-max中规定的最大线程数,场景如随意创建线程,没有使用线程池来管理。
127 | - 广播注册之后没有进行注销。
128 | - WebView没有销毁,应该调用destroy()函数去销毁。
129 | - Handler使用不当导致。
130 |
131 | #### [8、Bitmap使用的时候注意什么(Bitmap优化)。](https://www.jianshu.com/p/e49ec7d053b3)
132 |
133 | **基础知识:**
134 | - Options.inPreferredConfig修改图片编码格式:
135 |
136 | Bitmap.Config ALPHA_8 每个像素占用1 bit (8位)内存
137 | Bitmap.Config ARGB_4444 每个像素占用2 bit (16位)内存
138 | Bitmap.Config ARGB_8888 每个像素占用4 bit (32位)内存
139 | Bitmap.Config RGB_565 每个像素占用2 bit (16位)内存
140 |
141 | - 获取Bitmap大小:
142 |
143 | **加载一张本地资源图片,那么它占用的内存 = width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一个像素所占的内存。**
144 |
145 | 以1024 * 594的图片为例:
146 | ```
147 | // 不做处理,默认缩放。
148 | BitmapFactory.Options options = new BitmapFactory.Options();
149 | Bitmap bmp01 = BitmapFactory.decodeResource(getResources(), R.mipmap.bmp, options);
150 |
151 | int size01Allocation = bmp01.getAllocationByteCount();
152 | int size01 = bmp01.getByteCount();
153 |
154 | // 手动设置inDensity与inTargetDensity,影响缩放比例。
155 | BitmapFactory.Options options_setParams = new BitmapFactory.Options();
156 | options_setParams.inDensity = 320;
157 | options_setParams.inTargetDensity = 320;
158 |
159 | Bitmap bmp02 = BitmapFactory.decodeResource(getResources(), R.mipmap.bmp, options_setParams);
160 |
161 | int size02Allocation = bmp02.getAllocationByteCount();
162 | int size02 = bmp02.getByteCount();
163 | ```
164 | xhdpi的文件夹下,inDensity为320,inTargetDensity为440,内存大小为4601344;而4601344 = 1024 * 594 * (440 / 320)* (440 / 320)* 4
165 |
166 | 手动设置inDensity与inTargetDensity,使其比例为1,内存大小为2433024;2433024 = 1024 * 594 * 1 * 1 * 4。
167 |
168 | **除了加载本地资源文件的解码方法会默认使用资源所处文件夹对应密度和手机系统密度进行缩放之外,别的解码方法默认都不会。此时Bitmap默认占用的内存 = width * height * 一个像素所占的内存。**
169 |
170 | **优化策略:**
171 | - 使用setImageBitmap、setImageResource、BitmapFactory.decodeResource这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存,而BitmapFactory.decodeStream方法则是通过JNI来创建Bitmap,更节约内存。
172 | ```
173 | InputStream is = getResources().openRawResource(R.drawable.pic);
174 | Bitmap bitmap = BitmapFactory.decodeStream(is);
175 | ```
176 |
177 | - 对图片进行压缩显示,按需分配内存。
178 |
179 | - 对图片进行质量压缩。bitmap.compress
180 | - 对图片进行尺寸缩放。Bitmap.Options.inSampleSize
181 | inSampleSize的取值必须为2的整数倍,因为直接从点阵中隔行抽取最有效率,所以为了兼顾效率, inSampleSize 这个属性只认2的整数倍为有效。
182 |
183 | - 使用三方库对图片进行压缩。libjpeg
184 |
185 | - 使用LruCache和DiskLruCache做内存和磁盘缓存。
186 |
187 | - Bitmap进行复用:Android 3.0之前Bitmap.recycle,3.0之后进行复用。
188 |
189 | - 使用WeakReference,保证资源能够被及时回收。
190 |
191 | **[Bitmap在不同文件夹下面的大小计算](https://ivonhoe.github.io/2017/03/22/Bitmap&Memory/)**
192 |
193 | [dpi](https://github.com/chen-eugene/Android-Interview/blob/master/image/dpi.png)
194 |
195 | 当Android系统加载图片时,会针对当前系统的dpi和图片目录的source dpi比较做相应比例的缩放,如果一张图片放在drawable-xxxhdpi目录,这是告诉系统,针对dpi为640的手机屏幕上,这张图片是刚刚好的,它的scale为1.0。
196 |
197 | 例如一张180的图片在dpi为560的手机中,只有将其放在drawable-nodpi或者drawable-560dpi目录中才可以显示真正的大小。
198 |
199 | - 如果将这张图片放在xxxhdpi文件夹中,那么可以计算它的大小:size = (int)(180 * (560 / 640) + 0.5f) = 158px
200 | - 放入xxhdpi目录中,实际大小应该为:size = int (180 * (560 / 480) +0.5f ) = 210px
201 | - 放入xhdpi目录中,实际大小应该为:size = int (180 * (560 / 320) +0.5f ) = 315px
202 | - 放入hdpi目录中,实际大小应该为:size = int (180 * (560 / 240) +0.5f ) = 420px
203 |
204 |
205 | #### 9、 Bitmap recycler相关(Bitmap复用)。
206 |
207 | Google 官方教程 [Managing Bitmap Memory](https://developer.android.com/topic/performance/graphics/manage-memory)
208 | [Glide Bitmap复用](https://www.jianshu.com/p/d6cae68175f2)
209 |
210 | Android2.2(API 8)一下的时候,当 GC 工作时,应用的线程会暂停工作,同步的 GC 会影响性能。而 Android2.3 之后,GC 变成了并发的,意味着 Bitmap 没有引用的时候其占有的内存会很快被回收。
211 |
212 | 在Android2.3.3(API10)之前,Bitmap 的像素数据存放在 Native 内存,而 Bitmap 对象本身则存放在 Dalvik Heap 中。Native 内存中的像素数据以不可预测的方式进行同步回收,有可能会导致内存升高甚至 OOM Crash。而在 Android3.0 之后,Bitmap 的像素数据也被放在了 Dalvik Heap 中。
213 |
214 | Android2.3.3 及以下:推荐使用Bitmap#recycle方法进行内存回收。
215 |
216 | Android3.0 及以上:推荐使用Bitmap复用。
217 |
218 | **Bitmap复用:**
219 | - 被复用的 Bitmap 必须设置inMutable为true(通过 BitmapFactory.Options 设置。
220 | - Android4.4(API 19)之前只有格式为jpg、png,同等宽高(要求苛刻),inSampleSize为1的Bitmap才可以复用。
221 | - Android4.4(API 19)之前被复用的Bitmap的inPreferredConfig会覆盖待分配内存的Bitmap设置的inPreferredConfig。
222 | - Android4.4(API 19)之前待加载Bitmap的Options.inSampleSize必须明确指定为1。
223 | - Android4.4(API 19)之后被复用的Bitmap的内存必须大于需要申请内存的Bitmap的内存。
224 |
225 | ```
226 | BitmapFactory.Options options = new BitmapFactory.Options();
227 | // 图片复用,这个属性必须设置;
228 | options.inMutable = true;
229 | // 手动设置缩放比例,使其取整数,方便计算、观察数据;
230 | options.inDensity = 320;
231 | options.inTargetDensity = 320;
232 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.resbitmap, options);
233 | // 对象内存地址;
234 | Log.i(TAG, "bitmap = " + bitmap);
235 | Log.i(TAG, "bitmap:ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
236 |
237 | // 使用inBitmap属性,这个属性必须设置;
238 | options.inBitmap = bitmap;
239 | options.inDensity = 320;
240 | // 设置缩放宽高为原始宽高一半;
241 | options.inTargetDensity = 160;
242 | options.inMutable = true;
243 | Bitmap bitmapReuse = BitmapFactory.decodeResource(getResources(), R.drawable.resbitmap_reuse, options);
244 | // 复用对象的内存地址;
245 | Log.i(TAG, "bitmapReuse = " + bitmapReuse);
246 | Log.i(TAG, "bitmap:ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
247 | Log.i(TAG, "bitmapReuse:ByteCount = " + bitmapReuse.getByteCount() + ":::bitmapReuse:AllocationByteCount = " + bitmapReuse.getAllocationByteCount());
248 |
249 | 输出:
250 | I/lz: bitmap = android.graphics.Bitmap@35ac9dd4
251 | I/lz: width:1024:::height:594
252 | I/lz: bitmap:ByteCount = 2433024:::bitmap:AllocationByteCount = 2433024
253 | I/lz: bitmapReuse = android.graphics.Bitmap@35ac9dd4 // 两个对象的内存地址一致
254 | I/lz: width:512:::height:297
255 | I/lz: bitmap:ByteCount = 608256:::bitmap:AllocationByteCount = 2433024
256 | I/lz: bitmapReuse:ByteCount = 608256:::bitmapReuse:AllocationByteCount = 2433024 // ByteCount比AllocationByteCount小
257 | ```
258 |
259 | #### [10、WebView的泄露如何解决。](https://lipeng1667.github.io/2016/08/06/memory-optimisation-for-webview-in-android/)
260 |
261 | 很少发现WebView出现内存泄露的情况,在Android5.1系统中会出现,WebView没有得到及时的释放。
262 |
263 | - 不在xml中使用WebView,而是在Activity中代码创建,并且Context传入ApplicationContext。
264 |
265 | 并不能解决WebView的泄露问题,因为WebView持有Context而造成的Context泄露,而是WebView本身的泄露。
266 |
267 | - 销毁WebView前,先将WebView从View树中移除,然后在销毁。
268 |
269 | ```
270 | if(mWebView != null){
271 | ((ViewGroup)mWebView.getParent()).removeView(mWebView);
272 | mWebView.destroy();
273 | mWebView = null;
274 | }
275 | ```
276 |
277 | - 单独启动一个进程来进行WebView的相关操作,在结束的时候直接kill掉WebView所在的进程。(没有试过)
278 |
279 |
280 | ### [内存优化方案](https://www.jianshu.com/p/218e5cde47fe)
281 |
282 | #### 11、内存优化方案
283 |
284 | 内存优化工具:
285 | - LeakCanary:square出品的内存泄漏检测库,可以定位90%的内存泄漏问题
286 | - MemoryProfiler:AndroidStudio自带的内存分析工具。
287 | - AllocationTracer:AndroidStudio自带的查看内存分配的工具。
288 |
289 | 优化方向:
290 | - [① 解决所有的内存泄漏问题](https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=2649796884&idx=1&sn=92b4e344060362128e4a86d6132c3736&scene=0#wechat_redirect)
291 | - ② 避免内存抖动
292 | - 避免在循环中创建临时对象
293 | - 避免在onDraw中创建Paint、Bitmap对象等。
294 |
295 | - ③ Bitmap的优化
296 | - ④ 使用优化过的数据结构
297 | - ⑤ 使用onTrimMemory根据不同的内存状态做相应处理
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
--------------------------------------------------------------------------------
/Android/二、四大组件相关.md:
--------------------------------------------------------------------------------
1 | ### 四大组件
2 | ##### 1、手动画出Activity、Fragment的生命周期,他们是怎么关联的。
3 |
4 | 
5 |
6 | - onCreate():当 Activity 第一次创建时会被调用。这是生命周期的第一个方法。在这个方法中,可以做一些初始化工作,比如调用setContentView去加载界面布局资源,初始化Activity所需的数据。当然也可借助onCreate()方法中的Bundle对象来回复异常情况下Activity结束时的状态。
7 |
8 | - onRestart():表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为导致的,比如用户按Home键切换到桌面或打开了另一个新的Activity,接着用户又回到了这个Actvity。
9 |
10 | - onStart(): 表示Activity正在被启动,即将开始,这时Activity已经出现了,但是还没有出现在前台,无法与用户交互。**这个时候可以理解为Activity已经显示出来,但是我们还看不到。**
11 |
12 | - onResume():**表示Activity已经可见了,并且出现在前台并开始活动**。需要和onStart()对比,onStart的时候Activity还在后台,onResume的时候Activity才显示到前台。
13 |
14 | - onPause():表示 Activity正在停止,仍可见,正常情况下,紧接着onStop就会被调用。在特殊情况下,如果这个时候快速地回到当前Activity,那么onResume就会被调用(极端情况)。**onPause中不能进行耗时操作,会影响到新Activity的显示。因为onPause必须执行完,新的Activity的onResume才会执行。**
15 |
16 | - onStop():表示Activity即将停止,不可见,位于后台。可以做稍微重量级的回收工作,同样不能太耗时。
17 |
18 | - onDestory():表示Activity即将销毁,这是Activity生命周期的最后一个回调,可以做一些回收工作和最终的资源回收。
19 |
20 | ##### 2、Activity常见情形下的生命周期,如按下home键、锁屏。
21 | - Back键:onPause → onSaveInstanceState → onStop → onDestroy
22 | - Home键:onPause → onStop / onRestart → onStart → onResume
23 | - 锁屏: onPause → onSaveInstanceState → onStop / onRestart → onStart → onResume
24 | - A启动B:A:onPause → B:onCreate → B:onStart → B:onResume → A:onSaveInstanceState → A:onStop → A:onDestroy(A是否调用finish)
25 | - 被打断时(如接电话):onPause → onSaveInstanceState → onStop / onRestart → onStart → onResume
26 | ##### 3、 异常情况下Activity的生命周期,如横竖屏切换、系统资源不足。
27 | - 横竖屏切换
28 | Activity被销毁:onPause → onSaveInstanceState(与onPause没有时序关系,可能在之前,也可能在之后) → onStop → onDestroy
29 | Activity被重新创建:onCreate → onRestoreInstanceState → onStart → onResume
30 | 当不想Activity被重新创建,则需要在Manifest中添加configChanges属性,在Activity中复写onConfigurationChanged方法。
31 | - 资源内存不足导致低优先级Activity被kill
32 | ①前台Activity —— 正在和用户交互的Activity,优先级最高。
33 | ②可见但非前台Activity —— 如Activity弹了一个对话框,导致Activity可见但位于后台,无法与用户直接交互。
34 | ③后台Activity —— 已经被暂停的Activity,比如执行了onStop,优先级最低。
35 | 当系统内存不足时,会按照优先级去杀死Activity,并通过onSaveInstanceState和onRestoreInstanceState来保存和恢复数据。
36 |