飞道的博客

Android知识综合梳理(大篇幅、大范围)

186人阅读  评论(0)

因为最近在全方位总结安卓知识,这篇文章目录和大纲有点乱,继续写下去篇幅会特别长。所以我重新列了一个大纲,大纲会逐渐丰富其内容,从虚拟机到类加载,从堆栈内存到数据结构,从ui到网络层,大纲里的每一个知识点我都会用一篇新文章详细概述,会附有超链接,大纲新地址是:https://blog.csdn.net/weixin_42802049/article/details/115542194。还请移步新大纲地址哦~~~

虚拟机

jvm

1. 加载过程:
1,A.java经过编译器编译,生成A.class字节码文件,多个class文件会被打包成jar文件
2,程序访问A这个类时,会通过ClassLoader类加载器将A.class加载到jvm的内存中

dalvik+JIT

1. 加载过程:
1,A.java经过编译器编译,生成A.class字节码文件
2,dx工具将class文件打包成dex文件
2. JIT(Just In Time Compiler):
即时编译策略,即动态编译
缺点:
	1,每次启动应用都要重新编译(没有缓存)
	2,所以运行时比较耗电

art+AOT

1. AOT(Ahead Of Time)
提前编译策略,即静态编译
缺点:
	1,应用首次安装和系统升级之后(需要重新编译)比较耗时
	2,编译后的文件占用更多的内存空间,缓存的结果
2. JIT和AOT的区别
无非就是空间换时间

内存

dalvik内存(即java内存)

heap(堆内存):线程共享区域

1. 存储数据类型
1,成员变量
	基本数据类型:其变量名及其数据值存放在堆内存中
   		-
	引用数据类型:对象存放在堆内存中,其变量名和地址值存放在栈中,该地址值指向所引用的对象
2,局部变量
	引用数据类型:对象存放在堆内存中,其变量名和地址值存放在栈中,该地址值指向所引用的对象
2. 内存释放
GC

stack(栈内存):线程私有区域

1. 存储数据类型
1,局部变量
	基本数据类型:其变量名及其数据值存放在栈内存中
	
疑问:局部变量中的基本数据类型都是存储在栈内存中吗?
	不是。
	int[] array=new int[]{1,2};
	由于new了一个对象,所以new int[]{1,2}这个对象是存储在堆中的,也就是说1,2这两个基本数据类型是存储在堆中
2. 内存释放
方法结束,内存自动释放,有自己的作用域

方法区:线程共享区域

1. 存储数据类型
方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量

特殊类型String

String对象创建有两种方式:
1. 字面量形式:对象的引用存储在字符串常量池(在方法区中)
String str1 = "aaa";
	解释:字符串常量池中不存在aaa这个字符串对象的引用,所以新创建一个字符串对象,然后将这个引用放入字符串常量池。
String str2 = "aaa";
	解释:字符串常量池中已经存在aaa这个字符串对象的引用,于是将已经存在的字符串对象的引用返回给变量str2,这里不会再重新创建字符串对象。
结论:System.out.println(str1 == str2); 结果为true。
2. 使用new创建
不论字符串常量池有没有相同内容对象的引用,都重新创建字符串对象,然后将这个引用存储到栈内存中。

String str3 = new String("aaa");
String str4 = new String("aaa");
结论:System.out.println(str3 == str4); 结果为false。

如果想让new出来的String对象的引用加入到字符串常量池中,可以使用intern方法。
即String str4 = str3.intern();这时候System.out.println(str3 == str4); 结果为true。

native内存

Android的java程序为什么容易出现OOM?

	首先要知道一个概念:RAM,即物理内存。
	
	因为Android系统对dalvik的vm heapsize(虚拟内存)做了硬性限制,根据定制系统不一样,有16M、24M等。也就是说,在RAM充足的情况下,也可能发生OOM。

如何解决OOM

安卓代码程序优化(后面着重讲)

创建子进程

创建一个新的进程,就可以把一些对象分配到新进程的heap上。
方法:使用android:process标签
缺点:会增加系统开销

使用jni在native heap上申请空间

native heap的增长不受dalvik的vm heapsize的限制。
只要RAM有足够的剩余空间,都可以在nativr heap上申请空间。
当然,如果RAM快耗尽了,memory killer会杀进程释放RAM
---------------------------------------------------------------------
(所以这里会涉及到进程的优先级,后面着重讲)
---------------------------------------------------------------------

修改vm heapsize的大小

Bitmap分配在dalvik heap还是native heap?

过多的创建bitmap会导致OOM,且native heap不受dalvik限制,所以bitmap只能是分配在dalvik heap上。因为只有这样才能解释bitmap容易导致OOM。

但是,有人可能会说,Bitmap确实是使用java native方法创建的啊,为什么会分配到dalvik heap中呢?

1,bitmap创建:
	Bitmap bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);


2.进入frameworks源码:
	发现会调用到BitmapFactory.cpp中的deDecode方法,最终会调用到Graphics.cpp的createBitmap方法。

	通过NewObject,创建Bitmap又回到了JAVA层。

Android内存优化(即安卓代码程序优化)

解决内存溢出(OOM)

什么是内存泄漏,什么是内存溢出

1,内存泄漏:memory leak
	程序申请内存后,被某个其他对象一直持有,无法释放已申请的内存空间。(内存只进不出)
2,内存溢出:out of memory
	当内存一直被申请,却得不到释放,内存空间太小。当程序在申请内存时,没有足够的空间供其使用,就出现OOM。

注意:内存泄漏/溢出不能try catch处理。

如何解决

