首页编程java编程handler对应java的什么,android中handler和service的区别是什么

handler对应java的什么,android中handler和service的区别是什么

编程之家2023-10-1385次浏览

大家好,今天给各位分享handler对应java的什么的一些知识,其中也会对android中handler和service的区别是什么进行解释,文章篇幅可能偏长,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在就马上开始吧!

handler对应java的什么,android中handler和service的区别是什么

关于Handler 的这 20 个问题,你都清楚吗

Android 11 开始,AsyncTask 正式谢幕,变成了不推荐使用的 API。官方建议采用 Kotlin协程替代,或者自行实现。

事实上,无论是 AsyncTask还是协程,背后都有 Handler的功劳。无论从普及原理的角度、还是从自行实现的角度,我们都需要吃透这个 Android系统所特有的线程间通信方式Handler 机制!

handler对应java的什么,android中handler和service的区别是什么

初尝 Handler机制的时候,原以为 Handler类发挥了很大的作用。当你深入了解它的原理之后,会发现 Handler只是该机制的调用入口和回调而已,最重要的东西是 Looper 和 MessagQueue,以及不断流转的 Message。

handler对应java的什么,android中handler和service的区别是什么

本次针对 Handler机制常被提及和容易困扰的  20 个问题进行整理和回答,供大家解惑和回顾~

简述下 Handler机制的总体原理?

Looper存在哪?如何可以保证线程独有?

如何理解 ThreadLocal的作用?

主线程 Main Looper和一般 Looper的异同?

Handler或者说 Looper如何切换线程?

Looper的 loop()死循环为什么不卡死?

Looper的等待是如何能够准确唤醒的?

Message如何获取?为什么这么设计?

MessageQueue如何管理 Message?

理解 Message和 MessageQueue的异同?

Message的执行时刻如何管理?

Handler、Mesage和 Runnable的关系如何理解?

IdleHandler空闲 Message了解过吗?有什么用?

异步 Message或同步屏障了解过吗?怎么用?什么原理?

Looper和 MessageQueue、Message及 Handler的关系?

Native侧的 NativeMessageQueue和 Looper的作用是?

Native侧如何使用 Looper?

Handler为什么可能导致内存泄露?如何避免?

Handler在系统当中的应用

Android为什么不允许并发访问 UI?

1.简述下 Handler机制的总体原理?

Looper准备和开启轮循:

尚无 Message的话,调用 Native侧的 pollOnce() 进入无限等待

存在 Message,但执行时间 when 尚未满足的话,调用 pollOnce()时传入剩余时长参数进入有限等待

Looper#prepare() 初始化线程独有的 Looper 以及 MessageQueue

Looper#loop() 开启死循环读取 MessageQueue中下一个满足执行时间的 Message

Message发送、入队和出队:

Native侧如果处于无限等待的话:任意线程向 Handler 发送 Message 或 Runnable 后,Message将按照 when条件的先后,被插入 Handler持有的 Looper实例所对应的 MessageQueue 中适当的位置。MessageQueue发现有合适的 Message插入后将调用 Native侧的 wake() 唤醒无限等待的线程。这将促使 MessageQueue的读取继续进入下一次循环,此刻 Queue中已有满足条件的 Message则出队返回给 Looper

Native侧如果处于有限等待的话:在等待指定时长后 epoll_wait将返回。线程继续读取 MessageQueue,此刻因为时长条件将满足将其出队

Looper处理 Message的实现:

Looper得到 Message后回调 Message的 callback 属性即 Runnable,或依据 target 属性即 Handler,去执行 Handler的回调。

存在 mCallback 属性的话回调 Handler$Callback

反之,回调 handleMessage()

2. Looper存在哪?如何可以保证线程独有?

Looper实例被管理在静态属性 sThreadLocal 中

ThreadLocal 内部通过 ThreadLocalMap 持有 Looper,key 为 ThreadLocal实例本身,value 即为 Looper实例

每个 Thread都有一个自己的 ThreadLocalMap,这样可以保证每个线程对应一个独立的 Looper实例,进而保证 myLooper() 可以获得线程独有的 Looper

彩蛋:一个 App拥有几个 Looper实例?几个 ThreadLocal实例?几个 MessageQueue实例?几个 Message实例?几个 Handler实例

一个线程只有一个 Looper实例

一个 Looper实例只对应着一个 MessageQueue实例

一个 MessageQueue实例可对应多个 Message实例,其从 Message静态池里获取,存在 50的上限

一个线程可以拥有多个 Handler实例,其Handler只是发送和执行任务逻辑的入口和出口

ThreadLocal实例是静态的,整个进程共用一个实例。每个 Looper存放的 ThreadLocalMap均弱引用它作为 key

3.如何理解 ThreadLocal的作用?

首先要明确并非不是用来切换线程的,只是为了让每个线程方便程获取自己的 Looper实例,见 Looper#myLooper()

后续可供 Handler初始化时指定其所属的 Looper线程

也可用来线程判断自己是否是主线程

4.主线程 Main Looper和一般 Looper的异同?

区别:

Main Looper 不可 quit

主线程需要不断读取系统消息和用书输入,是进程的入口,只可被系统直接终止。进而其 Looper在创建的时候设置了不可quit的标志,而其他线程的 Looper则可以也必须手动 quit

Main Looper实例还被静态缓存

