刚刚过去2019,新的一年2020年。都说衣不如新人不如故,技术是学新不学旧的?但是旧的知识不巩固,根基不固很容易在面试或者实战遇到很大的问题的java
如下知识点PDF版后续可见android
更多面试内容等等
(更多完整项目下载。未完待续。源码。图文知识后续上传github。)
(VX:mm14525201314)
https://github.com/xiangjiana/Android-MSgit
参考答案:
使用线程池的好处是减小在建立和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。若是不使用线程池,有可能形成系统建立大量同类线程而致使消耗完内存或则“过分切换”的问题,概括总结就是github
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)