android的四大控件
Activity的生命周期
fragment的生命周期
jin使用语法
进程,线程间的通信
自定义view
常见的异常
有哪些设计模式
手写单例模式
有哪些排序方法
手写冒泡法排序
手写链表逆序
思维题怎么把一个数组中的数分正负两列,他问的不对啊….他问的是分正负两列,但讲解的是指针分别指向头和尾然后数据判断。。。
还有很多private,public ,protected的权限,0.62是什么类型的数据,许多数据结构的问题,简历上的问题。
别人的面经:
百度、阿里、腾讯(BAT)无疑是国内互联网行业的三巨头,虽然业务侧重各有不同,但是在移动互联网时代,无线入口是必争之地,三巨头也各自发力,招兵买马,大力推动移动端产品研发,以抢占更多智能终端用户,对很多想加入百度的同学来说,是很好的机会,但是很多应聘者对BAT内部不是很了解,以致不知道如何准备,需要注意哪些事项,以获得这些互联网巨头们的青睐。
橙GG今天先从百度Android客户端研发岗位开始,给大家介绍一些面试相关的注意点,橙GG在百度工作数年,面试过很多应聘者,不少应聘者本身能力还是不错,但是明显感觉在某些关键技术点上欠缺准确和深度的了解,以致最终与百度失之交臂。
从百度招聘的理念上来讲,比较讲究实用主义,和传统的大公司、大外企不一样,百度对学位和专业的包容力很强,只要能力足够,均可以破格录取。基本上,百度在人才筛选上重视以下几点:
1.扎实的程序设计基础
技术基础是任何业务及系统实现的根基,不管你到百度是做音乐,地图,视频还是移动云,都离不开扎实的技术基础,对于Android客户端研发职位来说,通常会从以下几个方面来考察基础。
1/1.基本语言开发能力,如Java的多线程知识,如何创建使用线程,extend Thread 和implement Runnable差异在哪里,线程间的同步,override和overload,或者问static关键字有哪些用法,final的意义等等,总之会围绕非常常用和重要的一些基础知识来判断应聘者基本的语言能力,这部分如果回答的比较糟糕,那就基本没后戏了,我面试过程中发现,好多应聘者虽然对用过的东西比较熟,但是明显发现没有系统学过,看面试宝典看得不够透彻,稍微再问多一点,就完全没概念了,例如问static关键字的用法,静态变量是一定会回答的,但是面试官再跟进一句,在我们系统中,static用得太多,会有什么问题,使用静态变量要注意什么,好多面试者就No idea了。
1/2 Android相关基础,既然做Android研发,这块当然是重点,七成面试内容,就是和Android相关的。橙GG 08年开始写Android,当时写Android的人很少很少,如今Android研发人员一大堆,但是优秀的Android工程师还是有限,如何来鉴别?Yes,就是看这个工程师对基础、要点理解的到不到位,几乎所有人都能回答Android的生命周期,每次问到这个问题,就像小时候学英语对话,how are you, fine thankyou and you, 眼睛不眨一下就把结论背出来了,不管自己今天是不是伤风感冒,女朋友吹了,反正就是fine thank youand you? 面试官再跟进一下,每个生命周期的节点有什么意义,到某个周期节点后,我们不应该做什么,好多就不清楚了,要是再让面试者结合
Android几种不同类别的进程做个对应的分析阐述,抓瞎的就更多了。跟这个类似的是问Activity的启动模式,大多数人都能回答出来,再要他们仔细阐述每种启动模式的意义,很多就只能死背了,一听就听出来,没有自己写代码测过、用过。基本上,对基本组件的一些使用场景和方法,一定会问到,例如会问问Service的使用,怎么使用,两种使用方式的差别,适合什么样的场景,如何尽量避免Service被杀。或者ContentProvider什么意义,和数据库存储什么关系。要做一个开机自启动的网络请求工作,怎么做,等等,这些都算基础。
提高一点的,会问performance相关以及一些深度一点的问题了,事实上这部分的回答,很可能影响到你是T4还是T5 J,例如,怎么检查应用是否有内存泄露问题,如果有泄露,可以用什么工具什么方法排查。我们写代码的时候要注意哪些方面,以避免出现内存泄露。再比如,联系人列表有好多人,有什么方法来改善滑动性能。 Handler、thread、message、messageQueue、looper具体什么含义,以及他们之间的关系,因为这是一个最常用的消息处理模型。Weakreference和softReference什么意思,使用场景在什么地方。
1/3.设计模式,这一块稍微高一点的职位都会问到,问一些常见的设计模式,单例类如何实现,单例类创建的单例怎么释放(你思考过没)?适配器模式的意义在哪里,为什么我们要适配器模式?观察者模式的UML模型,能结合Android讲一下观察者模式吗?再提高一点,会直接问系统设计的思路和设计模式的综合应用,例如带业务类别和优先级区分的HTTP请求线程池,如何实现,等等。这一块是提高部分,基础岗位如果没有回答出这些问题,只要态度诚恳、好学,基本上也不影响offer,百度还是有一定的培养心的。
1/4.算法,可恶的算法,某些世界著名的企业,例如微X,亚马X(绝对不是亚马碟),谷X,IBX,不管什么部门,有事没事就问你一些算法,什么分治算法,回溯算法,据说这些装X企业的员工,一年到头写不上千儿八百行代码,没事每天就折腾这些算法,以便在应试者面前显得很牛X的样子。算法这个事,个人觉得很有用,但是要看部门,看工作内容,百度还是比较有节操的,做基本应用开发的,很少问一些乱七八糟的算法相关的问题,实际上在互联网时代,真的需要用到分治算法的时候,我想大家这么聪明,借着百度文库现学现用,掌握不难。做Android客户端,最多问问一些常用的排序算法,基本就足够了。
2.相关的项目经验
目前公司大多都是实用主义,一般search简历的时候,尽量会找有相关业务经验的,比如做视频的,那么最好应聘者也是做视频业务的,最最好的是做完全一样的项目的,最最最好的是来自于竞品团队的(一举两得啊,哈哈,360和百度喜欢互挖,这个是业内都知道的秘密)。看项目经验,无非考虑到以下几点:
2/1,上手快,业务理解快。天下应用一大抄,界面变化再多,产品理念差异再大,来往和微信也就TM一回事,百度地图和高德地图也是一回事,做过相同的东西,对相关技术比较清楚,对业务理解也比较清楚,所以一般入手都比较快,没节操的程序员,可以直接把前一家公司的某块代码直接拷贝过来,没节操的二货程序员,连特么的copyright申明或者author邮件地址都可能没改,出活快不快?
上面都是扯淡,Hint在哪里?面试前,你一定要认认真真总结自己做的项目,对业务和使用到的主要技术,要很清楚,你来做音乐客户端,多线程下载、断点续传你都没做过,呃~好吧,只能说我们产品理念不同,世界观不同~~
2/2,快速解决类似问题。各位做大型项目基本都“挖过坑,埋过人,跳过陷阱撞过门”吧,这些经验相当宝贵,产品姐姐妹妹们冒出个想法很正常,R&D们要支持,谁让人家那么漂亮?做呗,于是乎,“栽栽愣愣来编程,坎坎坷坷全是坑,坑里TM全是水,水里尼玛还有钉儿”,悲催不?这时候,做过相关的项目的,可能就预知问题所在,或者能快速定位问题,“小心翼翼拔出钉儿,拼劲全力爬出坑,栽楞走过坎坷路,回头微笑看人生”,牛X不?
上面还是扯淡,Hint在哪里?面试前,你总结过你做的这些项目难点在哪里,重点在哪里了吗?一般的面试官都会问你遇到的难点,或者项目中的难点和陷阱,你要对这些很清楚。
Lastbut not least,别装X,装X容易遭雷劈,相关项目经验如果和目标职位基本一样的话,要实事求是,装X会死得很惨,因为一般面试官的人,都是比较了解项目,很有可能在爬坑过程中。我面过一个candidate,我们当时正在做DLNA,那货告诉我很懂DLNA,我就问了一个DLNA controller发给render的message,出现丢失和延迟的原因及如何优化,那货死得很惨,好吧,是他逼我出手的~~
3.踏实的工作态度
很多时候,老实说,项目的七成工作是属于基建,拼的不是聪明,而是踏实细心,对于“角色码农”来说,不需要牛X的突破能力,也不需要牛X的隔人暴扣,“踏踏实实投好篮,认认真真抢好板”,才是王道,一个球队五个科比,未必能进得了季后赛,“角色”很重要。所以,踏实认真,肯做事的态度,在互联网行业还是非常受欢迎的,老罗5.20锤子手机发布会上都煽情说,“我们不是为了输赢,为了认真”,认真,踏实认真!同样百度也是喜欢踏实干活的人,面试者除了展现能力和自信,也一定要控制好自己,不要表现的浮在上面,空杯心态,这个最好。所以,面试官可能会问你一些让你不爽的问题,或者你认为是刁难的问题,或者让你回答很基础的问题,都要耐心,别急躁,别骄傲。
4.思路清晰,有较强解决问题能力
谈到思路,你是不是想到什么“美国有多少条公路?”,“井盖为什么是圆的?”,“带两根一米长的模板怎么过宽一米二的矩形护城河?”,“你二大爷他七大姑的三姨你叫什么?”??叫去你大爷的,这种two hundred andfifty的问题,据说也只有著名的世界五百强XX企业年薪百万的岗位才问的问题,据说百度内部是严禁问这些问题的,如果哪个面试官问你这类问题了,你小报告给我,橙GG我在内部红黑榜里面给他上一回头条,如何?
我们这边所说的思路,还是主要针对实际需求的设计思路和分析思路,这个依赖于一定的经验,但是确实也需要较强的分析和知识整合能力,很多所谓的新玩意儿,实际上是借鉴其它相似技术的思路,做的延伸。举个例子,我要做两个手机之间高速传输文件的应用,让你设计一下技术架构,你怎么思考,可能你就从来没做过,那么你自己有个清晰的思路,考虑大概通过什么方案来实现,蓝牙行吗,WiFi行吗,怎么发现对方,怎么传输文件,需要定义消息么,文件传输怎么做,思考一下,心里就有底了。
所谓思路,实际上还是建立在个人知识体系之上的,知其然而不知其所以然的话,很难触类旁通,很难有思路,深度理解两三个技术框架和实现原理很有必要,能加重你的分量。
5.较强团队协作能力、沟通能力
互联网产品的团队都是快节奏的团队,考察个人的合作能力和沟通能力,也是必须的,这个HR面试会问的多一些,不过在百度这种业务主导的企业,HR的影响力有限,主要是协助考评。主要还是在于和技术面试官的沟通和交流,两个码农的心其实是很容易贴近的,沟通过程中,要有积极参与的心态,该倾听的地方认真倾听,要提问的时候大胆提问,很简单,所以一般倒在沟通上的candidate很少,除非你要装X,搞的很强势,或者很拽,或者人品有bug,三观crash,性格out of memory,那么没办法,No zuo No die。
我觉得写得还是满准确的,换位思考根据上面的面经对比评判今天的面试我发现了以下缺点:我事先没想过百度地图客户端部会问Activity的四大控件有哪些,这些基础理论知识很久没有去看,同时也发现,即使我现在可以开发出上线的项目,但是android应用开发的深层次理论知识还是很欠缺。还有就是英语不好很吃亏,当时愣是没有反应过来问的是什么。问不会的问题多了,有些心灰意冷破罐破摔的感觉,这要不得。大概其中两个问题的时间有些情绪化。心里还是明白在考察我的知识面,面试官很好,很有耐心,会问陷阱题,反应也特别快。我也不明白我学了android呢么多课程,但是问道专业知识的时候会傻眼。。。之前学习的兴趣点都在数据结构,计算机系统方面。android应用可以开发出来相应功能可以优化,但是理论基础好像确实没有整理过。进程间的通信也没有。许多代码都是知道怎么用但没有懂。通信,四大控件再学一遍,android基础类的应用,内存的优化。一定要有耐心,要踏实。面试之前清一清脑子。面试的小哥好厉害,什么都懂,而且完全没有不耐烦的感觉。好了,话不多说整理知识点。
android四大控件:
Android四大基本组件介绍与生命周期
Android四大基本组件分别是Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器。
一:了解四大基本组件
Activity :
应用程序中,一个Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。
Activity之间通过Intent进行通信。在Intent 的描述结构中,有两个最重要的部分:动作和动作对应的数据。
典型的动作类型有:M AIN(activity的门户)、VIEW、PICK、EDIT 等。而动作对应的数据则以URI 的形式进行表示。例如:要查看一个人的联系方式,你需要创建一个动作类型为VIEW 的intent,以及一个表示这个人的URI。
与之有关系的一个类叫IntentFilter。相对于intent 是一个有效的做某事的请求,一个intentfilter 则用于描述一个activity(或者IntentReceiver)能够操作哪些intent。一个activity 如果要显示一个人的联系方式时,需要声明一个IntentFilter,这个IntentFilter 要知道怎么去处理VIEW 动作和表示一个人的URI。IntentFilter 需要在AndroidManifest.xml 中定义。通过解析各种intent,从一个屏幕导航到另一个屏幕是很简单的。当向前导航时,activity 将会调用startActivity(Intent myIntent)方法。然后,系统会在所有安装的应用程序中定义的IntentFilter 中查找,找到最匹配myIntent 的Intent 对应的activity。新的activity 接收到myIntent 的通知后,开始运行。当startActivity 方法被调用将触发解析myIntent 的动作,这个机制提供了两个关键好处:
A、Activities 能够重复利用从其它组件中以Intent 的形式产生的一个请求;
B、Activities 可以在任何时候被一个具有相同IntentFilter 的新的Activity 取代。
AndroidManifest文件中含有如下过滤器的Activity组件为默认启动类当程序启动时系统自动调用它
BroadcastReceive广播接收器:
你的应用可以使用它对外部事件进行过滤只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接收并做出响应。广播接收器没有用户界面。然而,它们可以启动一个activity或serice 来响应它们收到的信息,或者用NotificationManager 来通知用户。通知可以用很多种方式来吸引用户的注意力──闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。
广播类型:
普通广播,通过Context.sendBroadcast(Intent myIntent)发送的
有序广播,通过Context.sendOrderedBroadcast(intent, receiverPermission)发送的,该方法第2个参数决定该广播的级别,级别数值是在 -1000 到 1000 之间 , 值越大 , 发送的优先级越高;广播接收者接收广播时的级别级别(可通过intentfilter中的priority进行设置设为2147483647时优先级最高),同级别接收的先后是随机的, 再到级别低的收到广播,高级别的或同级别先接收到广播的可以通过abortBroadcast()方法截断广播使其他的接收者无法收到该广播,还有其他构造函数
异步广播,通过Context.sendStickyBroadcast(Intent myIntent)发送的,还有sendStickyOrderedBroadcast(intent, resultReceiver, scheduler, initialCode, initialData, initialExtras)方法,该方法具有有序广播的特性也有异步广播的特性;发送异步广播要: 权限,接收并处理完Intent后,广播依然存在,直到你调用removeStickyBroadcast(intent)主动把它去掉
注意:发送广播时的intent参数与Contex.startActivity()启动起来的Intent不同,前者可以被多个订阅它的广播接收器调用,后者只能被一个(Activity或service)调用
监听广播Intent步骤:
1> 写一个继承BroadCastReceiver的类,重写onReceive()方法,广播接收器仅在它执行这个方法时处于活跃状态。当onReceive()返回后,它即为失活状态,注意:为了保证用户交互过程的流畅,一些费时的操作要放到线程里,如类名SMSBroadcastReceiver
2> 注册该广播接收者,注册有两种方法程序动态注册和AndroidManifest文件中进行静态注册(可理解为系统中注册)如下:
静态注册,注册的广播,下面的priority表示接收广播的级别"2147483647"为最高优先级
动态注册,一般在Activity可交互时onResume()内注册BroadcastReceiver
IntentFilter intentFilter=new IntentFilter(“android.provider.Telephony.SMS_RECEIVED”);
registerReceiver(mBatteryInfoReceiver ,intentFilter);
//反注册
unregisterReceiver(receiver);
注意:
1.生命周期只有十秒左右,如果在 onReceive() 内做超过十秒内的事情,就会报ANR(Application No Response) 程序无响应的错误信息,如果需要完成一项比较耗时的工作 , 应该通过发送 Intent 给 Service, 由Service 来完成 . 这里不能使用子线程来解决 , 因为 BroadcastReceiver 的生命周期很短 , 子线程可能还没有结束BroadcastReceiver 就先结束了 .BroadcastReceiver 一旦结束 , 此时 BroadcastReceiver 的所在进程很容易在系统需要内存时被优先杀死 , 因为它属于空进程 ( 没有任何活动组件的进程 ). 如果它的宿主进程被杀死 , 那么正在工作的子线程也会被杀死 . 所以采用子线程来解决是不可靠的
- 动态注册广播接收器还有一个特点,就是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕app本身未启动,该app订阅的广播在触发时也会对它起作用
系统常见广播Intent,如开机启动、电池电量变化、时间改变等广播
Service 服务:
一个Service 是一段长生命周期的,没有用户界面的程序,可以用来开发如监控类程序。
比较好的一个例子就是一个正在从播放列表中播放歌曲的媒体播放器。在一个媒体播放器的应用中,应该会有多个activity,让使用者可以选择歌曲并播放歌曲。然而,音乐重放这个功能并没有对应的activity,因为使用者当然会认为在导航到其它屏幕时音乐应该还在播放的。在这个例子中,媒体播放器这个activity 会使用Context.startService()来启动一个service,从而可以在后台保持音乐的播放。同时,系统也将保持这个service 一直执行,直到这个service 运行结束。另外,我们还可以通过使用Context.bindService()方法,连接到一个service 上(如果这个service 还没有运行将启动它)。当连接到一个service 之后,我们还可以service 提供的接口与它进行通讯。拿媒体播放器这个例子来说,我们还可以进行暂停、重播等操作。
Service使用步骤如下
1>继承service类
2>AndroidManifast.xml配置清单文件中<application>节点里对服务进行配置
<service name=".SMSService"/>
服务不能自己运行,需要通过Contex.startService()或Contex.bindService()启动服务
通过startService()方法启动的服务于调用者没有关系,即使调用者关闭了,服务仍然运行想停止服务要调用Context.stopService(),此时系统会调用onDestory(),使用此方法启动时,服务首次启动系统先调用服务的onCreate()–>onStart(),如果服务已经启动再次调用只会触发onStart()方法
使用bindService()启动的服务与调用者绑定,只要调用者关闭服务就终止,使用此方法启动时,服务首次启动系统先调用服务的onCreate()–>onBind(),如果服务已经启动再次调用不会再触发这2个方法,调用者退出时系统会调用服务的onUnbind()–>onDestory(),想主动解除绑定可使用Contex.unbindService(),系统依次调用onUnbind()–>onDestory();
Content Provider内容提供者 :
android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。这些数据可以存储在文件系统中、在一个SQLite数据库、或以任何其他合理的方式,
其他应用可以通过ContentResolver类(见ContentProviderAccessApp例子)从该内容提供者中获取或存入数据.(相当于在应用外包了一层壳),
只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中
它的好处:统一数据访问方式。
android系统自带的内容提供者(顶级的表示数据库名,非顶级的都是表名)这些内容提供者在SDK文档的android.provider Java包中都有介绍。见:http://developer.android.com/reference/android/provider/package-summary.html
├────Browser
├────CallLog
├────Contacts
│ ├────Groups
│ ├────People
│ ├────Phones
│ └────Photos
├────Images
│ └────Thumbnails
├────MediaStore
│ ├────Albums
│ ├────Artists
│ ├────Audio
│ ├────Genres
│ └────Playlists
├────Settings
└────Video
CallLog:地址和接收到的电话信息
Contact.People.Phones:存储电话号码
Setting.System:系统设置和偏好设置
使用Content Provider对外共享数据的步骤
1>继承ContentProvider类并根据需求重写以下方法:
这里写代码片
public boolean onCreate();//处理初始化操作
/**
* 插入数据到内容提供者(允许其他应用向你的应用中插入数据时重写)
* @param uri
* @param initialValues 插入的数据
* @return
*/
public Uri insert(Uri uri, ContentValues initialValues);
/**
* 从内容提供者中删除数据(允许其他应用删除你应用的数据时重写)
* @param uri
* @param selection 条件语句
* @param selectionArgs 参数
* @return
*/
public int delete(Uri uri, String selection, String[] selectionArgs);
/**
* 更新内容提供者已存在的数据(允许其他应用更新你应用的数据时重写)
* @param uri
* @param values 更新的数据
* @param selection 条件语句
* @param selectionArgs 参数
* @return
*/
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs);
/**
* 返回数据给调用者(允许其他应用从你的应用中获取数据时重写)
* @param uri
* @param projection 列名
* @param selection 条件语句
* @param selectionArgs 参数
* @param sortOrder 排序
* @return
*/
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) ;
/**
* 用于返回当前Uri所代表数据的MIME类型
* 如果操作的数据为集合类型(多条数据),那么返回的类型字符串应该为vnd.android.cursor.dir/开头
* 例如要得到所有person记录的Uri为content://com.bravestarr.provider.personprovider/person,
* 那么返回的MIME类型字符串应该为”vnd.android.cursor.dir/person”
* 如果操作的数据为单一数据,那么返回的类型字符串应该为vnd.android.cursor.item/开头
* 例如要得到id为10的person记录的Uri为content://com.bravestarr.provider.personprovider/person/10,
* 那么返回的MIME类型字符串应该为”vnd.android.cursor.item/person”
* @param uri
*/
public String getType(Uri uri)
这些方法中的Uri参数,得到后需要进行解析然后做对应处理,Uri表示要操作的数据,包含两部分信息:
1.需要操作的contentprovider
2.对contentprovider中的什么数据进行操作,一个Uri格式:结构头://authorities(域名)/路径(要操作的数据,根据业务而定)
content://com.bravestarr.provider.personprovider/person/10
说明:contentprovider的结构头已经由android规定为content://
authorities用于唯一标识这个contentprovider程序,外部调用者可以根据这个找到他
路径表示我们要操作的数据,路径的构建根据业务而定.路径格式如下:
要操作person表行号为10的记录,可以这样构建/person/10
要操作person表的所有记录,可以这样构建/person
2>在AndroidManifest.xml中使用对ContentProvider进行配置注册(内容提供者注册它自己就像网站注册域名),ContentProvider采用authoritie(原意授权,可理解为域名)作为唯一标识,方便其他应用能找到
复制代码
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (null != savedInstanceState) {
String _userid = savedInstanceState.getString("StrUserId");
String _uid = savedInstanceState.getString("StrUid");
String _serverid = savedInstanceState.getString("StrServerId");
String _servername = savedInstanceState.getString("StrServerName");
int _rate = savedInstanceState.getInt("StrRate");
//updateUserId(_userid);
//updateUId(_uid);
//updateServerId(_serverid);
//updateUserServer(_servername);
//updateRate(_rate);
}
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putString("StrUserId", getUserId());
savedInstanceState.putString("StrUid", getUId());
savedInstanceState.putString("StrServerId", getServerId());
savedInstanceState.putString("StrServerName", getServerName());
savedInstanceState.putInt("StrRate", getRate());
}
引发activity摧毁和重建的其他情形
除了系统处于内存不足的原因会摧毁activity之外, 某些系统设置的改变也会导致activity的摧毁和重建. 例如改变屏幕方向(见上例), 改变设备语言设定, 键盘弹出等.
官网帮助文档链接:
http://developer.Android.com/guide/components/fragments.html
主要看两张图,和跑代码
一,Fragment的生命周
Fragment生命周期
二,与Activity生命周期的对比
场景演示 : 切换到该Fragment
11-29 14:26:35.095: D/AppListFragment(7649): onAttach
11-29 14:26:35.095: D/AppListFragment(7649): onCreate
11-29 14:26:35.095: D/AppListFragment(7649): onCreateView
11-29 14:26:35.100: D/AppListFragment(7649): onActivityCreated
11-29 14:26:35.120: D/AppListFragment(7649): onStart
11-29 14:26:35.120: D/AppListFragment(7649): onResume
屏幕灭掉:
11-29 14:27:35.185: D/AppListFragment(7649): onPause
11-29 14:27:35.205: D/AppListFragment(7649): onSaveInstanceState
11-29 14:27:35.205: D/AppListFragment(7649): onStop
屏幕解锁
11-29 14:33:13.240: D/AppListFragment(7649): onStart
11-29 14:33:13.275: D/AppListFragment(7649): onResume
切换到其他Fragment:
11-29 14:33:33.655: D/AppListFragment(7649): onPause
11-29 14:33:33.655: D/AppListFragment(7649): onStop
11-29 14:33:33.660: D/AppListFragment(7649): onDestroyView
切换回本身的Fragment:
11-29 14:33:55.820: D/AppListFragment(7649): onCreateView
11-29 14:33:55.825: D/AppListFragment(7649): onActivityCreated
11-29 14:33:55.825: D/AppListFragment(7649): onStart
11-29 14:33:55.825: D/AppListFragment(7649): onResume
回到桌面
11-29 14:34:26.590: D/AppListFragment(7649): onPause
11-29 14:34:26.880: D/AppListFragment(7649): onSaveInstanceState
11-29 14:34:26.880: D/AppListFragment(7649): onStop
回到应用
11-29 14:36:51.940: D/AppListFragment(7649): onStart
11-29 14:36:51.940: D/AppListFragment(7649): onResume
退出应用
11-29 14:37:03.020: D/AppListFragment(7649): onPause
11-29 14:37:03.155: D/AppListFragment(7649): onStop
11-29 14:37:03.155: D/AppListFragment(7649): onDestroyView
11-29 14:37:03.165: D/AppListFragment(7649): onDestroy
11-29 14:37:03.165: D/AppListFragment(7649): onDetach
Android线程间通信机制
当Android应用程序运行时,一个主线程被创建(也称作UI线程),此线程主要负责处理UI相关的事件,由于Android采用UI单线程模型,所以只能在主线程中对UI元素进行操作,如果在非UI线程直接对UI进行了操作,则会报错,另外,对于运算量较大的操作和IO操作,我们需要新开线程来处理这些工作,以免阻塞UI线程,子线程与主线程之间是怎样进行通信的呢?此时就要采用消息循环机制(Looper)与Handler进行处理。
一、基本概念
Looper:每一个线程都可以产生一个Looper,用来管理线程的Message,Looper对象会建立一个MessgaeQueue数据结构来存放message。
Handler:与Looper沟通的对象,可以push消息或者runnable对象到MessgaeQueue,也可以从MessageQueue得到消息。
查看其构造函数:
Handler()
Default constructor associates this handler with the queue for the current thread.//如不指定Looper参数则默认利用当前线程的Looper创建
Handler(Looper looper)
Use the provided queue instead of the default one.//使用指定的Looper对象创建Handler
线程A的Handler对象引用可以传递给别的线程,让别的线程B或C等能送消息来给线程A。
线程A的Message Queue里的消息,只有线程A所属的对象可以处理。
注意:Android里没有global的MessageQueue,不同进程(或APK之间)不能通过MessageQueue交换消息。
二、Handler通过Message通信的基本方式
使用Looper.myLooper可以取得当前线程的Looper对象。
使用mHandler = new Handler(Looper.myLooper()); 可产生用来处理当前线程的Handler对象。
使用mHandler = new Handler(Looper.getMainLooper()); 可诞生用来处理main线程的Handler对象。
使用Handler传递消息对象时将消息封装到一个Message对象中
Message对象可以通过Message类的构造函数获得,但Google推荐使用Message.obtain()方法获得,该方法会从全局的对象池里返回一个可复用的Messgae实例,API中解释如下:
Message()
Constructor (but the preferred way to get a Message is to call Message.obtain()).
Handler发出消息时,既可以指定消息被接受后马上处理,也可以指定经过一定时间间隔之后被处理,如sendMessageDelayed(Message msg, long delayMillis),具体请参考API。
Handler消息被发送出去之后,将由handleMessage(Message msg)方法处理。
注意:在Android里,新诞生一个线程,并不会自动建立其Message Loop
可以通过调用Looper.prepare()为该线程建立一个MessageQueue,再调用Looper.loop()进行消息循环。
下面举例说明:
在main.xml中定义两个button及一个textview
<Button
android:id="@+id/a"
android:layout_width="80dp"
android:layout_height="60dp"
android:padding="6dp"
android:layout_marginTop="10dp"
android:text="Test looper"
android:hint="Test looper" />
<Button
android:id="@+id/b"
android:layout_width="80dp"
android:layout_height="60dp"
android:padding="6dp"
android:layout_marginTop="10dp"
android:text="Exit" />
<TextView
android:id="@+id/tv"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"/>
Activity源代码如下:
public class HandlerTestActivity extends Activity implements Button.OnClickListener{
public TextView tv;
private myThread myT;
Button bt1, bt2;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
bt1 = (Button)findViewById(R.id.a);
bt2 = (Button)findViewById(R.id.b);
tv = (TextView)findViewById(R.id.tv);
bt1.setId(1);//为两个button设置ID,此ID用于后面判断是哪个button被按下
bt2.setId(2);
bt1.setOnClickListener(this);//增加监听器
bt2.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch(v.getId()){//按键事件响应,如果是第一个按键将启动一个新线程
case 1:
myT = new myThread();
myT.start();
break;
case 2:
finish();
break;
default:
break;
}
}
class myThread extends Thread
{
private EHandler mHandler;
public void run()
{
Looper myLooper, mainLooper;
myLooper = Looper.myLooper();//得到当前线程的Looper
mainLooper = Looper.getMainLooper();//得到UI线程的Looper
String obj;
if(myLooper == null)//判断当前线程是否有消息循环Looper
{
mHandler = new EHandler(mainLooper);
obj = "current thread has no looper!";//当前Looper为空,EHandler用mainLooper对象构造
}
else
{
mHandler = new EHandler(myLooper);//当前Looper不为空,EHandler用当前线程的Looper对象构造
obj = "This is from current thread.";
}
mHandler.removeMessages(0);//清空消息队列里的内容
Message m = mHandler.obtainMessage(1, 1, 1, obj);
mHandler.sendMessage(m);//发送消息
}
}
class EHandler extends Handler
{
public EHandler(Looper looper)
{
super(looper);
}
@Override
public void handleMessage(Message msg) //消息处理函数
{
tv.setText((String)msg.obj);//设置TextView内容
}
}
}
程序运行后点击TestLooper按键,TextView输出如下,说明新创建的线程里Looper为空,也就说明了新创建的线程并不会自己建立Message Looper。
修改myThread类:
class myThread extends Thread
{
private EHandler mHandler;
public void run()
{
Looper myLooper, mainLooper;
Looper.prepare();
myLooper = Looper.myLooper();
mainLooper = Looper.getMainLooper();
......
......
mHandler.sendMessage(m);
Looper.loop();
}
}
Looper.prepare为当前线程创建一个Message Looper,Looper.loop()开启消息循环。这样修改是OK呢?
答案是否定的!运行时Logcat将抛出CalledFromWrongThreadException异常错误,提示如下:
意思就是说“只有原始创建这个视图层次的线程才能修改它的视图”,本例中的TextView是在UI线程(主线程)中创建,因此,myThread线程不能修改其显示内容!
一般的做法是在子线程里获取主线程里的Handler对象,然后通过该对象向主线程的消息队列里发送消息,进行通信。
如果子线程想修改主线程的UI,可以通过发送Message给主线程的消息队列,主线程经行判断处理再对UI经行操作,具体可以参考之前的代码。
三、Handler通过runnable通信的基本方式
我们可以通过Handler的post方法实现线程间的通信,API中关于post方法说明如下
public final boolean post (Runnable r)
Causes the Runnable r to be added to the message queue. The runnable will be run on the thread to which this handler is attached.
Post方法将安排runnable对象在主线程的某个位置运行,但是并不会开启一个新的线程,验证代码如下:
public class HandlerTestActivity extends Activity {
private Handler handlerTest;
Runnable runnableTest = new Runnable()
{
public void run()
{
String runID = String.valueOf(Thread.currentThread().getId());//输出Runnable 线程的ID号
Log.v("Debug",runID);
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
handlerTest = new Handler();
String mainID = String.valueOf(Thread.currentThread().getId());
Log.v("Debug",mainID);//输出主线程的ID号
handlerTest.post(runnableTest);
}
}
Logcat里输出如下:
说明只是把runnable里的run方法放到UI线程里运行,并不会创建新线程
因此我们可以在子线程中将runnable加入到主线程的MessageQueue,然后主线程将调用runnable的方法,可以在此方法中更新主线程UI。
将之前的代码修改如下:
public class HandlerTestActivity extends Activity {
private Handler handlerTest;
private TextView tv;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv = (TextView)findViewById(R.id.tv);//TextView初始化为空
handlerTest = new Handler();
myThread myT = new myThread();
myT.start();//开启子线程
}
class myThread extends Thread{
public void run(){
handlerTest.post(runnableTest);//子线程将runnable加入消息队列
}
}
Runnable runnableTest = new Runnable()
{
public void run()
{
tv.setText("此信息由子线程输出!");
} };
}