为了便于每个线程获得主线程 Looper实例,见 Looper#getMainLooper(),Main Looper实例还作为 sMainLooper 属性缓存到了 Looper类中。

相同点:

都是通过 Looper#prepare()间接调用 Looper构造函数创建的实例

都被静态实例 ThreadLocal管理,方便每个线程获取自己的 Looper实例

彩蛋:主线程为什么不用初始化 Looper?

App的入口并非 MainActivity,也不是 Application,而是 ActivityThread。

其为了 Application、ContentProvider、Activity等组件的运行,必须事先启动不停接受输入的 Looper机制,所以在 main()执行的最后将调用 prepareMainLooper()创建 Looper并调用 loop()轮循。

不需要我们调用,也不可能有我们调用。

可以说如果主线程没有创建 Looper的话,我们的组件也不可能运行得到!

5. Handler或者说 Looper如何切换线程?

Handler创建的时候指定了其所属线程的 Looper,进而持有了 Looper独有的 MessageQueue

Looper#loop()会持续读取 MessageQueue中合适的 Message,没有 Message的时候进入等待

当向 Handler发送 Message或 Runnable后,会向持有的 MessageQueue中插入 Message

Message抵达并满足条件后会唤醒 MessageQueue所属的线程,并将 Message返回给 Looper

Looper接着回调 Message所指向的 Handler Callback或 Runnable,达到线程切换的目的

简言之,向 Handler发送 Message其实是向 Handler所属线程的独有 MessageQueue插入 Message。而线程独有的 Looper又会持续读取该 MessageQueue。所以向其他线程的 Handler发送完 Message,该线程的 Looper将自动响应。

6. Looper的 loop()死循环为什么不卡死?

为了让主线程持续处理用户的输入,loop()是死循环,持续调用 MessageQueue#next() 读取合适的 Message。

但当没有 Message的时候,会调用 pollOnce() 并通过 Linux的 epoll 机制进入等待并释放资源。同时 eventFd 会监听 Message抵达的写入事件并进行唤醒。

这样可以空闲时释放资源、不卡死线程,同时能持续接收输入的目的。

彩蛋1:loop()后的处理为什么不可执行

因为 loop()是死循环,直到 quit前后面的处理无法得到执行,所以避免将处理放在 loop()的后面。

彩蛋2:Looper等待的时候线程到底是什么状态?

调用 Linux的 epoll机制进入等待,事实上 Java侧打印该线程的状态,你会发现线程处于 Runnable 状态,只不过 CPU资源被暂时释放。

7. Looper的等待是如何能够准确唤醒的?

读取合适 Message的 MessageQueue#next()会因为 Message尚无或执行条件尚未满足进行两种等的等待:

无限等待

尚无 Message(队列中没有 Message或建立了同步屏障但尚无异步 Message)的时候,调用 Natvie侧的 pollOnce() 会传入参数 -1。

Linux执行 epoll_wait() 将进入无限等待,其等待合适的 Message插入后调用 Native侧的 wake() 向唤醒 fd写入事件触发唤醒 MessageQueue读取的下一次循环

有限等待

有限等待的场合将下一个 Message 剩余时长作为参数交给 epoll_wait(),epoll将等待一段时间之后自动返回,接着回到 MessageQueue读取的下一次循环

8. Message如何获取?为什么这么设计?

享元设计模式:通过 Message的静态方法 obatin() 获取,因为该方法不是无脑地 new,而是从单链表池子里获取实例,并在 recycle() 后将其放回池子

好处在于复用 Message实例,满足频繁使用 Message的场景,更加高效

当然,缓存池存在上限  50,因为没必要无限制地缓存,这本身也是一种浪费

需要留意缓存池是静态的,也就是整个进程共用一个缓存池

9. MessageQueue如何管理 Message?

MessageQueue通过单链表管理 Message,不同于进程共用的 Message Pool,其是线程独有的

通过 Message的执行时刻 when对 Message进行排队和出队

MessageQueue除了管理 Message,还要管理空闲 Handler和同步屏障

10.理解 Message和 MessageQueue的异同?

相同点:都是通过单链表来管理 Message实例;

Message通过  obtain()和 recycle() 向单链表获取插入节点

MessageQueue通过  enqueueMessage()和 next() 向单链表获取和插入节点

区别:

Message单链表是静态的,供进程使用的缓存池

MessageQueue单链表非静态,只供 Looper线程使用

11. Message的执行时刻如何管理?

发送的 Message都是按照执行时刻 when 属性的先后管理在 MessageQueue里

延时 Message的 when等于调用的当前时刻和 delay 之和

非延时 Message的 when等于当前时刻(delay为 0)

插队 Message的 when固定为 0,便于插入队列的 head

之后 MessageQueue会根据读取的时刻和 when进行比较

将 when已抵达的出队,

尚未抵达的计算出当前时刻和目标 when的插值,交由 Native等待对应的时长,时间到了自动唤醒继续进行 Message的读取

事实上,无论上述哪种 Message都不能保证在其对应的 when时刻执行,往往都会延迟一些!因为必须等当前执行的 Message处理完了才有机会读取队列的下一个 Message。

比如发送了非延时 Message,when即为发送的时刻,可它们不会立即执行。都要等主线程现有的任务(Message)走完才能有机会出队,而当这些任务执行完 when的时刻已经过了。假使队列的前面还有其他 Message的话,延迟会更加明显!

彩蛋:. onCreate()里向 Handler发送大量 Message会导致主线程卡顿吗?

