Android 开发艺术探索笔记(22)

网友投稿 727 2022-11-17

Android 开发艺术探索笔记(22)

Android 开发艺术探索笔记(22)

HandlerThread

HandlerThread继承了Thread,它是一种可以使用Handler的Thread,它的实现就是在run方法中通过Looper.prepare()来创建消息队列,并通过Loop.loop()来开启消息循环,这样在实际中就允许在HandlerThread中创建Handler了。

它和普通的Thread不同在于普通Thread主要是从run方法中执行一个耗时任务,而HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知Thread来完成一个具体的任务,主要应用于IntentService,不用时要通过quit和quitSafely终止。IntentService

继承了Service的抽象类,是一个特殊的Service,所以必须要创建它的子类才能使用IntentService。适合执行高优先级的后台任务。IntentSerrvice封装了HandlerThread和Handler。

当它第一次被启动时,它的onCreate调用,onCreate会创建一个HandlerThread,然后使用它的Looper来够着一个Handler对象mServiceHandler,然后这个对象发送的消息最终都会在HandlerThread中执行。每次启用IntentService它的onStartCommand就调用一次,在这个方法中不过是通过mServiceHandler发送intent消息而已。mServiceHandler接收到这个intent后会把intent(和外界startService(intent)中intent一样)传递给onHandlerIntent处理,处理完后通过stopSelf来停止服务。而onHandlerIntent是一个抽象类,需要我们写子类来继承它,在子类中我们要去区分多个后台任务,然后串行执行每个后台任务,每次执行都要启动一次IntentService云云。

下面来看一个实例,我们派生一个IntentService:

这里实现的是在onHandlerIntent中通过task_action来标识intent,Service通过这个标识来处理对应的intent。

总的来说,IntentService是一种拿来就用,用完就走的service,它串行执行完所有耗时任务后会通过stopSelf自动停止服务。

实例使用:​​传送门​​Android中的线程池

线程池的三大优点:

(1)重用线程池中的线程,避免因为线程的创建和销毁带来的性能开销

(2)能有效控制线程的最大并发数,减少因为线程抢占资源导致的阻塞

(3)能够对线程进行简单的管理。

ThreadPoolExecutor

ThreadPoolExecutor是线程池的真正实现,它的构造方法提供了一系列的参数来配置线程池。

上面的构造方法中的参数分别代表:核心线程数、线程池能容纳的最多线程数、非核心线程闲置的超时时长、用于指定keepAliveTime的单位、线程池的队列、线程工厂。

ThreadPoolExecutor执行任务大致遵守如下规则:

(1)如果线程池的线程数未达到最大容纳量,那么会直接启动一个核心线程来执行任务。

(2)如果线程池中的线程数已达到或者超过最大容纳量,那么任务会被插入到任务队列中排队等待执行。

(3)如果步骤2中的任务队列已满,如果这个时候线程池中的线程数未达到最大容纳量,则立刻执行一个非核心线程来执行任务。

(4)如果步骤3中线程数量已经达到最大值,那么就拒绝执行此任务。会通过ThreadPoolExecutor的rejectedExecution来通知调用者。

在AsyncTask(两个线程中的THREAD_POOL_EXECUTOR)就明显的体现了线程池的特点:

核心线程为CPU核心+1、最大容纳量为CPU核心*2+1、非核心闲置时间为1s、任务队列容量为128。

线程池的分类

Bitmap和加载Chache

因为Android对单个应用所施加的内存限制如16mb,所以我们想办法高速加载Bitmap。 实际开发中经常用到Bitmap做缓存。比较常用的缓存策略有LruChache和DiskLruChache。 前者常被用做内存缓存,后者则是存储缓存。Lru是Least Recently Used的缩写,即最近最少使用算法,它的核心思想为:当缓存快满时,会淘汰近期最少使用的缓存目标。

Bitmap的高效加载

如何加载一个Bitmap呢,BitmapFactory提供了四个方法:decodeFile、decodeResource、decodeStream、decodeByteArray,分别用于从文件系统、资源、输入流以及字节数组中加载处一个Bitmap对象。都是Android底层实现的,对应着BitmapFactory的几个native方法。

高效加载Bitmap核心思想就是使用Bitmap.Options方法加载所需尺寸的图片。因为一般ImageView的尺寸都没有图片大,所以把图片整个加载进来就显得很拖沓,所以先把图片按照一定的采样率缩小,然后加载给ImageView。这会一定程度避免程序OOM,提高了加载性能。