1,大图片和多图片的优化
图片如果太大,加载到内存时可能导致OOM。
解决:
	1,合理使用JPG和PNG
		JPG内存小,但解码复杂
		PNG内存大,但解码简单。如果PNG图片过多,会造成频繁GC,甚至OOM。
	
	2,图片压缩:
		2.1,createScaledBitmap();现成的API,但是这个API的使用前提条件是图片需要先加载到内存中。
		2.2,还有一个经常使用到的技巧是inJustDecodeBounds,可以事先获取到图片的大小。需要设置bitmapOptions.inJustDecodeBounds = true;
		2.3,降低分辨率
		2.4,指定解码方式:Bitmap.Config ARGB_8888  默认,一个像素点占32位,最占内存的
					 Bitmap.Config ARGB_4444  一个像素点占16位
	3,bitmap对象不使用时,要recycle()释放内存
	4,尽量使用RecyclerView或者ListView替换ScrollView
2,聊一下图片的三级缓存
目的:减少不必要的流量消耗,增加加载速度
原理:
	一级缓存-->内存,LruCache,采用最近最少原则,把最近使用的通过LinkedhashMap来强引用持有,把最少使用的对象在缓存值达到预设值之前就从内存中移除
	二级缓存-->本地,DiskLruCache,
	三级缓存-->网络
流程:先从内存加载,内存中有,就直接加载;内存中没有,就从本地加载,若本地中有,加载的同时缓存到内存;若本地也没有,从网络加载,同时缓存到内存和本地。
Glide自带三级缓存
	skipMemoryCache(false),默认false,自动开启内存缓存。
	diskCacheStrategy(DiskCacheStrategy.RESULT),开启硬盘缓存
		DiskCacheStrategy.NONE: 表示不缓存任何内容
		DiskCacheStrategy.SOURCE: 表示只缓存原始图片
		DiskCacheStrategy.RESULT: 表示只缓存转换过后的图片(默认选项)
		DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片
3,聊一下图片加载框架
Glide:
	可以处理大型的图片流,图片自动缩放的
Picasso:
	体积非常小,图片未缩放的
Fresco:
	体积非常大,图片存储在系统的匿名共享内存,而不是dalvik heap内存中,所以不会导致OOM,减少了频繁GC,性能更高。
4,强引用、软引用、弱引用、虚引用
1,强引用:直接new出来的对象
		String str = new String("aaa");
	特点:对象任何时候都不会对系统回收,JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。

2,软引用:SoftReference
		String str = new String("aaa");
		SoftReference s = new SoftReference(str);
		if(s.get() != null){ // 一定要判空
			String softStr = s.get();
		}
	特点:内存空间足,不回收;内存空间不足,就会回收
		  
3,弱引用:WeakReference
		String str = new String("aaa");
		WeakReferences = new WeakReference(str);
		if(s.get() != null){ // 一定要判空
			String weakStr = s.get();
		}
	特点:无论内存空间是否足够,只要发现,就会回收

4,虚引用:PhantomReference
	特点:可以在任何时候被回收,无法通过get()方法来获取对象实例。仅仅只能在对象被回收时收到一个通知。

我们知道,java的Object类里有个finalize()方法,它的工作原理是:一旦该对象即将被回收,会调用其finalize()方法,并且在下一次真正回收动作发生时,才会真正的回收它。
但是问题在于,GC时间是随机的,finalize()方法被调用时间也是随机的,使用虚引用就可以解决这个问题。虚引用可以精细的跟踪内存使用情况。

到底什么时候用软引用,什么时候用弱引用?
1,如果只是想避免OOM,则可以使用软引用;如果对应用的性能更在意,则可以使用弱引用。
2,根据对象是否经常被使用。如果对象经常被使用,则尽量用软引用,否则使用弱引用。
5,解决内存泄漏
5.1 单例导致的
	单例的静态特性使其生命周期和应用一样长,所以传入的context的生命周期至关重要,一定要传入Application的Context,而不是Activity的Context。
5.2 非静态类创建静态实例
	该静态实例生命周期和应用一样长,导致了该静态实例一直持有该Activity的引用,该Activity销毁后内存资源不能正常回收。
	要么去掉static,要么将this换成Application的Context

5.3 非静态内部类创建静态实例
	非静态内部类会默认持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
	要么将该内部类设为静态内部类,因为静态的内部类不会持有外部类的一个隐式引用;要么将该内部类抽取出来,如果需要使用Context,需要使用ApplicationContext 。

5.4 异步任务导致的
	若Activity已经销毁,但是异步任务还没结束,需要将AsyncTask、Thread等设为静态内部类,而且需要在Activity销毁时取消相应的异步任务。

5.5 Handler导致的
	Handler是非静态匿名内部类,持有Activity的引用,虽然Handler实例不是静态的,但是消息队列是在Looper线程中不断轮询处理消息的,当Activity销毁,消息队列中还有未处理的消息,消息队列中的Message持有Handler实例的引用,Handler又持有Activity的引用,就会导致Activity内存资源无法正常回收。
	将Handler设为静态内部类,传入的Context可以使用ApplicationContext,也可以将Context弱引用,而且需要在Activity销毁时移除消息队列中的消息,handler.removeCallbacksAndMessages。

5.6 动画导致的
	属性动画中可以设置无限循环动画,该View持有该Activity对象的引用
	需要在Activity销毁时停止动画,objectAnimator.cancel()。

5.7 资源未关闭导致的
	IO流未关闭、BroadcastReceiver未注销、Bitmap未释放等。

5.8 第三方库导致的
	EventBus,RxJava等,在Activity销毁时需要解除订阅

解决ANR

Android启动优化


转载:https://blog.csdn.net/weixin_42802049/article/details/115506336
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场