不会,发送的大量 Message并非立即执行,只是先放到队列当中而已。

onCreate()以及之后同步调用的 onStart()和 onResume()处理,本质上也是 Message。等这个 Message执行完之后,才会进行读取 Message的下一次循环,这时候才能回调 onCreate里发送的 Message。

需要说明的是,如果发送的是 FrontOfQueue将 Message插入队首也不会立即先执行,因为 onStart和 onResume是 onCreate之后同步调用的,本质上是同一个 Message的作业周期

12. Handler、Mesage和 Runnable的关系如何理解?

作为使用 Handler机制的入口, Handler是发送 Message或 Runnable的起点

发送的  Runnable本质上也是 Message,只不过作为 callback 属性被持有

Handler作为 target 属性被持有在 Mesage中,在 Message执行条件满足的时候供 Looper回调

事实上,Handler只是供 App使用 Handler机制的 API,实质来说,Message是更为重要的载体。

13. IdleHandler空闲 Message了解过吗?有什么用?

适用于期望空闲时候执行,但不影响主线程操作的任务

系统应用:

Activity destroy 回调就放在了 IdleHandler 中

ActivityThread 中 GCHandler 使用了 IdleHandler,在空闲的时候执行  GC 操作

App应用:

发送一个返回 true的 IdleHandler,在里面让某个 View不停闪烁,这样当用户发呆时就可以诱导用户点击这个 View

将某部分初始化放在 IdleHandler里不影响 Activity的启动

14.异步 Message或同步屏障了解过吗?怎么用?什么原理?

异步 Message:设置了 isAsync 属性的 Message实例

可以用异步 Handler发送

也可以调用 Message#setAsynchronous() 直接设置为异步 Message

同步屏障:在 MessageQueue的某个位置放一个 target属性为 null的 Message,确保此后的非异步 Message无法执行,只能执行异步 Message

原理:当 MessageQueue轮循 Message时候发现建立了同步屏障的时候,会去跳过其他 Message,读取下个 async的 Message并执行,屏障移除之前同步 Message都会被阻塞

应用:比如屏幕刷新 Choreographer 就使用到了同步屏障,确保屏幕刷新事件不会因为队列负荷影响屏幕及时刷新。

注意:同步屏障的添加或移除 API并未对外公开,App需要使用的话需要依赖反射机制

15. Looper和 MessageQueue、Message及 Handler的关系?

Message是承载任务的载体,在 Handler机制中贯穿始终

Handler则是对外公开的 API,负责发送 Message和处理任务的回调,是  Message的生产者

MessagQueue负责管理待处理 Message的入队和出队,是  Message的容器

Looper负责轮循 MessageQueue,保持线程持续运行任务,是  Message的消费者

彩蛋:如何保证 MessageQueue并发访问安全?

任何线程都可以通过 Handler生产 Message并放入 MessageQueue中,可 Queue所属的 Looper在持续地读取并尝试消费 Message。如何保证两者不产生死锁?

Looper在消费 Message之前要先拿到 MessageQueue 的锁,只不过没有 Message或 Message尚未满足条件的进行等待前会事先释放锁,具体在于 nativePollOnce()的调用在 synchronized方法块的外侧。

Message入队前也需先拿到 MessageQueue 的锁,而这时 Looper线程正在等待且不持有锁,可以确保 Message的成功入队。入队后执行唤醒后释放锁,Native收到 event写入后恢复 MessagQueue的读取并可以拿到锁,成功出队。

这样一种在没有 Message可以消费时执行等待同时不占着锁的机制,避免了生产和消费的死锁。

16. Native侧的 NativeMessageQueue和 Looper的作用是?

NativeMessageQueue负责连接 Java侧的 MessageQueue,进行后续的 wait 和 wake,后续将创建 wake的FD,并通过 epoll机制等待或唤醒。但并不参与管理 Java的 Message

Native侧也需要 Looper机制,等待和唤醒的需求是同样的,所以将这部分实现都封装到了 JNI的NativeMessageQueue 和 Native的 Looper中,供 Java和 Native一起使用

17. Native侧如何使用 Looper?

Looper Native部分承担了 Java侧 Looper的等待和唤醒,除此之外其还提供了 Message、MessageHandler 或 WeakMessageHandler、LooperCallback 或 SimpleLooperCallback 等 API

这些部分可供 Looper被 Native侧直接调用,比如 InputFlinger 广泛使用了 Looper

主要方法是调用 Looper构造函数或 prepare创建 Looper,然后通过 poll开始轮询,接着 sendMessage 或 addEventFd,等待 Looper的唤醒。使用过程和 Java的调用思路类似

18. Handler为什么可能导致内存泄露?如何避免?

持有 Activity实例的内名内部类或内部类的生命周期应当和 Activity保持一致,否则产生内存泄露的风险。

如果 Handler使用不当,将造成不一致,表现为:匿名内部类或内部类写法的 Handler、Handler$Callback、Runnable,或者Activity结束时仍有活跃的 Thread线程或 Looper子线程

具体在于:异步任务仍然活跃或通过发送的 Message尚未处理完毕,将使得内部类实例的生命周期被错误地延长。造成本该回收的 Activity实例被别的 Thread 或 Main Looper 占据而无法及时回收(活跃的 Thread或静态属性 sMainLooper是 GC Root对象)

建议的做法:

