csdnuuu 2020-01-03
刚刚过去2019,新的一年2020年。都说衣不如新人不如故,技术是学新不学旧的?可是旧的知识不巩固,根基不固很容易在面试或者实战遇到很大的问题的
以下知识点PDF版后续可见
更多面试内容等等
(更多完整项目下载。未完待续。源码。图文知识后续上传github。)
(VX:mm14525201314)
https://github.com/xiangjiana/Android-MS
参考答案:
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或则“过度切换”的问题,归纳总结就是
Android 中的线程池都是直接或间接通过配置
ThreadPoolExecutor 来实现不同特性的线程池.Android 中最常见的类具有不同特性的线程池分别为:
newCachedThreadPool
: 只有非核心线程,最大线程数非常大,所有线程都活动时会为新任务创建新线程,否则会利用空闲线程 ( 60s 空闲时间,过了就会被回收,所以线程池中有 0 个线程的可能 )来处理任务.
优点: 任何任务都会被立即执行(任务队列SynchronousQuue
相当于一个空集合);比较适合执行大量的耗时较少的任务.
newFixedThreadPool
: 只有核心线程,并且数量固定的,所有线程都活动时,因为队列没有限制大小,新任务会等待执行,当线程池空闲时不会释放工作线程,还会占用一定的系统资源。
优点: 更快的响应外界请求
newScheduledThreadPool
: 核心线程数固定,非核心线程(闲着没活干会被立即回收数)没有限制.
优点: 执行定时任务以及有固定周期的重复任务
newSingleThreadExecutor
: 只有一个核心线程,确保所有的任务都在同一线程中按序完成通过源码可以了解到上面的四种线程池实际上还是利用ThreadPoolExecutor
类实现的
//详细介绍课参考Executors.java类 public static ExecutorService newCachedThreadpool () { return new ThreadPoolExecutor (0,Integer.MAX_VALUE 60L,TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } ThreadPoolExecutor(int corepoolSize,int maxmumpoolSize, long keepAliveTime,TimeUnit unit, Blockingqueue<Runnable>workqueue,RejectedExecutionHandler handler
参考回答:
AsyncTask
: 底层封装了线程池和 Handler,便于执行后台任务以及在子线程中进行 UI 操作。HandlerThread
: 一种具有消息循环的线程,其内部可使用Handler。IntentService
: 是一种异步、会自动停止的服务,内部采用HandlerThread
。参考回答:
AsyncTask
中有两个线程池(SerialExecutor
和THREAD_POOL_EXECUTOR)和一个 Handler(InternalHandler
),其中线程池 SerialExecutor
用于任务的排队,而线程池THREAD_POOL_EXECUTOR 用于真正地执行任务,InternalHandler
用于将执行环境从线程池切换到主线程。AsyncTask
的类必须在主线程中加载,否则同一个进程中的 AsyncTask
都将无法正常工作。IntentService
可用于执行后台耗时的任务,当任务执行完成后会自动停止,同时由于 IntentService
是服务的原因,不同于普通 Service,IntentService
可自动创建子线程来执行任务,这导致它的优先级比单纯的线程要高,不容易被系统杀死,所以IntentService
比较适合执行一些高优先级的后台任务。
参考回答:
onCreate()
中创建,在 onDestroy()
中销毁。所以,在 Service 中创建的 Thread,适合长期执行一些独立于 APP 的后台任务,比较常见的就是:在 Service 中保持与服务器端的长连接。参考回答: ThreadPoolExecutor
执行任务时会遵循如下规则
ThreadPoolExecutor
会调用RejectedExecutionHandler
的rejectedExecution
方法来通知调用者。参考回答:
HandlerThread
,Android 中没有对 Java 中的 Thread 进行任何封装,而是提供了一个继承自 Thread 的类 HandlerThread
类,这个类对 Java的 Thread 做了很多便利的封装。HandlerThread
继承于Thread,所以它本质就是个 Thread。与普通 Thread 的差别就在于,它在内部直接实现了 Looper
的实现,这是 Handler 消息机制必不可少的。有了自己的 Looper
,可以让我们在自己的线程中分发和处理消息。如果不用 HandlerThread
的话,需要手动去调用 Looper.prepare()
和 Looper.loop()
这些方法。参考回答:ThreadLocal
是一个关于创建线程局部变量的类。使用场景如下所示:
当需要使用多线程时,有个变量恰巧不需要共享,此时就不必使用 synchronized
这么麻烦的关键字来锁住,每个线程都相当于在堆内存中开辟一个空间,线程中带有对共享变量的缓冲区,通过缓冲区将堆内存中的共享变量进行读取和操作,ThreadLocal
相当于线程内的内存,一个局部变量。每次可以对线程自身的数据读取和操作,并不需要通过缓冲区与 主内存中的变量进行交互。并不会像 synchronized
那样修改主内存的数据,再将主内存的数据复制到线程内的工作内存。ThreadLocal
可以让线程独占资源,存储于线程内部,避免线程堵塞造成 CPU 吞吐下降。
在每个 Thread 中包含一个 ThreadLocalMap
,ThreadLocalMap
的 key 是 ThreadLocal
的对象,value 是独享数据。
参考回答:
多线程的优点:
多线程的缺点:
综上得出,多线程不一定能提高效率,在内存空间紧张的情况下反而是一种负担,因此在日常开发中,应尽量
参考回答:
实现方法有多种,饿汉,懒汉(线程安全,线程非安全),双重检查(DCL),内部类,以及枚举
//OkHttp例子 private static volatile OkHttpHelper SInstance; public static OkHttpHelper getInstance() { if (sInstance =null) { synchronized (OkHttpHelper.class) { if (sInstance =null) { sInstance = new OkHttpHelper(); } } } return sInstance; }
参考回答:
notifyAll()
方法就唤醒全部的线程。注意:调用notify()
方法后并不会立即释放 object 锁,会等待该线程执行完毕后释放 Object 锁。参考回答:
BroadcastReceiver
、20s 内未结束 ServiceRunnable
接口、使用AsyncTask
IntentService
、HandlerThread
等参考回答:
负责跨线程通信,这是因为在主线程不能做耗时操作,而子线程不能更新 UI,所以当子线程中进行耗时操作后需要更新 UI时,通过 Handler 将有关 UI 的操作切换到主线程中执行。
具体分为四大要素:
MessageQueue
(消息队列): 负责消息的存储与管理,负责管理由 Handler 发送过来的 Message。读取会自动删除消息,单链表维护,插入和删除上有优势。在其 next()
方法中会无限循环,不断判断是否有消息,有就返回这条消息并移除。Handler.sendMessage()
)和处理相应消息事件(Handler.handleMessage()
),按照先进先出执行,内部使用的是单链表的结构。Looper
(消息池): 负责关联线程以及消息的分发,在该线程下从 MessageQueue
获取 Message,分发给Handler,Looper
创建的时候会创建一个MessageQueue
,调用 loop()方法的时候消息循环开MessageQueue
的 next()
方法,当有消息就处理,否则阻塞在 MessageQueue
的next()
方法中。当 Looper
的 quit()
被调用的时候会调用MessageQueue
的 quit()
,此时 next()
会返回 null,然后 loop()
方法也就跟着退出。流程:
MessageQueue.enqueueMessage
在消息队列中添加一条 Message。Looper.loop()
开启消息循环不断轮询调用MessageQueue.next()
,取得对应的 Message 并且通过 Handler.dispatchMessage
传递给 Handler,最终调用 Handler.handlerMessage
处理消息。参考回答:
Looper
,一个 MessageQueen
,可以有多个 HandlerThread(1)
:Looper(1)
: MessageQueue(1)
: Handler(N)
参考回答:
SoftReference
): 如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以一直被程序使用。WeakReference
): 如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。参考回答:
泄露原因:Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被Handler 持有,这样最终就导致 Activity 泄露。
解决方案:将 Handler 定义成静态的内部类,在内部持有Activity 的弱引用,并在 Acitivity
的 onDestroy()
中调用 handler.removeCallbacksAndMessages(null)
及时移除所有消息。
参考回答:
Android 的 UI 控件不是线程安全的,如果在多线程中并发访问可能会导致 UI 控件处于不可预期的状态
这时你可能会问为何系统不对 UI 控件的访问加上锁机制呢?因为
参考回答:
Looer.loop()
方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生 ANR 异常。参考回答:
如果队列中只有这个消息,那么消息不会被发送,而是计算到时唤醒的时间,先将 Looper 阻塞,到时间就唤醒它。但如果此时要加入新消息,该消息队列的对头跟 delay 时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大
参考回答:
不可以,因为在主线程中,Activity 内部包含一个 Looper 对象,它会自动管理 Looper
,处理子线程中发送过来的消息。而对于子线程而言,没有任何对象帮助我们维护 Looper
对象,所以需要我们自己手动维护。所以要在子线程开启 Handler 要先创建 Looper
,并开启 Looper
循环
//代码示例 new Thread(new Runnable() { @Override public void run() { looper.prepare(); new Handler() { @Override public void handlerMessage(Message msg) { super.handleMessage(msg); } looper.loop(); } }).start();
参考回答: 可以通过三种方法创建:
Message m = new Message
Message m = Message.obtain
Message m = mHandler.obtainMessage()
后两者效果更好,因为 Android 默认的消息池中消息数量是 10,而后
两者是直接在消息池中取出一个 Message 实例,这样做就可以避免多
生成 Message 实例。
参考回答:
参考回答:
AndroidMenifest
中给四大组件指定属性android:process 开启多进程模式参考回答:
所有运行在不同进程的四大组件(Activity、Service、Receiver丶ContentProvider)共享数据都会失败,这是由于 Android 为每个应用分配了独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这会导致在不同的虚拟机中访问同一个类的对象会产生多份副本。比如常用例子( 通过开启多进程获取更大内存空间、两个或则多个应用之间共享数据、微信全家桶)
一般来说,使用多进程通信会造成如下几方面的问题
SharedPreferences
的可靠性下降: 这是因为 Sp 不支持两个进程并发进行读写,有一定几率导致数据丢失参考回答:
与 Linux 上传统的 IPC 机制,比如 System V,Socket 相比,Binder 好在哪呢?
传输效率高、可操作性强: 传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。从 Android进程架构角度分析:对于消息队列、Socket 和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝,如图:
而对于 Binder 来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程,如图:
由于共享内存操作复杂,综合来看,Binder 的传输效率是最好的。
实现 C/S 架构方便: Linux 的众 IPC 方式除了 Socket 以外都不是基于 C/S 架构,而 Socket 主要用于网络间的通信且传输效率较低。Binder 基于 C/S 架构 ,Server 端与Client 端相对独立,稳定性较好。
安全性高: 传统 Linux IPC 的接收方无法获得对方进程可靠的 UID/PID,从而无法鉴别对方身份;而 Binder 机制为每个进程分配了 UID/PID 且在 Binder 通信时会根据UID/PID 进行有效性检测。
参考回答:
Linux 系统将一个进程分为 用户空间和 内核空间。对于进程之间来说,用户空间的数据不可共享,内核空间的数据可共享,为了保证安全性和独立性,一个进程不能直接操作或者访问另一个进程,即 Android 的进程是相互独立、隔离的,这就需要跨进程之间的数据通信方式
一次完整的 Binder IPC 通信过程通常是这样:
copyfromuser()
将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。参考回答:
参考回答:
参考回答:
AIDL(Android Interface Definition Language,Android接口定义语言):如果在一个进程中要调用另一个进程中对象的方法,可使用 AIDL 生成可序列化的参数,AIDL 会生成一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法
AIDL 的本质是系统提供了一套可快速实现 Binder 的工具。关键类和方法:
Interface
。asInterface()
: 客户端调用,将服务端的返回的Binder 对象,转换成客户端所需要的 AIDL 接口类型对象。如果客户端和服务端位于统一进程,则直接返回 Stub 对象本身,否则返回系统封装后的Stub.proxy 对象asBinder()
:根据当前调用情况返回代理 Proxy 的Binder 对象。onTransact()
: 运行服务端的 Binder 线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。transact()
: 运行在客户端,当客户端发起远程请求的同时将当前线程挂起。之后调用服务端的onTransact()
直到远程请求返回,当前线程才继续执行。当有多个业务模块都需要 AIDL 来进行 IPC,此时需要为每个模块创建特定的 aidl 文件,那么相应的 Service 就会很多。必然会出现系统资源耗费严重、应用过度重量级的问题。解决办法是建立 Binder 连接池,即将每个业务模块的Binder 请求统一转发到一个远程 Service 中去执行,从而避免重复创建 Service。
工作原理: 每个业务模块创建自己的 AIDL 接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的 Binder 对象。服务端只需要一个 Service,服务器提供一个 queryBinder
接口,它会根据业务模块的特征来返回相应的 Binder 对象,不同的业务模块拿到所需的 Binder 对象后就可进行远程方法的调用了
查看完整的PDF版
(更多完整项目下载。未完待续。源码。图文知识后续上传github。)
可以联系我获取完整PDF
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)