1,图片缓存
1.1 基本原理:
①优先从内存中加载图片,速度最快,无流量消耗
②其次从本地(sdcard)加载图片,速度快,无流量消耗
③最后从网络下载图片,速度慢,消耗流量
1.2 网络缓存
①创建一个工具类myUtils,用来做缓存(bitMapUtils底层也是用的三级缓存)
创建一个方法display(imageView,url);
②先倒叙写流程(因为一开始没有图片,不先从网络加载,看不到图片)
创建一个网络缓存工具类:NetCacheUtils netUtils;
创建一个方法:通过url路径,设置图片给ImageView
1.3 AsyncTask的使用(android里提供的异步封装工具,可以实现异步请求及主界面更新,实际上是对线程池 + handler的封装)
①在NewCacheUtils中,创建一个类继承AsyncTask<Void,Void,Void>//不确定泛型传Void
重写方法
onPreExecute()//预加载,运行在主线程中
doInBackground()//正在加载,运行在子线程(可以异步请求)
onPostExecute()//加载结束,运行在主线程(核心方法)
onProgressUpdate()//更新进度的方法,运行在主线程中
②在创建的方法中
BitMapTask().execute();//启动AsyncTask
③泛型上的三个参数
第一个Void,影响doInBackground()方法上的参数
doinBackground()的参数是通过execute(参数....)传递过来的
doInBackground()的参数为可变参数,实际上是一个数组
params[index]可以取出对应的参数
把ImageView和Url传递进来,然后进行开始下载
第二个Void,影响onProgressUpdate()里的参数类型(参考使用Integer)
这个参数也是一个数组,总大小,之类的都可以传入这里
第三个Void,影响doInBackground()的返回值类型,onPostExecute()的参数类型
doInBackground()返回结果给onPostExecute()方法
然后在onPostExecute()方法中对结果进行处理(这里参考使用BitMap)
1.4 ①在doInBackground()方法中创建一个下载的方法
创建 HttpURLConnection conn;
setConnectionTimeout()//设置链接超时时间
setReadTimeout(5000);//读取超时,链接上了服务器,但是没有数据
拿到响应码,判断是否成功(200)
Is = conn.getInputStream();拿到输入流
BitMapFactory.decodeStream(is)//通过输入流拿到 BItMap 对象
额外1:onProgressUpdate()方法的使用,
PublishProgress(参数2)//调用此方法实现进度更新,回调onProgressUpdate()方法,当使用while循环下文件的时候,可以通过调用该方法设置进度
额外2:释放资源
if(conn!=null){
conn.disconnect();
conn = null;
}
②在结果中判断返回的BitMap对象是否为null;
就给BitMap对象设置数据
③设置默认图片,在工具类创建的方法里,直接就对ImageView设置数据(默认图)
额外1:网络缓存和ListView条目复用的BUG
有可能出现图片错位(网速慢的情况),新的图片未加载出来,复用旧的图片会显示异常(图片与文字对不上号).
解决1:在onInBackgound()方法中,打一个标记ImageView.setTag(url)//绑定图片和imageView,然后在加载结束onPostExecute()方法中,拿到标记getTag();
判断两个url是否一致
一致就设置图片,不一致就不设置图片
2.本地缓存
创建一个类LocalCacheUtils
主要是两个方法setLocalCache();//写本地缓存
getLocalCache();//读本地缓存
创建一个变量,记录需要保存的路径
(缓存文件夹,一般在sd卡中)(注意,文件夹不要带中文)
①setLocalCache(url)方法中
判断该文件夹是否存在,还需要判断是否是文件夹
缓存文件的命名:通过md5运算url(因为里面会有特殊字符)
bitMap.compress(格式,100(压缩比),输出流)//压缩图片,格式,压缩比,输出流(文件)
②getLocalCache(url)方法中
Md5运行url,查找对应的文件是否存在
如果存在,通过文件输入流解析成BitMap对象
③使用位置:在缓存工具类中,创建一个本地缓存工具的引用,传递给网络缓存
在结果方法中,设置缓存
在加载网路之前.判断是否有缓存(是否为空)
有缓存就不再访问网络了,设置图片,return掉逻辑
3,内存缓存,创建一个类:MemoryCacheUtils
两个方法 setMemoryCache();//写内存缓存
getMemoryCache();//读内存缓存
内存缓存,是运行起来之后才有的
创建一个集合HashMap<String(url),BitMap>,代表图片缓存的键值对
写缓存就put一个值进去,读缓存就通过url读取一个BitMap对象
在缓存工具类中,创建一个引用,优先从内存中加载图片(把引用传递给另两个工具)
写缓存的位置:读取到了本地缓存之后写一个内存缓存
读取到了网络缓存之后也写一个内存缓存
读缓存的时候,如果从内存读取到了,同样设置图片,然后return掉
4.使用软引用改造内存
4.1因为上面的写法,当图片数量比较多的时候,有内存溢出oom的可能.
android有默认分配的应用内存,一般为:16MB,一旦超出就会内存溢出了
4.2 垃圾回收器不起作用
因为垃圾回收器只回收没有引用的对象
引用的,所以不会进行回收
即使它会回收,也不会即时回收
4.3 ①只要让有引用的对象会被回收,就可以让垃圾回收器起作用
一般情况下的引用称为强引用,不会被回收
软引用:垃圾回收器会考虑回收
弱引用:垃圾回收器更会考虑回收,优先级高一些
虚引用:垃圾回收器会最优先回收,优先级相对最高
②Java中有对应的几个类表示这几类引用
SoftReference 软引用
WeakReference 弱引用
PhantomReference 虚引用(用得比较少,太容易被回收了)
③使用改造对象,SoftReference<BitMap>//需要包装的对象当做泛型传递进去
使用软引用包装
SoftReference<BitMap> soft = new SoftReference(bitMap);
获取内存中的对象时,要判断是否为null,因为它有可能被回收了.
使用软引用改造之后,读取缓存就不一定会读取到内存的,因为它被回收了,相对硬引用而言,提升的效率可能少一些,但是一般不会出现OOM的情况
5.LruCache的使用(google推荐使用它来管理内存溢出的方法)
api9,android2.3以后,垃圾回收器更倾向于回收软引用和弱引用,不管内存是否充足.
所以谷歌在v4包中提供了一个LruCache来替代上面的软引用和弱引用
Lru: least recently used 最近最少使用算法,可以将最近最少使用的对象回收掉,从而保证内存不会超出范围
Runtime.getRuntime().maxMemory();//获取分配给 app的内存大小
一般用内存最大值/8作为内存缓存大小
LruCache = new LruCache<String,bitMap>(int){//参数为内存空间大小
//重写方法,返回每个对象的大小
sizeOf(arg1,arg2)
通过参数上的bitMap.getByteCount()//返回图片的总大小,api12
查看源码可以看到底层实际上是通过
getRawBytes()//拿到每一行的像素*getHeight()//得到所有像素点个数==占用byte数
bitMap.getRowBytes() * bitMap.getHeight()//兼容低版本的写法
}
5.2 lruCache源码简析:
①底层维护了一个LinkedHashMap.
②put一个对象的时候,给集合中添加了一个对象
全局维护了一个size,会把sizeOf()返回的对象大小记录下来
③trimToSize()方法
这个方法里有一个while循环
循环里对size与构造里传进来的总大小做比较
如果大于总大小,拿到最早(迭代器next()获得)放进来的对象,remove()删除掉
这个算法实际上是当内存超出预期的时候,删掉早期添加进来的对象
6,用自己工具类替代BitMapUtils();
发现在快速滑动切换页签的时候还是会报错,开发中还是用BitMapUtils.
BitMapUtils底层使用的也是三级缓存,使用了lruCache来解决加载图片内存溢出问题.
7.屏幕适配
常见屏幕适配
图片适配,布局适配,尺寸适配,权重适配,代码适配
7.1 图片适配
把图片放到不同的文件夹下
设备密度(不绝对)
Hdpi 高分辨率 >> 480*800 0.75
Ldpi 低分辨率 >> 240*320 1
Mdpi 中等分辨率 >> 320*240 1.5
Xhdpi 超高分辨率 >> 1280*720 2
Xxhdpi 超超高分辨率 >> 1920*1080 3
开发中:如果发现图片不符合预期,就针对具体的机型放置图片即可
常规做法:做一套图:比如1280*720 切图,放在哪个文件夹都可以,一般Hdpi下,如果摸个屏幕出了问题,再针对该屏幕,对相关出问题的图片进行替换
7.2 布局适配
有时候即使用了dp去设置,也会出现布局混乱的情况
出现这种情况,可以针对对应的屏幕写一个文件夹layout-800x480(针对480*800的),在这下面写对应的布局文件(名称一致,id一致,就是布局样式是根据不同屏幕来的,不建议对控件类型和个数进行调整,不然代码编写会很麻烦的)
7.3 尺寸适配
①设备密度
拿到设备密度:float density = getResource().getDisplayMetrics().density
//dp和px
Dp:根据设备转换大小
px = dp*设备密度
②values>>dimens.xml 定义尺寸的文件
<dimens name=”width”>160dp</diments>
在属性中引入@dimens:width
可以指定多个values文件夹 values-1280x720,然后写一个dimens文件,指定该屏幕的dp;
③可以通过尺寸适配来替代布局适配
7.4,权重适配
在LinearLayout中指定属性:weightSum = 3;可以指定总权重
子控件设置的权重比就是根据这个3为比值做显示
通过权重适配,可以适配所有屏幕.
7.5 代码适配
拿到屏幕的宽高,WindowManager wm = getWindowManager();
wm.getDefaultDisplay().getWidth();//获取宽高
需要设置的控件,拿到对应的布局参数(布局参数根据父控件来拿,比如LinearLayout)
LinearLayout.LayoutParams params;
然后设置想要设置的宽高
同样的,也是不用考虑屏幕分辨率
8,解决智慧北京的遗留的适配问题
例如:新手引导小圆点的间距,是在代码中写的,代表的是px
写一个工具类DensityUtils
dp2px>>>dp转换成px(我们一般都是按dp来设置的)
//拿到屏幕密度
context.getResources().getDisplayMetrics().density
Dp * density >>>得到转换过来的px
但是float 4.9强转成整数>>>4
所以Dp*density + 0.5 就能实现四舍五入
同样也可以px2dx,把px转换成dp
注意:这里的转换实际上是数字的转换,并不是说有种数据类型是dp和px
代码里一般直接写的数字都是px,开发中要注意转换成dp转换的值
侧边栏的宽度,不应该写死成200
获取屏幕的宽度wm.getDefalutDisplay().getWidth();
通过width * 200/320 获取比值,设置到侧边栏上
9,屏幕适配总结
养成良好的开发习惯,多用sp,dp;不用px,多用相对布局和线性布局,不用绝对布局,代码中如果必须设置像素的话,将dp转为px进行设置
项目开发后期,对适配问题进行验证,如果发现有问题,针对性进行修改
480*800,1280*720,1920*1080 都要跑一下
320*480 就没必要了,基本上被淘汰了.
平板电脑和手机,平板需要另外开发,因为适配问题差别很大.
在公司里往往都用真机进行测试.
需要的分辨率手机可以向公司申请,但是不要买太多,适配太麻烦了