无论是 Handler、Handler$Callback还是 Runnable,尽量采用静态内部类+弱引用的写法,确保尽管发生不当引用的时候也可以因为弱引用能清楚持有关系

另外在 Activity销毁的时候及时地终止 Thread、停止子线程的 Looper或清空 Message,确保彻底切断 Activity经由 Message抵达 GC Root的引用源头(Message清空后会其与 Handler的引用关系,Thread的终止将结束其 GC Root的源头)

注意:静态的 sThreadLocal实例不持有存放 Looper实例的 ThreadLocalMap,而是由 Thread持有。从这个角度上来讲,Looper会被活跃的 GC Root Thread持有,进而也可能导致内存泄露。

彩蛋:网传的 Handler$Callback方案能否解决内存泄露?

不能。

Callback采用内部类或匿名内部类写法的话,默认持有 Activity的引用,而 Callback被 Handler持有。这最终将导致 Message-> Handler-> Callback-> Activity的链条仍然存在。

19. Handler在系统当中的应用

特别广泛,比如:

Activity生命周期的管理

屏幕刷新

HandlerThread、IntentService

AsyncTask等。

主要利用 Handler的切换线程、主线程异步 Message的重要特性。注意:Binder线程非主线程,但很多操作比如生命周期的管理都要回到主线程,所以很多 Binder调用过来后都要通过 Handler切换回主线程执行后续任务,比如 ActviityThread$H就是 extends Handler。

20. Android为什么不允许并发访问 UI?

Android中 UI非线程安全,并发访问的话会造成数据和显示错乱。

但此限制的检查始于ViewRootImpl#checkThread(),其会在刷新等多个访问 UI的时机被调用,去检查当前线程,非主线程的话抛出异常。

而 ViewRootImpl的创建在 onResume()之后,也就是说如果在 onResume()执行前启动线程访问 UI的话是不会报错的,这点需要留意!

彩蛋:onCreate()里子线程更新 UI有问题吗?为什么?

不会。

因为异常的检测处理在 ViewRootImpl中,该实例的创建和检测在 onResume()之后进行。

rxjava能代替handler吗