通过Options来缩放图片,主要用到的是inSampleSize参数,即采样率。当其大于1,比如为2时,图片宽高取1/2,像素变为原来的1/4,内存也变为1/4,比如一个1024*1024像素采用ARGB8888存储格式的图片来说,其大小为1024*1024*4=4mb,缩小后大小为1mb。所以采样率的缩放比例为1/(inSampleSize^2),比如采样率为4,则缩放比例就是1/16。一般这个数自定位2的指数。

获取采样率的步骤:

(1)将BitmapFactory.Options的inJustDecodeBounds参数设置为true并加载图片

(2)从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数。

(3)根据采样率的规则并结合目标View的大小计算inSampleSize

(4)将BitmapFactory.Options的inJustDecodeBounds参数设置为false,然后重新加载图片。

经过上述四个步骤,加载出来的是缩放后的图片。inJustDecodeBounds设置为True时,BitmapFactory只会去解析图片的原始宽/高而不会加载它,所以这个操作时轻量级的。所以有如下代码

通过上述的代码,我们使用就简便了,比如一个ImageView的大小为100*100,使用为:

除了用decodeResource还可以用其他三个方法来解析加载图片。

Android中的缓存策略 可以用LruChache和DiskLruChache结合实现一个优秀的ImageLoader。

LruChache

LruChache是一个缓存类、泛型类,它内部采用了一个LinkedHashMap以强引用的方式存储外界的缓存对象,其提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,LruChache会移除较早使用的缓存对象,然后添加新的缓存对象。下面要弄清楚强引用、弱引用、软引用

- 强引用

直接的对象应用。

- 软引用

当一个对象只有软引用存在时,系统内存不足时,此对象会被gc回收。

- 弱引用

当一个对象只有弱引用存在时,此对象随时会被gc回收。

另外LruChache线程是安全的。下面为LruChache的初始化:

上面代码中,只需提供缓存的总容量大小并重写sizeOf即可。sizeOf的作用是计算缓存对象的大小,单位和总容量一致。上例中,总容量大小为当前进程可用内存的1/8,单位为KB,而siezOf则完成了Bitmap对象大小的计算,除1024也是将单位转化为KB,特殊情况下还要重写entryRemoved方法,在这个方法中做一些资源的回收工作。

通过get、put、remove可以得到、添加、删除一个缓存对象。

LruChacha其实已经是Android源码的一部分了。DiskLruChache

DiskLruChache作用是实现存储设备缓存,即磁盘存储,它通过将缓存对象写入文件系统从而实现缓存效果。

DiskLruChache并不能通过构造方法来创建,而是通过open来创建自身。

第一个参数为磁盘缓存在文件系统中的存储路径,可以选择sd卡的缓存目录,第二个为应用的版本号,第三个为单个节点对应的数据的个数,一般设为1,第四个表示缓存总大小,当缓存超出这个大小后,会清楚一些缓存。

DiskLruChache的缓存添加

DiskLruChache的缓存添加操作是通过Editor完成的,Editor表示一个缓存对象的编辑对象。实例中,我们首先获取图片的Url所对应的key,然后根据key就可以通过edit()来获得Editor的对象,如果这个缓存正在被编辑则返回null,因为DiskLruChache不允许同时编辑一个缓存对象。之所以要把url转化为key是因为url中可能有特殊字符。一般用url的md5值作为key。

将图片的url转为key之后就可以获取Editor对象了,对于这个key来说如果当前不存在其他的Editor则edit()返回一个新的Editor对象,通过它可以得到一个文件输出流。open中因为设置了一个节点只能有一个数据,则下面的DISK_CHACHE_INDEX设为0就可以了

有了这个文件输出流,从网上-这个图片就可以通过这个输出流写入到文件系统上。实现如下:

经过上面的步骤,还必须通过Editor.commit()来提交写入操作。如果写入出现异常,则可以用abort()来退回整个操作。DiskLruChache的缓存查找

缓存查找也需要将url转化为key,然后通过DiskLruChache的get方法得到一个Snapshot对象,从Snapshot获取输入流,有了输入流就可以得到输出流,就能得到缓存的Bitmap对象了。一般不建议直接加载这个Bitmap对象,之前用过Options来缩放,但是它不支持FileInputStream,原因是FileInputStream是有序的文件流,两次的decodeStream调用影响了文件流的位置,导致第二次decodeStream时得到的是null。解决这个问题的方法是通过文件流来得到它的文件描述符,然后通过BitmapFactory.decodeFileDescriptor方法来加载一个缩放后图片。代码如下:

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:谈到云原生, 绕不开"容器化"
下一篇:容器整体性理解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~