`
chriszeng87
  • 浏览: 713748 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Anroid中的任务、进程和线程

阅读更多

转自duguguiyu的博客:http://www.cnblogs.com/duguguiyu/

 

关于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 ProcessVisible ProcessService ProcessBackground ProcessEmpty 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 函数中读取并恢复相关信息 (onCreateonStart ,也都可以处理...)。

线程

读取数据,后台处理,这些猥琐的伙计,自然少不了线程的参与。在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栈而言的...),ServiceRecordBroadcastRecordContentProviderRecordTaskRecordProcessRecord , 等等。
值得注意的,是TaskRecord,我 们一直再说,Task栈这样的概念,其实,真实的底层,并不会在TaskRecord中,维系一个Activity的栈。在 ActivityManagerService中,各个任务的Activity,都以HistoryRecord的形式,集中存放在一个 ArrayList中,每个HistoryRecord,会存放它所在TaskRecord的引用。当有一个Activity,执行完成,从概念上的 Task栈中退出,Android是通过从当前HistoryRecord位置往前扫描同一个TaskRecord的HistoryRecord来完成 的。这个设计,使得上层很多看上去逻辑很复杂的Task体系,在实现变得很统一而简明,值得称道。
ProcessRecord,是整个进程托管实现的核心, 它存放有运行在这个进程上,所有组件的信息,根据这些信息,系统有一整套的算法来决议如何处置这个进程,如果对回收算法感兴趣,可以从 ActivityManagerService的trimApplications 函数入手来看。
对于开发者来说,去了解这部分实现,主要是可以帮助理解 整个进程和任务的概念,如果觉得这块理解的清晰了,就不用去碰ActivityManagerService这个庞然大物了。

分享到:
评论
1 楼 leizuishuai201314 2012-06-11  
"而singleTask,和singleInstance,则都采取的另辟Task的蹊径"
纠正下,这句话不对,singleTask还是在当前应用程序中的当前task执行,不会重新开task,。

相关推荐

    Android_进程任务线程[参照].pdf

    Android_进程任务线程[参照].pdf

    Android 应用程序模块 应用, 任务, 进程, 和线程--千锋培训

    文档介绍了引言,任务,任务亲和力(Affinities),启动模式以及启动标记,进程,线程

    android 异步任务 Binder 线程间交互 Handler 迭代器模式

    android 线程交互 进程讲解 Handler Message AsyncTask 线程模式

    Android应用程序模型之应用程序,任务,进程,线程分析

    主要介绍了Android应用程序模型之应用程序,任务,进程,线程分析,较为详细的分析了Android应用程序模型中关于任务、进程、线程的相关概念及注意事项,需要的朋友可以参考下

    深入Android【五】——任务和进程

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

    浅谈Android 的线程和线程池的使用

    Android 中主线程交 UI 线程,主要作用是运行四大组件以及处理它们和用户的交互;子线程的作业则是执行耗时任务。 Android 中的线程形态 1、AsyncTask AsyncTask 是一种轻量级的异步任务类,可以在线程池中执行后台...

    android开发系列美文

    Android开发:android架构 Android开发:活动与任务 Android开发:进程与线程 Android开发:应用程序基础及组件 Android开发:组件生命周期 ...

    Android开发指南中文版.pdf

    Android开发指南中文版 应用程序组件 Activity和任务 进程和线程 组件生命周期 调用父类共五章

    Android开发之旅 完整版pdf

    • Android 开发之旅:进程与线程 • Android 开发之旅:组件生命周期(一) • Android 开发之旅:组件生命周期(二) • Android 开发之旅:组件生命周期(三) • Android 开发之旅:又见Hello World! • ...

    Android服务Service详解

    本章内容服务(Service)是Android系统中4个应用程序组件之一(其他的组件详见3.2节的内容)。服务主要用于两个目的:后台运行和跨进程访问。... 在线程中更新GUI组件  AIDL服务  在AIDL服务中传递复杂的数据  

    Android开发之旅

    Android开发之旅:进程与线程 7 Android开发之旅:组件生命周期(一) 8 Android开发之旅:组件生命周期(二) 9 Android开发之旅:组件生命周期(三) 10 Android 开发之旅:又见Hello World!11 Android 开发...

    Android开发指南.rar

    进程和线程 13 进程 13 线程 14 远程过程调用 14 线程安全方法 15 组件生命周期 15 Activity生命周期 15 调用父类 16 服务生命周期 19 广播接收器生命周期 20 进程与生命周期 21

    如何正确使用Android线程详解

    人脑的多任务和计算机的多任务性能差异太大导致我们在设计并行的业务逻辑之时,很容易犯错。 另一个复杂点在于线程所带来的副作用,这些副作用包括但不限于:多线程数据安全,死锁,内存消耗,对象的生命周期管理,

    Android开发案例驱动教程 配套代码

    8.4.1 Android线程应用中的问题与分析 164 8.4.2 Message和MessageQueue 169 8.4.3 Handler 169 8.4.4 Looper和HandlerThread 172 本章小结 178 第9章 Activity和Intent 179 9.1 Activity 179 9.1.1 创建...

    精通Android4

    不仅在结构上有了相应的调整,内容上更是与时俱进,增加了Android内部构件的相关知识,介绍了线程、进程、长期运行的服务、广播接收程序、闹钟管理器、设备配置变化和异步任务。关于碎片、碎片对话框、ActionBar和...

    新版Android开发教程及笔记-完整版

    Android开发教程及笔记,基础用法,Activity和任务,进程和线程

    Android中文学习资料大全

    Android中文学习资料大全 随便列几个 andbook中文版 [publish][1].pdf Android帮助文档(第二部分)开发工具.doc Android帮助文档(第一部分)...Android 应用程序模块 应用, 任务, 进程, 和线程.pdf 等等。。。不一一列出

    Android开发之旅——完整版

    • Android 开发之旅:进程与线程 • Android 开发之旅:组件生命周期(一) • Android 开发之旅:组件生命周期(二) • Android 开发之旅:组件生命周期(三) • Android 开发之旅:又见Hello World! • ...

Global site tag (gtag.js) - Google Analytics