RxJava好在哪换句话说,『同样是做异步,为什么人们用它,而不用现成的AsyncTask/Handler/XXX/?』一个词:简洁。异步操作很关键的一点是程序的简洁性,因为在调度过程比较复杂的情况下,异步代码经常会既难写也难被读懂。Android创造的AsyncTask和Handler,其实都是为了让异步代码更加简洁。RxJava的优势也是简洁,但它的简洁的与众不同之处在于,随着程序逻辑变得越来越复杂,它依然能够保持简洁。假设有这样一个需求:界面上有一个自定义的视图imageCollectorView,它的作用是显示多张图片,并能使用addImage(Bitmap)方法来任意增加显示的图片。现在需要程序将一个给出的目录数组File[]folders中每个目录下的png图片都加载出来并显示在imageCollectorView中。需要注意的是,由于读取图片的这一过程较为耗时,需要放在后台执行,而图片的显示则必须在UI线程执行。常用的实现方式有多种,我这里贴出其中一种:newThread(){@Overridepublicvoidrun(){super.run();for(Filefolder:folders){File[]files=folder.listFiles();for(Filefile:files){if(file.getName().endsWith(".png")){finalBitmapbitmap=getBitmapFromFile(file);getActivity().runOnUiThread(newRunnable(){@Overridepublicvoidrun(){imageCollectorView.addImage(bitmap);}});}}}}}.start();而如果使用RxJava,实现方式是这样的:Observable.from(folders).flatMap(newFunc1>(){@OverridepublicObservablecall(Filefile){returnObservable.from(file.listFiles());}}).filter(newFunc1(){@OverridepublicBooleancall(Filefile){returnfile.getName().endsWith(".png");}}).map(newFunc1(){@OverridepublicBitmapcall(Filefile){returngetBitmapFromFile(file);}}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(newAction1(){@Overridepublicvoidcall(Bitmapbitmap){imageCollectorView.addImage(bitmap);}});那位说话了:『你这代码明明变多了啊!简洁个毛啊!』大兄弟你消消气,我说的是逻辑的简洁,不是单纯的代码量少(逻辑简洁才是提升读写代码速度的必杀技对不?)。观察一下你会发现,RxJava的这个实现,是一条从上到下的链式调用,没有任何嵌套,这在逻辑的简洁性上是具有优势的。当需求变得复杂时,这种优势将更加明显(试想如果还要求只选取前10张图片,常规方式要怎么?如果有这样那样的要求呢?再试想,在这一大堆需求实现完两个月之后需要改功能,当你翻回这里看到自己当初写下的那一片迷之缩进,你能保证自己将迅速看懂,而不是对着代码重新捋一遍思路?)。另外,如果你的IDE是AndroidStudio,其实每次打开某个Java文件的时候,你会看到被自动Lambda化的预览,这将让你更加清晰地看到程序逻辑:Observable.from(folders).flatMap((Func1)(folder)->{Observable.from(file.listFiles())}).filter((Func1)(file)->{file.getName().endsWith(".png")}).map((Func1)(file)->{getBitmapFromFile(file)}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe((Action1)(bitmap)->{imageCollectorView.addImage(bitmap)});如果你习惯使用Retrolambda,你也可以直接把代码写成上面这种简洁的形式。而如果你看到这里还不知道什么是Retrolambda,我不建议你现在就去学习它。原因有两点:1.Lambda是把双刃剑,它让你的代码简洁的同时,降低了代码的可读性,因此同时学习RxJava和Retrolambda可能会让你忽略RxJava的一些技术细节;2.Retrolambda是Java6/7对Lambda表达式的非官方兼容方案,它的向后兼容性和稳定性是无法保障的,因此对于企业项目,使用Retrolambda是有风险的。所以,与很多RxJava的推广者不同,我并不推荐在学习RxJava的同时一起学习Retrolambda。事实上,我个人虽然很欣赏Retrolambda,但我从来不用它。

android中handler和service的区别是什么

任务、进程和线程

关于Android中的组件和应用,之前涉及,大都是静态的概念。而当一个应用运行起来,就难免会需要关心进程、线程这样的概念。在Android中,组件的动态运行,有一个最与众不同的概念,就是Task,翻译成任务,应该还是比较顺理成章的。

Task的介入,最主要的作用,是将组件之间的连接,从进程概念的细节中剥离出来,可以以一种不同模型的东西进行配置,在很多时候,能够简化上层开发人员的理解难度,帮助大家更好的进行开发和配置。

任务

在SDK中关于Task(guide/topics/fundamentals.html#acttask),有一个很好的比方,说,Task就相当于应用(application)的概念。在开发人员眼中,开发一个Android程序,是做一个个独门独户的组件,但对于一般用户而言,它们感知到的,只是一个运行起来的整体应用,这个整体背后,就是Task。

Task,简单的说,就是一组以栈的模式聚集在一起的Activity组件集合。它们有潜在的前后驱关联,新加入的Activity组件,位于栈顶,并仅有在栈顶的Activity,才会有机会与用户进行交互。而当栈顶的 Activity完成使命退出的时候,Task会将其退栈,并让下一个将跑到栈顶的Activity来于用户面对面,直至栈中再无更多 Activity,Task结束。

事件 Task栈

点开Email应用,进入收件箱(Activity A) A

选中一封邮件,点击查看详情(Activity B) AB

点击回复,开始写新邮件(Activity C) ABC

写了几行字,点击选择联系人,进入选择联系人界面(Activity D) ABCD

选择好了联系人,继续写邮件 ABC

写好邮件,发送完成,回到原始邮件 AB

点击返回,回到收件箱 A

退出Email程序 null

如上表所示,是一个实例。从用户从进入邮箱开始,到回复完成,退出应用整个过程的Task栈变化。这是一个标准的栈模式,对于大部分的状况,这样的Task模型,足以应付,但是,涉及到实际的性能、开销等问题,就会变得残酷许多。比如,启动一个浏览器,在Android中是一个比较沉重的过程,它需要做很多初始化的工作,并且会有不小的内存开销。但与此同时,用浏览器打开一些内容,又是一般应用都会有的一个需求。设想一下,如果同时有十个运行着的应用(就会对应着是多个Task),都需要启动浏览器,这将是一个多么残酷的场面,十个Task栈都堆积着很雷同的浏览器Activity,是多么华丽的一种浪费啊。于是你会有这样一种设想,浏览器Activity,可不可以作为一个单独的Task而存在,不管是来自那个Task的请求,浏览器的Task,都不会归并过去。这样,虽然浏览器Activity本身需要维系的状态更多了,但整体的开销将大大的减少,这种舍小家为大家的行为,还是很值得歌颂的。

如此值得歌颂的行为,Android当然会举双手支持的。在Android中,每一个Activity的Task模式,都是可以由Activity提供方(通过配置文件...)和Activity使用方(通过Intent中的flag信息...)进行配置和选择。当然,使用方对Activity的控制力,是限定在提供方允许的范畴内进行,提供方明令禁止的模式,使用方是不能够越界使用的。

在SDK中(guide/topics/fundamentals.html#acttask),将两者实现Task模式配置的方式,写的非常清晰了,我再很絮叨挑选一些来解释一下(完整可配置项,一定要看SDK,下面只是其中常用的若干项...)。提供方对组件的配置,是通过配置文件(Manifest)<activity>项来进行的,而调用方,则是通过Intent对象的flag进行抉择的。相对于标准的Task栈的模式,配置的主要方向有两个:一则是破坏已有栈的进出规则,或样式;另一则是开辟新Task栈完成本应在同一Task栈中完成的任务。

对于应用开发人员而言,<activity>中的launchMode属性,是需要经常打交道的。它有四种模式:"standard","singleTop","singleTask","singleInstance"。

standard模式,是默认的也是标准的Task模式,在没有其他因素的影响下,使用此模式的Activity,会构造一个Activity的实例,加入到调用者的Task栈中去,对于使用频度一般开销一般什么都一般的Activity而言,standard模式无疑是最合适的,因为它逻辑简单条理清晰,所以是默认的选择。

而singleTop模式,基本上于standard一致,仅在请求的Activity正好位于栈顶时,有所区别。此时,配置成singleTop的Activity,不再会构造新的实例加入到Task栈中,而是将新来的Intent发送到栈顶Activity中,栈顶的Activity可以通过重载onNewIntent来处理新的Intent(当然,也可以无视...)。这个模式,降低了位于栈顶时的一些重复开销,更避免了一些奇异的行为(想象一下,如果在栈顶连续几个都是同样的Activity,再一级级退出的时候,这是怎么样的用户体验...),很适合一些会有更新的列表Activity展示。一个活生生的实例是,在 Android默认提供的应用中,浏览器(Browser)的书签Activity(BrowserBookmarkPage),就用的是singleTop。

singleTop模式,虽然破坏了原有栈的逻辑(复用了栈顶,而没有构造新元素进栈...),但并未开辟专属的Task。而singleTask,和singleInstance,则都采取的另辟Task的蹊径。标志为singleTask的Activity,最多仅有一个实例存在,并且,位于以它为根的Task中。所有对该Activity的请求,都会跳到该Activity的Task中展开进行。singleTask,很象概念中的单件模式,所有的修改都是基于一个实例,这通常用在构造成本很大,但切换成本较小的Activity中。在Android源码提供的应用中,该模式被广泛的采用,最典型的例子,还是浏览器应用的主Activity(名为Browser...),它是展示当前tab,当前页面内容的窗口。它的构造成本大,但页面的切换还是较快的,于 singleTask相配,还是挺天作之合的。

相比之下,singleInstance显得更为极端一些。在大部分时候singleInstance与singleTask完全一致,唯一的不同在于,singleInstance的Activity,是它所在栈中仅有的一个Activity,如果涉及到的其他Activity,都移交到其他Task中进行。这使得singleInstance的Activity,像一座孤岛,彻底的黑盒,它不关注请求来自何方,也不计较后续由谁执行。在Android默认的各个应用中,很少有这样的Activity,在我个人的工程实践中,曾尝试在有道词典的快速取词Activity中采用过,是因为我觉得快速取词入口足够方便(从notification中点选进入),并且会在各个场合使用,应该做得完全独立。

除了launchMode可以用来调配Task,<activity>的另一属性taskAffinity,也是常常被使用。taskAffinity,是一种物以类聚的思想,它倾向于将taskAffinity属性相同的Activity,扔进同一个Task中。不过,它的约束力,较之launchMode而言,弱了许多。只有当<activity>中的allowTaskReparen ting设置为true,抑或是调用方将Intent的flag添加FLAG_ACTIVITY_NEW_TASK属性时才会生效。如果有机会用到Android的Notification机制就能够知道,每一个由notification进行触发的Activity,都必须是一个设成FLAG_ACTIVITY_NEW_TASK的Intent来调用。这时候,开发者很可能需要妥善配置taskAffinity属性,使得调用起来的Activity,能够找到组织,在同一taskAffinity的Task中进行运行。

进程

在大多数其他平台的开发中,每个开发人员对自己应用的进程模型都有非常清晰的了解。比如,一个控制台程序,你可以想见它从main函数开始启动一个进程,到 main函数结束,进程执行完成退出;在UI程序中,往往是有一个消息循环在跑,当接受到Exit消息后,退出消息循环结束进程。在该程序运行过程中,启动了什么进程,和第三方进程进行通信等等操作,每个开发者都是心如明镜一本帐算得清清楚楚。进程边界,在这里,犹如国界一般,每一次穿越都会留下深深的印迹。

在Android程序中,开发人员可以直接感知的,往往是Task而已。倍感清晰的,是组件边界,而进程边界变得难以琢磨,甚至有了进程托管一说。Android中不但剥夺了手工锻造内存权力,连手工处置进程的权责,也毫不犹豫的独占了。

当然,Android隐藏进程细节,并不是刻意为之,而是自然而然水到渠成的。如果,我们把传统的应用称为面向进程的开发,那么,在Android中,我们做得就是面向组件的开发。从前面的内容可以知道,Android组件间的跳转和通信,都是在第三方介入的前提下进行,正由于这种介入,使得两个组件一般不会直接发生联系(于Service的通信,是不需要第三方介入的,因此Android把它全部假设成为穿越进程边界,统一基于RPC来通信,这样,也是为了掩盖进程细节...),其中是否穿越进程边界也就变得不重要。因此,如果这时候,还需要开发者关注进程,就会变得很奇怪,很费解,干脆,Android将所有的进程一并托管去了,上层无须知道进程的生死和通信细节。

在Android的底层,进程构造了底部的一个运行池,不仅仅是Task中的各个Activity组件,其他三大组件Service、Content Provider、Broadcast Receiver,都是寄宿在底层某个进程中,进行运转。在这里,进程更像一个资源池(概念形如线程池,上层要用的时候取一个出来就好,而不关注具体取了哪一个...),只是为了承载各个组件的运行,而各个组件直接的逻辑关系,它们并不关心。但我们可以想象,为了保证整体性,在默认情况下,Android肯定倾向于将同一Task、同一应用的各个组件扔进同一个进程内,但是当然,出于效率考虑,Android也是允许开发者进行配置。

在Android中,整体的<application>(将影响其中各个组件...)和底下各个组件,都可以设置<process>属性,相同<process>属性的组件将扔到同一个进程中运行。最常见的使用场景,是通过配置<application>的process属性,将不同的相关应用,塞进一个进程,使得它们可以同生共死。还有就是将经常和某个Service组件进行通信的组件,放入同一个进程,因为与Service通信是个密集操作,走的是RPC,开销不小,通过配置,可以变成进程内的直接引用,消耗颇小。

除了通过<process>属性,不同的组件还有一些特殊的配置项,以Content Provider为例(通过<provider>项进行配置...)。<provider>项有一个mutiprocess的属性,默认值为false,这意味着Content Provider,仅会在提供该组件的应用所在进程构造一个实例,第三方想使用就需要经由RPC传输数据。这种模式,对于构造开销大,数据传输开销小的场合是非常适用的,并且可能提高缓存的效果。但是,如果是数据传输很大,抑或是希望在此提高传输的效率,就需要将mutiprocess设置成true,这样,Content Provider就会在每一个调用它的进程中构造一个实例,避免进程通信的开销。

既然,是Android系统帮助开发人员托管了进程,那么就需要有一整套纷繁的算法去执行回收逻辑。Android中各个进程的生死,和运行在其中的各个组件有着密切的联系,进程们依照其上组件的特点,被排入一个优先级体系,在需要回收时,从低优先级到高优先级回收。Android进程共分为五类优先级,分别是:Foreground Process, Visible Process, Service Process, Background Process, Empty Process。顾名思义不难看出,这说明,越和用户操作紧密相连的,越是正与用户交互的,优先级越高,越难被回收。具体详情,参见:guide/topics/fundamentals.html#proclife。

有了优先级,还需要有良好的回收时机。回收太早,缓存命中概率低可能引起不断的创造进程销毁进程,池的优势荡然无存;回收的太晚,整体开销大,系统运行效率降低,好端端的法拉利可能被糟蹋成一枚QQ老爷车。Android的进程回收,最重要的是考量内存开销,以及电量等其他资源状况,此外每个进程承载的组件数量、单个应用开辟的进程数量等数量指标,也是作为衡量的一个重要标识。另外,一些运行时的时间开销,也被严格监控,启动慢的进程会很被强行kill掉。Android会定时检查上述参数,也会在一些很可能发生进程回收的时间点,比如某个组件执行完成后,来做回收的尝试。

从用户体验角度来看,Android的进程机制,会有很可喜的一面,有的程序启动速度很慢,但是在资源充沛的前提下,你反复的退出再使用,则启动变得极其快速(进程没死,只是从后台弄到了前台),这就是拜进程托管所赐的。当然,可喜的另一面就是可悲了,Android的托管算法,还时不时的展现其幼稚的一面,明明用户已经明显感觉到操作系统运行速度下降了,打开任务管理器一看,一票应用还生龙活虎的跳跃着,必须要手动帮助它们终结生命找到坟墓,这使得任务管理器基本成为Android的装机必备软件。

从开发角度上来看,Android这套进程机制,解放了开发者的手脚。开发人员不需要处心积虑的构造一个后台进程偷偷默默监听某个时间,并尝试用各种各样的守护手段,把自己的进程锻造的犹如不死鸟一辉一般,进程生死的问题,已经原理了普通开发人员需要管理的范畴内。但同时,于GC和人肉内存管理的争议一样,所有开发人员都不相信算法能比自己做得效率更高更出色。但我一直坚信一点,所有效率的优势都会随着算法的不断改良硬件的不断提升而消失殆尽,只有开发模式的简洁不会随时间而有任何变化。

组件生命周期

任何架构上的变化,都会引起上层开发模式的变化,Android的进程模型,虽然使开发者不再需要密切关注进程的创建和销毁的时机,但仍然需要关注这些时间点对组件的影响。比如,你可能需要在进程销毁之前,将写到内存上的内容,持久化到硬盘上,这就需要关注进程退出前发生的一些事件。

在Android中,把握这些时间点,就必须了解组件生命周期(Components Lifecycles)。所谓组件的生命在周期,就是在组件在前后台切换、被用户创建退出、被系统回收等等事件发生的时候,会有一些事件通知到对应组件上,开发人员可以选择性的处理这些事件在对应的时间点上来完成一些附加工作。

除Content Provider,其他组件都会有生命周期的概念,都需要依照这个模型定时定点处理一些状况,全部内容参见:guide/topics/fundamentals.html#lcycles。在这里,擒贼先擒王,还是拿Activity出来作楷模。

继续偷图,来自SDK。一个自然的Activity生命旅途,从onCreate开始,到onDestroy消亡。但月有阴晴圆缺组件有祸福旦夕,在系统需要的时候且组件位于后台时,所在的进程随时可能为国捐躯被回收,这就使得知道切入后台这个事情也变得很重要。

当组件进入栈顶,与用户开始交互,会调用onResume函数,类似,当退出栈顶,会有onPause函数被呼唤。onResume和onPause可以处理很多事情,最常规的,就是做一些文件或设置项的读写工作。因为,在该组件不再前台运行的时候,可能别的组件会需要读写同样一份文件和设置,如果不再onResume做刷新工作,用的可能就是一份脏数据了(当然,具体情况,还需要具体分析,如果文件不会被多头读写,可以放到onCreate里面去做读工作)。

除了前述切入后台会被其他组件骚扰的问题,另外,死无定因也是件很可怕的事情。在Android中,组件都有两种常见的死法,一种是自然消亡,比如,栈元素ABC,变成AB了,C组件就自然消亡了。这种死发轻如鸿毛,不需要额外关心。但另一种情况,就是被系统回收,那是死的重如泰山,为国捐躯嘛。

但这种捐躯的死法,对用户来说,比较费解。想象一下,一款游戏,不能存盘,你一直玩啊玩,三天三夜没合眼,这时候你mm打来电话鼓励一下,你精神抖擞的准备再接再厉,却发现你的游戏进程,在切入后台之后,被系统回收了,一夜回到解放前三天努力成为一场泡影,你会不会想杀做游戏的人,会不会会不会会不会,一定会嘛。这时候,如果没有Activity生命周期这码事,游戏程序员一定是被冤死的,成了Android的替罪羊。但是,Android的组件是有生命周期的,如果真的发生这样情况,不要犹豫,去杀开发的程序员吧。

为了逃生,程序员们有一块免死金牌,那就是Android的state机制。所谓state,就是开发人员将一些当前运行的状态信息存放在一个Bundle对象里面,这是一个可序列化键值对集合。如果该Activity组件所处的进程需要回收,Android核心会将其上Activity组件的Bundle对象持久化到磁盘上,当用户回到该Activity时候,系统会重新构造该组件,并将持久化到磁盘上的Bundle对象恢复。有了这样的持久化的状态信息,开发人员可以很好的区分具体死法,并有机会的使得死而复生的Activity恢复到死前状态。开发者应该做的,是通过onSaveInstanceState函数把需要维系的状态信息(在默认的状态下,系统控件都会自己保存相关的状态信息,比如TextView,会保存当前的Text信息,这都不需要开发人员担心...),写入到Bundle对象,然后在onRestoreInstanceState函数中读取并恢复相关信息(onCreate,onStart,也都可以处理...)。

线程

读取数据,后台处理,这些猥琐的伙计,自然少不了线程的参与。在Android核心的调度层面,是不屑于考量线程的,它关注的只有进程,每一个组件的构造和处理,都是在进程的主线程上做的,这样可以保证逻辑的足够简单。多线程,往往都是开发人员需要做的。

Android的线程,也是通过派生Java的Thread对象,实现Run方法来实现的。但当用户需要跑一个具有消息循环的线程的时候,Android有更好的支持,来自于Handler和Looper。Handler做的是消息的传送和分发,派生其handleMessage函数,可以处理各种收到的消息,和win开发无异。Looper的任务,则是构造循环,等候退出或其他消息的来临。在Looper的SDK页面,有一个消息循环线程实现的标准范例,当然,更为标准的方式也许是构造一个HandlerThread线程,将它的Looper传递给Handler。

在Android中,Content Provider的使用,往往和线程挂钩,谁让它和数据相关呢。在前面提到过,Content Provider为了保持更多的灵活性,本身只提供了同步调用的接口,而由于异步对Content Provider进行增删改查是一个常做操作,Android通过AsyncQueryHandler对象,提供了异步接口。这是一个Handler的子类,开发人员可以调用startXXX方法发起操作,通过派生onXXXComplete方法,等待执行完毕后的回调,从而完成整个异步调用的流程,十分的简约明了。

实现

整个任务、进程管理的核心实现,尽在ActivityManagerService中。上一篇说到,Intent解析,就是这个ActivityManagerService来负责的,其实,它是一个很名不副实的类,因为虽然名为Activity的Manager Service,但它管辖的范围,不只是Activity,还有其他三类组件,和它们所在的进程。

在ActivityManagerService中,有两类数据结构最为醒目,一个是ArrayList,另一个是HashMap。 ActivityManagerService有大量的ArrayList,每一个组件,会有多个ArrayList来分状态存放。调度工作,往往就是从一个ArrayList里面拿出来,找个方法调一调,然后扔到另一个ArrayList里面去,当这个组件没对应的ArrayList放着的时候,说明它离死不远了。HashMap,是因为有组件是需要用名字或Intent信息做定位的,比如Content Provider,它的查找,都是依据Uri,有了HashMap,一切都顺理成章了。

ActivityManagerService用一些名曰xxxRecord的数据结构,来表达各个存活的组件。于是就有了,HistoryRecord(保存Activity信息的,之所以叫History,是相对Task栈而言的...),ServiceRecord,BroadcastRecord,ContentProviderRecord,TaskRecord,ProcessRecord,等等。

值得注意的,是TaskRecord,我们一直再说,Task栈这样的概念,其实,真实的底层,并不会在TaskRecord中,维系一个Activity的栈。在ActivityManagerService中,各个任务的Activity,都以HistoryRecord的形式,集中存放在一个 ArrayList中,每个HistoryRecord,会存放它所在TaskRecord的引用。当有一个Activity,执行完成,从概念上的 Task栈中退出,Android是通过从当前HistoryRecord位置往前扫描同一个TaskRecord的HistoryRecord来完成的。这个设计,使得上层很多看上去逻辑很复杂的Task体系,在实现变得很统一而简明,值得称道。

ProcessRecord,是整个进程托管实现的核心,它存放有运行在这个进程上,所有组件的信息,根据这些信息,系统有一整套的算法来决议如何处置这个进程,如果对回收算法感兴趣,可以从ActivityManagerService的trimApplications函数入手来看。

对于开发者来说,去了解这部分实现,主要是可以帮助理解整个进程和任务的概念,如果觉得这块理解的清晰了,就不用去碰ActivityManagerService这个庞然大物了。

================

这是转载的,感觉比一楼说的好

=============

一下是自己的

服务(Service)需要配置才能使用

线程使用就不说了

hanler,是用来进行消息队列的一个东东,handler可以用来更新控件的显示,以及线程之间的通信,

service只能启动后台,属于应用组件之一

END,本文到此结束,如果可以帮助到大家,还望关注本站哦!

java项目中为什么要写日志,java输出日志是什么有什么用java默认方法是什么 java默认方法的修饰符是什么