Android架构层次
android系统架构分从下往上为Linux内核层、运行库、应用程序框架层和应用程序层
- Linux内核层:负责硬件的驱动程序、网络、电源、系统安全以及内存管理等功能。
- 运行库和android runtion:运行库:即c/c++函数库部分,大多数都是开放源代码的函数库,例如webkit,该函数库负责android网页浏览器的运行;例如标准的c函数库libc、openssl、sqlite等,当然也包括支持游戏开发的2dsgl和3dopengles,在多媒体方面有mediaframework框架来支持各种影音和图形文件的播放与显示,如mpeg4、h.264、mp3、aac、amr、jpg和png等众多的多媒体文件格式。Androidruntion负责解释和执行生成的dalvik格式的字节码
- 应用软件架构:java应用程序开发人员主要是使用该层封装好的api进行快速开发的。
- 应用程序层:该层是java的应用程序层,android内置的googlemaps、email、IM、浏览器等,都处于该层,java开发人员工发的程序也处于该层,而且和内置的应用程序具有平等的地位,可以调用内置的应用程序,也可以替换内置的应用程序
ANR的产生原因以及解决方法
- ANR由Activity manager和windows manager来负责监听,你的应用程序所做的事情如果在主线程里占用了大长时间的话,就会引发ANR对话框,因为你的应用程序并没有给自己机会来处理输入事件或者Intent广播
- 产生原因: ①Activity 5s没有响应输入事件,比如按键,触摸事件②广播接收器在10s没有处理完毕事件 ③Service在20s之类没有完成事件处理
- 解决方法: 不在主线程进行网络请求,磁盘读取,位图修改,更新UI等耗时操作,而应该采用子线程来完成,完成之后通过Handler通知主线程
- 如何定位ANR:①检查log文件并分析LogCat输出,查看CPU使用量等信息; ②从/data/anr/目录下的trace.txt文件查看app调用栈信息; ③代码检查
Android 界面卡顿怎么处理?Link
Android子线程和UI线程交互的5种方式
handler: 参考“实习项目总结”
AsyncTask: 参考“实习项目总结”
Activity.runOnUIThread(Runnable)
子线程更新UI
new Thread() { public void run() { //这儿是耗时操作,完成之后更新UI; runOnUiThread(new Runnable(){ @Override public void run() { //更新UI imageView.setImageBitmap(bitmap); } }); } }.start();
非Activity更新UI
Activity activity = (Activity) imageView.getContext(); activity.runOnUiThread(new Runnable() { @Override public void run() { imageView.setImageBitmap(bitmap); } });
View.Post(Runnable)
imageView.post(new Runnable(){ @Override public void run() { imageView.setImageBitmap(bitmap); } });
View.PostDelayed(Runnabe,long)
Activity四种启动模式和生命周期
- Application: 应用程序是组件的集合,manifest文件中展现了application中组件的结构,加载app时会根据manifest去加载和实例化组件
- Process: 一个应用程序占据一个进程,一个运行中的dalvik虚拟机实例就占据一个进程; 但是我们可以给组件设置android:process = “name”来让组件运行在独立进程中
- Task: 一组以栈的形式来进行管理的相互关联的activity的集合,它是存在于framework层的一个跨应用的概念,控制界面的跳转和返回;task中所有的activity在一个叫做back stack的栈中进行管理
- 四种启动模式
- standard: 默认的lauchmode, 同一个activity可以被实例化多次,在一个task栈中可以同时存在一个activity的多个实例,每次startActivity就新建一个实例入栈
- singleTop: 先检查栈顶是否是该activity的实例,是则重用该实例,并且调用该实例的onNewIntent()方法,否则要新建实例压入栈顶
- singleTask: 先检查栈中是否包含该activity的实例,是则重用该实例,清理该实例上的所有activity并将其显示给用户;否则新建实例压入back stack
- singleInstance: 以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例;而且具备独占性,以该模式启动的Activity不能与其他Activity共存在一个task中
- standard: 默认的lauchmode, 同一个activity可以被实例化多次,在一个task栈中可以同时存在一个activity的多个实例,每次startActivity就新建一个实例入栈
Activity以及Fragment生命周期:
Activity切换时生命周期交集
- 当一个activity A启动了另外一个activity B,它们的生命周期是有交叉的
- 首先A的onPause()被调用;之后B的onCrate(), onStart()及onResume() 方法会被调用(此时B拥有用户焦点);最后,如果A在屏幕上不可见,onStop()方法被调用;
- 因此,我们在两个activities中传递数据,或者共享资源时(如数据库连接),需要在前一个activity的onPause()方法而不是onStop()方法中进行
onSaveInstanceState() 和 onRestoreInstanceState(): Link
①当某个activity变得”容易”被系统销毁时,该activity的onSaveInstanceState()就会被执行,除非该activity是被用户主动销毁的,例如当用户按BACK键的时候;他的调用有5种情况:(1)、当用户按下HOME键时:这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,因此系统会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则 (2)、长按HOME键,选择运行其他的程序时。(3)、按下电源按键(关闭屏幕显示)时。(4)、从activity A中启动一个新的activity时。(5)、屏幕方向切换时,例如从竖屏切换到横屏时。在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState()一定会被执行,且也一定会执行onRestoreInstanceState()。
②onRestoreInstanceState()被调用的前提是,activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行 此也说明上二者,大多数情况下不成对被使用。如果一个以SingleTask启动的的Activity实例,再新建一个该实例,Activity的执行流程???singleTask保证了在栈中Activity的唯一性,如果被SingleTask标记的Activity处于栈底,上面的Activity都会被pop出栈,这个时候被标记过SingleTask的Activity生命周期会做出调整:onNewIntent->onStart->onResume, 不再调用onCreate函数
- android:taskAffinity:每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该 Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果 Application也没有指明,那么该taskAffinity的值就等于包名;例如 android:taskAffinity=””的意思是不依附于任何task,也就是自己新建一个task
Service
Service默认运行在主线程,Service主要运行在后台执行一些监测行为或者其他加载网页等后台操作,可以通过iBinder或者广播与Activity进行交互
- Service两种类型:本地和远程service:
- 本地服务运行于主进程上的主线程,主进程被杀死后,服务便会终止;因为是在同一进程,因此不需要IPC,也不需要AIDL.
- 远程服务运行于独立进程的主线程,不受其他进程影响,有利于为多个进程提供服务;但是会占用一定资源,并且使用AIDL进行IPC通信;对应的独立进程的名字格式为所在包名加上指定android:process字符串。
- 两种启动模式: 具体区别Link
- startService方法一旦服务开启跟开启者就没有任何关系了;开启者退出了,服务还在后台长期的运行;开启者不能调用服务里面的方法
- 多次调用startService,该Service只能被创建一次,即该Service的onCreate方法只会被调用一次。但是每次调用startService,onStartCommand方法都会被调用;通过只能调用Context.stopService()方法结束服务
- bindService的方式开启并绑定服务,绑定者挂了,服务也会跟着挂掉.绑定者可以调用服务里面的方法
- 第一次执行bindService时,onCreate和onBind方法会被调用,但是多次执行bindService时,onCreate和onBind方法并不会被多次调用,即并不会多次创建服务和绑定服务;可以通过调用unbindService()方法来结束服务,调用该方法也会导致系统调用服务的onUnbind()—>onDestroy()方法
- 注意:对于既使用了startService同时也使用了bindService启动的服务我们如果需要停止该服务,应该同时调用stopService与unbindService方法;单独调用unbindService将不会停止Service,而必须调用stopService或Service的stopSelf 来停止服务
- onDestroy方法中做清理工作例如停止在Service中创建并运行的线程。
- 绑定者Activity如何调用service里的方法呢?
- 第一步自定义Service,在其内部重写onBind函数返回一个实现IBinder接口或者继承Binder的对象给绑定者—-这个自定义Binder是一个内部类,提供一个可以间接调用服务或者直接返回service对象的方法
- 第二步在Activity中调用bindService方法,服务成功绑定之后,新建一个ServiceConnection的匿名内部类对象,在回调方法onServiceConnected中会传递过来一个IBinder对象给Activity使用
Service生命周期
Service与Activity的通信机制
- 广播接收器:
- iBinder机制:
- IntentService
- 为什么要用IntentService如果Service将要运行耗时或者可能被阻塞的操作时,例如直接把耗时操作放在 Service 的 onStartCommand()方法中可能会出现ANR错误;所以应该在Service中重新启动一个新的线程来进行这些操作,这时我们就可以用IntentService
- IntentService内部原理IntentService是一个用来处理异步请求的Service类的子类,当客户端通过startService(Intent)方法传递多个请求给IntentService;IntentService会在onCreate函数中新建HandlerThread去执行这些耗时操作,并新建ServiceHandler对象处理消息,当启动一个IntentService的时候接下来会调用onstart方法,该方法会给ServiceHandler发送消息,在ServiceHandler的handleMessage回调函数中调用onHandleIntent函数,因此我们可以在这个onHandleIntent方法里面处理我们的耗时工作;所以IntentService不仅有Service的功能,还有Handler处理和循环消息的功能
- IntentService对多个异步请求的处理流程IntentService使用队列的方式将请求的Intent加入队列,然后开启一个工作线程来处理队列中的Intent,对于异步的多次startService请求,IntentService会处理完成一个之后再处理第二个,每一个请求都会在一个单独的worker thread中处理,不会阻塞应用程序的主线程;当任务执行完后,IntentService 会自动停止,而不需要我们去手动控制。
- Service后台保活:
- 提高Service的优先级或者提高Service所在进程的优先级:这个,也只能说在系统内存不足需要回收资源的时候,优先级较高,不容易被回收;实现方法有
①startForeground()方法来设置 Service为前台服务,优先级和正在运行的 Activity类似
②在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = “1000”这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低 - 在onDestroy方法里重启service:这个倒还算挺有效的一个方法,但是,直接干掉进程的时候,onDestroy方法都进不来,更别想重启了
- broadcast广播:和第3种一样,没进入onDestroy,就不知道什么时候发广播了,另外,在Android4.4以上,程序完全退出后,就不好接收广播了,需要在发广播的地方特定处理
- Service的onStartCommand方法返回START_STICKY,这个也主要是针对系统资源不足而导致的服务被关闭。四个返回值参考link:start_sticky、start_no_sticky、START_REDELIVER_INTENT、START_STICKY_COMPATIBILITY
- App进程之间互相唤醒
- 通过JNI的方式(NDK编程),fork()出一个子线程作为守护进程,轮询监听服务状态。守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。而守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行,这些都没有改变。
- 提高Service的优先级或者提高Service所在进程的优先级:这个,也只能说在系统内存不足需要回收资源的时候,优先级较高,不容易被回收;实现方法有
BroadcastReceiver
广播接收器用于过滤接收并响应来自其他应用程序或者系统的广播消息;每次广播到来时, 会重新创建 BroadcastReceiver对象,并且调用onReceive()方法,执行完以后,该对象即被销毁。当onReceive()方法在 10 秒内没有执行完毕,就会导致ANR。如果需要执行长任务,那么就有必要使用Service;BroadcastReceiver会堵塞主线程。唯有onReceive()结束,主线程才得以继续进行。
- 种类和特点:
- 全局广播包括有序广播、同步广播等,即发出去的广播可以被其他任何应用程序接收到,并且我们也可以接收来自于其他任何应用程序的广播。
- 全局广播跨进程必然会造成安全问题,于是便有了本地广播:即只能在本应用程序中发送和接收广播。这就要使用到了LocalBroadcastManager这个类来对广播进行管理。
- 静态注册和动态注册:
ContentProvider
Android View事件分发机制
Android Touch事件分发
- Touch事件传递的相关函数有dispatchTouchEvent(决定是否分发事件的函数)、onInterceptTouchEvent(决定是否截停事件的函数)、onTouchEvent(决定是否消费本次事件的函数)
- Touch事件会被封装成MotionEvent对象,该对象封装了手势按下、移动、松开等动作;事件类型分为ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP, ACTION_CANCEL,每个事件都是以ACTION_DOWN开始ACTION_UP结束。
- Touch事件通常从Activity#dispatchTouchEvent发出,只要没有被消费,会一直往下传递,到最底层的View; 如果Touch事件传递到的每个View都不消费事件,那么Touch事件会反向向上传递,最终交由Activity#onTouchEvent处理.
- Touch事件相关的类有View、ViewGroup、Activity, onInterceptTouchEvent为ViewGroup特有,可以拦截事件; Down事件到来时,如果一个View没有消费该事件,那么后续的MOVE/UP事件都不会再给它;OnTouchListener优先于onTouchEvent()对事件进行消费
- Touch事件分发详细图示
Android平台中实现数据存储的5种方式
- 使用SharedPreferences存储数据:SharedPreferences是Android平台上一个轻量级的存储类,本质是基于XML文件存储key-value键值对数据,主要是保存一些常用的配置信息,提供了Long长整形、Int整形、String字符串型的保存。它分为多种权限,可以全局共享访问,最终是以xml方式来保存。SharedPreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过Editor对象实现。
- 存储步骤:①根据Context获取SharedPreferences对象 ②利用edit()方法获取Editor对象。 ③通过Editor对象存储key-value键值对数据。 ④通过commit()方法提交数据。
- 文件存储数据
- SQLite数据库存储数据
- 使用ContentProvider存储数据
- 网络存储数据
Android 屏幕适配方法总结
Android进程间通信方法
- AIDL+Binder机制
- AIDL:即Android接口定义语言,一种帮助实现Binder通信的工具。由于Android不同的进程不能共享内存,所以为了解决进程间通讯的问题,Android使用一种接口定义语言来公开服务的接口,本质上,AIDL非常像一个接口,通过公开接口,让别的进程调用该接口,从而实现进程间的通讯。具体用法: 只要用几条简单的函数声明,AIDL就会帮忙生成一个JAVA文件,包括了一个Proxy和Stub的访问接口,以及用于它们之间通信的Parcel,并保证Parcel的输入输出的顺序一致性,其中Proxy用于客户端进程,Stub用于Service端进程.AIDL基本用法
- Binder机制:Android进程间通信是通过Binder来实现的,Binder机制使本地对象能够调用远程对象的方法,在不同进程中传递单向/双向消息。远程Service在Client绑定服务时,会在onBind()的回调中返回一个Binder,当Client调用bindService()与远程Service建立连接成功时,会拿到远程Binder实例,从而使用远程Service提供的服务。Binder优点是基于C/S通信模式,传输过程只需要一次拷贝,且为Client添加UID/PID身份,性能和安全性更好,并且Binder可以建立私有通道,这是linux的通信机制所无法实现的,因此Android进程间通信使用了Binder。
- Binder通信模型: Android系统Binder机制中的四个组件Client、Server、Service Manager和Binder驱动程序的关系如下图
- Client、Server和Service Manager实现在用户空间中,Binder驱动程序实现在内核空间中
- Binder驱动程序和Service Manager在Android平台中已经实现,开发者只需要在用户空间实现自己的Client和Server
- Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信
- Client和Server之间的进程间通信通过Binder驱动程序间接实现
- Service Manager是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力
- Client、Server和Service Manager实现在用户空间中,Binder驱动程序实现在内核空间中
- Binder CS通信流程:
- Server作为众多Service的拥有者,当它想向Client提供服务时,得先去Service Manager(以后缩写成SM)那儿注册自己的服务。Server可以向SM注册一个或多个服务。
- Client作为Service的使用者,当它想使用服务时,得向SM申请自己所需要的服务。Client可以申请一个或多个服务。
- ServiceManagerSM一方面管理Server所提供的服务,同时又响应Client的请求并为之分配相应的服务。
- Messenger:Messenger是基于AIDL实现的,服务端(被动方)提供一个Service来处理客户端(主动方)连接,维护一个Handler来创建Messenger,在onBind时返回Messenger的binder。
双方用Messenger来发送数据,用Handler来处理数据。Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理 - Socket通信: 在服务端定义ServerSocket并使用一个while循环监听客户端端口,客户端使用Socket来请求端口,连通后就可以进行通信
Android数据共享
- ContentProvider: 系统四大组件之一,底层也是Binder实现,主要用来为不同应用程序之间提供数据共享;可以用文件,内存数据,数据库等一切来充当数据源
- Bundle/Intent传递数据:可传递基本类型,String,实现了Serializable或Parcellable接口的数据结构。Serializable是Java的序列化方法,Parcellable是Android的序列化方法,前者代码量少,但I/O开销较大,一般用于输出到磁盘或网卡;后者实现代码多,效率高,一般用户内存间序列化和反序列化传输
- 本地文件共享:对同一个文件先后写读,从而实现传输,Linux机制下,可以对文件并发写,所以要注意同步。顺便一提,Windows下不支持并发读或写。
SharedPreferences:Preference提供了一种轻量级的数据存取方法,应用场合主要是数据比较少的配置信息。它以“键-值”(是一个Map)对的方式将数据保存在一个XML配置文件中
Context otherAppContext = createPackageContext("com.gary.appdisplaycontrol", Context.CONTEXT_IGNORE_SECURITY); SharedPreferences sharedPreferences = otherAppContext.getSharedPreferences("preferences",Context.MODE_WORLD_READABLE|Context.MODE_MULTI_PROCESS)
Json VS XML
Android安全
- 错误导出组件
- 参数校验不严
- WebView引入各种安全问题,webview中的js注入
- 不混淆、不防二次打包
- 明文存储关键信息
- 错误使用HTTPS
- 山寨加密方法
- 滥用权限、内存泄露、使用debug签名
设备横竖屏切换的时候,接下来会发生什么?
- 不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
- 设置Activity的android:configChanges=”orientation”时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
- 设置Activity的android:configChanges=”orientation|keyboardHidden”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
谈谈你对Android中Context的理解?
Context:包含上下文信息(外部值) 的一个参数. Android 中的 Context 分三种,Application Context ,Activity Context ,Service Context.
它描述的是一个应用程序环境的信息,通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个Activity,发送广播,接受Intent信息等
如何缩减APK包大小?
- 代码
保持良好的编程习惯,不要重复或者不用的代码,谨慎添加libs,移除使用不到的libs。
使用proguard混淆代码,它会对不用的代码做优化,并且混淆后也能够减少安装包的大小。
native code的部分,大多数情况下只需要支持armabi与x86的架构即可。如果非必须,可以考虑拿掉x86的部分。 - 资源
使用Lint工具查找没有使用到的资源。去除不使用的图片,String,XML等等。 assets目录下的资源请确保没有用不上的文件。
生成APK的时候,aapt工具本身会对png做优化,但是在此之前还可以使用其他工具如tinypng对图片进行进一步的压缩预处理。
jpeg还是png,根据需要做选择,在某些时候jpeg可以减少图片的体积。 对于9.png的图片,可拉伸区域尽量切小,另外可以通过使用9.png拉伸达到大图效果的时候尽量不要使用整张大图。 - 策略
有选择性的提供hdpi,xhdpi,xxhdpi的图片资源。建议优先提供xhdpi的图片,对于mdpi,ldpi与xxxhdpi根据需要提供有差异的部分即可。
尽可能的重用已有的图片资源。例如对称的图片,只需要提供一张,另外一张图片可以通过代码旋转的方式实现。
能用代码绘制实现的功能,尽量不要使用大量的图片。例如减少使用多张图片组成animate-list的AnimationDrawable,这种方式提供了多张图片很占空间。
Android插件化与动态加载
- 为什么要插件化???And它是什么?有什么好处?当一个app的方法数超过了一个 Dex 最大方法数 65535 的上限,因而便有了插件化的概念,Android插件化指将一个程序划分为不同的部分,也就是将一个App划分为多个插件(Apk或相关格式)比如一般App的皮肤样式就可以看成一个插件; 插件化的好处包括:(1) 模块解耦(2) 可以动态升级(3) 高效并行开发(编译速度更快) (4) 按需加载,内存占用更低 (5) 节省升级流量
- 插件化的原理实际是 Java ClassLoader 的原理: Android 有自己的 ClassLoader,分为 dalvik.system.DexClassLoader 和 dalvik.system.PathClassLoader,区别在于:
- PathClassLoader: 不能直接从 zip 包中得到 dex,因此只支持直接操作 dex 文件或者已经安装过的 apk(因为安装过的 apk 在 cache 中存在缓存的 dex 文件)
- DexClassLoader 可以加载外部文件系统中的 apk、jar 或 dex文件,并且会在指定的 outpath 路径存放其 dex 文件。
- 此外还有URLClassLoader :可以加载java中的jar,但是由于dalvik不能直接识别jar,所以此方法在Android中无法使用,尽管还有这个类
- Android热加载或Android动态部署指App在运⾏状态下动态加载某个模块,从而新增功能或改变某⼀部分行为. 例如一个app(称之为宿主程序)去本地文件中动态加载apk文件并将其放在自己的进程中执行
- 为什么有dex文件以及为什么要有DexClassLoader???Android应用程序,本质上使用的是java开发,使用标准的java编译器编译出Class文件,和普通的java开发不同的地方是把class文件再重新打包成dex类型的文件,这种重新打包会对Class文件内部的各种函数表、变量表等进行优化,最终产生了dex文件。dex文件是一种经过android打包工具优化后的Class文件,因此加载这样特殊的Class文件就需要特殊的类装载器;所以android中提供了DexClassLoader类
AsyncTask和Handler,Looper
- 网络请求交互:参见百度锁屏项目
- Android AsynTask实现多任务下载管理:
private class DemoAsyncTask extends AsyncTask<String, Integer, Bitmap> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Bitmap doInBackground(String... params) {
return null;
}
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
}
@Override
protected void onProgressUpdate(Integer... progress) {
super.onProgressUpdate(progress);
}
@Override
protected void onCancelled() {
super.onCancelled();
}
}
- AsyncTask
参数解析 - params 启动任务执行的输入参数,比如HTTP请求的URL(doInBackground函数的参数类型)
- progress 后台任务执行的百分比(onProgressUpdate函数的参数类型)
- result 后台执行任务最终返回的结果,比如String(doInBackground返回的参数类型
- new AsynTask.execute(url); execute方法内部调用executeOnExecutor()方法,执行之后AsyncTask会把任务交给线程池,由线程池来管理创建Thread和运行Therad执行任务
- 使用AsyncTask类,以下是几条必须遵守的准则:AsyncTask的实例必须在UI thread中创建; execute方法必须在UI thread中调用,并且只能调用一次;3.0版本之后的AsyncTask同时只能执行一个任务,但是提供executeOnExecutor(executor)来接收自定义ThreadPoolExecutor线程池,3.0之前默认为SerialExecutor,其核心线程池的大小是5,线程池总大小为128,缓存任务队列是10
AsyncTask内部实现原理
- AsyncTask的本质是一个线程池,AsyncTask在构造函数中新建一个继承Callable接口的WorkerRunnable类对象,重载call方法,在call方法中执行语句return postResult(doInBackground(mParams));执行doInBackground中的异步任务;然后新建FutureTask接受WorkerRunnable作为参数,提交到线程池执行;在FutureTask的重载done函数中调用get方法接收线程执行的返回结果并定义任务状态变化后的操作(包括失败和成功),再调用postResult方法利用handler(一个static class InternalHandler extends Handler对象)发送message给UI线程
- AsyncTask派生出的子类可以实现不同的异步任务,这些任务都是提交到静态的线程池中执行; 线程池中的工作线程执行doInBackground(mParams)方法执行异步任务
- 当任务状态改变(例如进度更新,执行成功,取消)之后,线程池中的工作线程会向UI线程发送消息,UI线程中的AsyncTask内部的InternalHandler响应这些消息并进行处理;例如:如果当前任务被取消掉了,就会调用onCancelled()方法不再调用onPostExecute方法,如果没有被取消,则调用onPostExecute()方法,这样当前任务的执行就全部结束了
AsyncTask和Handler,Looper之间的区别联系
- AsyncTask是对Handler与Thread的封装。
- AsyncTask在代码上比Handler要轻量级别,但实际上比Handler更耗资源,因为AsyncTask底层是一个线程池,而Handler仅仅就是发送了一个消息队列。但是,如果异步任务的数据特别庞大,AsyncTask线程池比Handler节省开销,因为Handler需要不停的new Thread执行
- AsyncTask的实例化只能在主线程,Handler可以随意,只和Looper有关系
Handler+Looper+MessageQueue异步信息处理系统模型 Link
- Handler+Looper+MessageQueue概念 Looper本质是一个ThreadLocal变量,它持有一个MessageQueue消息队列,负责实现消息循环和消息派发; Handler通过post和sendMessage方法去负责收发Message,包括push新消息到MessageQueue或者接收Looper从其持有的MessageQueue中取出来的消息; MessageQueue是一个FIFO消息队列
异步通信流程:
① 消息分发和处理: 首先一个线程通过调用Looper.prepare(),为自己创建一个唯一的Looper对象,在该Looper对象的构造函数中会执行mQueue = new MessageQueue(quitAllowed);去创建一个消息队列;然后我们新建一个或者多个Handler并重载他们的handleMessage回调函数,用于处理其他线程中发送过来的消息;接下来我们调用Looper.loop方法,在该方法中会建立一个无限循环体,通过调用msg.target.dispatchMessage(msg);函数(其内部调用Handler的handleMessage回调函数)不断地从消息队列中取出message,分发给不同的Handler target.
(注:在主线程中默认新建Looper对象不需要显示调用prepare和loop函数,Looper、MessageQueue和Handler都是在同一个线程中)② Handler发送消息: Handler在初始化时就会调用mLooper = Looper.myLooper(); mQueue = mLooper.mQueue;和Looper以及消息队列相关联,然后在子线程中调用post或者sendMessage方法发送消息插入到消息队列中,这两个函数内部的调用顺序大致为sendMessageDelayed—> sendMessageAtTime—> enqueueMessage,在enqueueMessage函数中会执行msg.target = this;这样Looper就可以识别每条信息来自于哪个Handler,以便于分发消息的正确性
Handler更新UI:Android系统中耗时操作不能放在主线程进行,例如网络请求、数据库操作、复杂计算等逻辑;所以他们一般放在AsyncTask或者新建子线程进行,然后通过Handler发送消息通知主线程;如果这些耗时操作涉及到UI界面的更新,可以借助于(Android系统UI的更新必须放在主线程进行)Handler发送消息给UI线程更新UI
Handler更新UI实例:
// UI线程新建的Handler,在handleMessage()中更新UI private Handler mHandler = new Handler() { public void handleMessage (Message msg) { switch(msg.what) { case MSG_SUCCESS: mImageView.setImageBitmap((Bitmap) msg.obj); break; case MSG_FAILURE: break; } } }; // 子线程中获取网络图片并发送message通知UI线程更新显示图片 Runnable runnable = new Runnable() { @Override public void run() { HttpClient hc = new DefaultHttpClient(); HttpGet hg = new HttpGet("http://www.oschina.net/img/logo.gif"); final Bitmap bm; try { HttpResponse hr = hc.execute(hg); bm = BitmapFactory.decodeStream(hr.getEntity().getContent()); } catch (Exception e) { mHandler.obtainMessage(MSG_FAILURE).sendToTarget(); return; } // Handler发送消息可以new但是最好使用obtainMessage,因为Message内部 // 通过next实现一个缓存消息链表,使用obtain方法获取Message使用完之后系统 // 会调用recycle方法进行回收,节省内存 mHandler.obtainMessage(MSG_SUCCESS,bm).sendToTarget(); } }; new Thread(runnable).start();
子线程中Handler结合Looper处理其他线程发送过来的消息实例
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { } }; Looper.loop(); } }
一个线程只能拥有一个Looper实例(该Looper对象可以通过Looper.myLooper()来获取),对应着一个MessageQueue消息队列,但是可以拥有多个Handler; 主线程初始化时默认创建一个Looper对象,可以通过Looper.getMainLooper()来获取他;
- 主线程中可以直接新建Handler,但是子线程中新建Handler需要先调用Looper.prepare()
- 消息队列其实内部实现是一个管道通信机制,当队列为空时线程进入等待状态,不为空时才进入处理消息状态;enqueueMessage方法也分为2种情况,队列为空直接将新message添加到队列头部,否则需要将消息按照时间先后顺序插入到合适位置,因为消息要按照处理时间排序
handler可能引发的内存泄露和解决方案
- 当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用。而Handler通常会伴随着一个耗时的后台线程一起出现,这个后台线程在任务执行完毕之后,通过消息机制通知Handler,然后Handler把消息发送到UI线程。然而,如果用户在耗时线程执行过程中关闭了Activity(正常情况下Activity不再被使用,它就有可能在GC检查时被回收掉),由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity暂时无法被回收(即内存泄露)
解决方案:
Handler声明为静态内部类,不再持有外部类Activity,在其内部声明一个Activity的弱引用来操作Activity中的对象
static class TestHandler extends Handler { WeakReference<Activity > mActivityReference; TestHandler(Activity activity) { mActivityReference= new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { final Activity activity = mActivityReference.get(); if (activity != null) { mImageView.setImageBitmap(mBitmap); } } }
①在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收;②如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除
- HandlerThread是一个Looper,Handler,Thread的组合实现;在其构造函数中对Looper进行初始化,并提供一个Looper对象给新创建的Handler对象,使得Handler处理消息事件在子线程中处理