1.Handler、Thread、HandlerThread三者的区别
①Handler是Android消息机制的上层接口,通过它可以轻松地将一个任务切换到Handler所在的线程中去执行,该线程既可以是主线程,也可以是子线程,要看构造Handler时使用的构造方法中传入的Looper位于哪里;
②Handler的运行需要底层的MessageQueue和Looper的支撑,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,而线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper;
③上述代码中的第一个Handler-mainHandler,实例化的时候,直接在onCreate()方法中new出了实例,其实是其已经在主线程中了,主线程-ActivityThread,ActivityThread被创建时就会初始化Looper,这就是主线程中默认可以直接使用Handler的原因;
④上述代码中的第二个Handler-workHandler,它在实例化的时候,参数传入了 mHandlerThread.getLooper() ,注意,这个Handler使用的就不是主线程的Looper了,而是子线程的Looper,HandlerThread在调用start()方法之后,就可以获取到子线程的Looper,然后将其传入workHandler的构造方法中,那么此时的workHandler就会运行在子线程中,用于处理耗时操作。
⑤Handler的工作原理:Handler创建时会采用当前线程的Looper来构建内部消息循环系统,如果当前线程没有Looper,那么就会报错“Can`t create handler inside thread that has not called Looper.prepare()”解决方法有两个:为当前线程创建Looper即可,像上述代码中workHandler,或者在一个有Looper的线程中创建Handler也行,就像上述代码中的mainHandler一样;
⑥调用Handler的post方法会将一个Runnable投递到Handler内部的Looper中去处理,也可以通过Handler的send方法来发送一个消息,这个消息同样会在Looper中去处理。其实post方法最终也是通过send方法来完成的。每当Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable的run方法或者Handler的handleMessage方法就会被调用。注意Looper是运行在创建Handler所在的线程中的,这样一来Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了;
⑦Looper的工作原理:Looper在Android的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。注意关注一些重要的Looper的方法:
Looper.prepare()-为当前线程创建一个Looper;
Looper.loop()-开启消息循环,只有调用该方法,消息循环系统才会开始循环;
Looper.prepareMainLooper()-为主线程也就是ActivityThread创建Looper使用;
Looper.getMainLooper()-通过该方法可以在任意地方获取到主线程的Looper;
Looper.quit() Looper.quitSafely()-退出Looper,自主创建的Looper建议在不使用的时候退出
⑧ActivityThread主线程通过ApplicationThread和AMS进行进程间通信
2. ThreadLocal实现原理详解
ThreadLocal大家应该不陌生,经常在一些同步优化中会使用到它。很多地方叫线程本地变量,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。也就是对于同一个ThreadLocal,每个线程通过get、set、remove接口操作只会影响自身线程的数据,不会干扰其他线程中的数据。
每个线程自身都维护着一个ThreadLocalMap,用来存储线程本地的数据,可以简单理解成ThreadLocalMap的key是ThreadLocal变量,value是线程本地的数据。就这样很简单的实现了线程本地数据存储和交互访问。
3.事件分发中的onTouch 和onTouchEvent 有什么区别,又该如何使用
OnTouch方法:
onTouch()是OnTouchListener接口的方法,它是获取某一个控件的触摸事件,因此使用时。通过getAction()方法可以获取当前触摸事件的状态:
如:ACTION_DOWN:表示按下了屏幕的状态。
OnTouchEvent()方法:
onTouchEvent是手机屏幕事件的处理方法,是获取的对屏幕的各种操作,比如向左向右滑动,点击返回按钮等等。
通过查看安卓源码中View对dispatchTouchEvent的实现,可以知道onTouchListener(onTouch方法在其中)的接口的执行顺序是要先于onTouchEvent的,onTouch方法会先触发。
如果onTouchListener中的onTouch方法返回true,表示此次事件已经被消费了,那onTouchEvent是接收不到消息的。(内置诸如click事件的实现等等都基于onTouchEvent,这些事件将不会被触发)
如果onTouch方法返回false会接着触发onTouchEvent。
4.为什么不能在子线程更新UI?
ViewRootImpl的创建在onResume方法回调之后,而我们一开篇是在onCreate方法中创建了子线程并访问UI,在那个时刻,ViewRootImpl是没有创建的,无法检测当前线程是否是UI线程,所以程序没有崩溃一样能跑起来,而之后修改了程序,让线程休眠了200毫秒后,程序就崩了。很明显200毫秒后ViewRootImpl已经创建了,可以执行checkThread方法检查当前线程。
4策略和考虑
首先UI线程(mainThread)并不是线程安全的,这样如果子线程修改UI容易数据错乱,如果做到线程安全的话,这样做是很低效的。
其次谷歌推荐如果子线程需要修改UI可以使用handler,这样的队列设也是考虑到并发,效率的体现。
为什么 android 会设计成只有创建 ViewRootImpl 的原始线程才能更改 ui 呢?这就要说到 Android 的单线程模型了,因为如果支持多线程修改 View 的话,由此产生的线程同步和线程安全问题将是非常繁琐的,所以 Android 直接就定死了,View 的操作必须在创建它的 UI 线程,从而简化了系统设计。
有没有可以在其他非原始线程更新 ui 的情况呢?有,SurfaceView 就可以在其他线程更新,具体的大家可以去网上了解一下相关资料。
4.AsyncTask原理及不足
AsyncTask是Handler与线程池的封装。
网络请求等耗时操作在线程池中完成,通过handler发送给主线程完成UI更新。
使用线程池的主要原因是避免不必要的创建及销毁线程的开销。
内存泄漏问题
AsyncTask对象必须在主线程中创建,这与内部sHandler有关,下面会解释。
AsyncTask对象的execute方法必须在主线程中调用。
一个AsyncTask对象只能调用一次execute方法
5.如何通过广播拦截和abort一条短信?
短信接收方式也是通过广播来接收,而且这个广播是有序广播
首先添加接收短信的权限
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
在清单文件中注册广播接收器,设置该广播接收器优先级,尽量设高一点
创建一个BroadcastReceiver来实现广播的处理,并设置拦截器abortBroadcast();
6. ANR超时时间的定义
broadcast超时时间为10秒
按键无响应的超时时间为5秒
前台service无响应的超时时间为20秒,后台service为200秒
7.Android线程有没有上限
原创FDoubleman 最后发布于2019-08-06 11:38:33 阅读数 790 收藏
展开
分析:
Android系统会给每个应用分配一个内存空间(不同的系统分配的内存大小不同),这块内存空间大小是有限的。
创建线程需要占用内存空间,
不可能拿有限的内存空间创建无限的线程
8.线程池有没有上限?
线程数该配置多少呢?网上有一个比较通用的公式可以作为参考:有一个简单并且适用面比较广的公式:CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
9.Parcelable和Serializable的区别:
android自定义对象可序列化有两个选择一个是Serializable和Parcelable
一、对象为什么需要序列化
1.永久性保存对象,保存对象的字节序列到本地文件。
2.通过序列化对象在网络中传递对象。
3.通过序列化对象在进程间传递对象。
二、当对象需要被序列化时如何选择所使用的接口
1.在使用内存的时候Parcelable比Serializable的性能高。
2.Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC(内存回收)。
3.Parcelable不能使用在将对象存储在磁盘上这种情况,因为在外界的变化下Parcelable不能很好的保证数据的持续性