Android 面试题(window、进程、线程篇
数据拷贝次数:Binder 数据拷贝只需要一次,而管道、消息队列、Socket 都需要 2 次,但共享内存方式一次内存拷贝都不需要;从性能角度看,Binder 性能仅次于共享内存。
socket 作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。
(2)从稳定性的角度
Binder 是基于 C/S 架构的,简单解释下 C/S 架构,是指客户端(Client)和服务端(Server)组成的架构,Client 端有什么需求,直接发送给 Server 端去完成,架构清晰明朗,Server 端与 Client 端相对独立,稳定性较好;
而共享内存实现方式复杂,没有客户与服务端之别, 需要充分考虑到访问临界资源的并发同步问题,否则可能会出现死锁等问题;从这稳定性角度看,Binder 架构优越于共享内存。
仅仅从以上两点,各有优劣,还不足以支撑 google 去采用 binder 的 IPC 机制,那么更重要的原因是:
(3)从安全的角度
传统 Linux IPC 的接收方无法获得对方进程可靠的 UID/PID,从而无法鉴别对方身份;而 Android 作为一个开放的开源体系,拥有非常多的开发平台,App 来源甚广,因此手机的安全显得额外重要;对于普通用户,绝不希望从 App 商店下载偷窥隐射数据、后台造成手机耗电等等问题,传统 Linux IPC 无任何保护措施,完全由上层协议来确保。?
Android 为每个安装好的应用程序分配了自己的 UID,故进程的 UID 是鉴别进程身份的重要标志,前面提到 C/S 架构,Android 系统中对外只暴露 Client 端,Client 端将任务发送给 Server 端,Server 端会根据权限控制策略,判断 UID/PID 是否满足访问权限,目前权限控制很多时候是通过弹出权限询问对话框,让用户选择是否运行。Android 6.0,也称为 Android M,在 6.0 之前的系统是在 App 第一次安装时,会将整个 App 所涉及的所有权限一次询问,只要留意看
会发现很多 App 根本用不上通信录和短信,但在这一次性权限权限时会包含进去,让用户拒绝不得,因为拒绝后 App 无法正常使用,而一旦授权后,应用便可以胡作非为。
针对这个问题,google 在 Android M 做了调整,不再是安装时一并询问所有权限,而是在 App 运行过程中,需要哪个权限再弹框询问用户是否给相应的权限,对权限做了更细地控制,让用户有了更多的可控性,但同时也带来了另一个用户诟病的地方,那也就是权限询问的弹框的次数大幅度增多。对于 Android M 平台上,有些 App 开发者可能会写出让手机异常频繁弹框的 App,企图直到用户授权为止,这对用户来说是不能忍的,用户最后吐槽的可不光是 App,还有 Android 系统以及手机厂商,有些用户可能就跳果粉了,这还需要广大 Android 开发者以及手机厂商共同努力,共同打造安全与体验俱佳的 Android 手机。
Android 中权限控制策略有 SELinux 等多方面手段,下面列举从 Binder 的一个角度的权限控制:
Android 源码的 Binder 权限是如何控制? -Gityuan 的回答
传统 IPC 只能由用户在数据包里填入 UID/PID;另外,可靠的身份标记只有由 IPC 机制本身在内核中添加。其次传统 IPC 访问接入点是开放的,无法建立私有通道。从安全角度,Binder 的安全性更高。
说到这,可能有人要反驳,Android 就算用了 Binder 架构,而现如今 Android 手机的各种流氓软件,不就是干着这种偷窥隐射,后台偷偷跑流量的事吗?没错,确实存在,但这不能说 Binder 的安全性不好,因为 Android 系统仍然是掌握主控权,可以控制这类 App 的流氓行为,只是对于该采用何种策略来控制,在这方面 android 的确存在很多有待进步的空间,这也是 google 以及各大手机厂商一直努力改善的地方之一。在 Android 6.0,google 对于 app 的权限问题作为较多的努力,大大收紧的应用权限;另外,在 Google 举办的 Android Bootcamp 2016 大会中,google 也表示在 Android 7.0 (也叫 Android N)的权限隐私方面会进一步加强加固,比如 SELinux,Memory safe language(还在 research 中)等等,在今年的 5 月 18 日至 5 月 20 日,google 将推出 Android N。?
话题扯远了,继续说 Binder。
(4)从语言层面的角度
大家多知道 Linux 是基于 C 语言(面向过程的语言),而 Android 是基于 Java 语言(面向对象的语句),而对于 Binder 恰恰也符合面向对象的思想,将进程间通信转化为通过对某个 Binder 对象的引用调用该对象的方法,而其独特之处在于 Binder 对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布于系统的各个进程之中。可以从一个进程传给其它进程,让大家都能访问同一 Server,就像将一个对象或引用赋值给另一个引用一样。Binder 模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。从语言层面,Binder 更适合基于面向对象语言的 Android 系统,对于 Linux 系统可能会有点“水土不服”。
Q:使用 Binder 进行数据传输的具体过程?
https://blog.csdn.net/freshui/article/details/55051268
Q:Binder 框架中 ServiceManager 的作用?
https://blog.csdn.net/hu3167343/article/details/38441119
Android 系统进程间通信机制 Binder 的总体架构由 Client、Server、ServiceManager 和驱动程序 Binder 四个组件构成。
ServiceManager 是整个 Binder IPC 通信过程中的守护进程,本身也是一个 Binder 服务,但并没有采用 libbinder 中的多线程模型来与 Binder 驱动通信,而是自行编写了 binder.c 直接和 Binder 驱动来通信,并且只有一个循环 binder_loop 来进行读取和处理事务,这样的好处是简单而高效。?
ServiceManager 本身工作相对并不复杂,主要就两个工作:查询和注册服务。?
Q:Android 中有哪些基于 Binder 的 IPC 方式?简单对比下?
Q:是否了解 AIDL?原理是什么?如何优化多模块都使用 AIDL 的情况?
AIDL 是 Android Interface Define Language 安卓接口语言缩写。实现了服务端和客户端的通信。
服务端:创建一个 Service 用来监听客户端的链接需求,然后创建一个 AIDL 文件,将暴露给客户端的接口在这个 AIDL 文件中声明,最后在 Service 中实现这个接口
客户端:绑定服务端的 Service,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,接着就可以调用 AIDL 中的方法了。
对于多模块,可用 Binder 连接池的思想:?
每个业务模块创建自己的 AIDL 接口并实现此接口,但是不同模块之间不能耦合,所有实现单独开来,然后向服务端提供自己的唯一标识和其相对应的 Binder 对象,对于服务端来说,只需要一个 Service 就可以了,服务端提供一个 queryBinder 接口,这个接口根据业务模块特征返回相应打的 Binder 对象给客户端,不同业务模块拿到所需的 Binder 对象后就可以进行远程方法的调用了。由此可见,Binder 连接池的作用就是将每个业务模块的 Binder 请求统一转发到远程 Service 中去执行,从而避免了重复创建 Service 的过程
Handler
=======
Q:谈谈消息机制 Hander?作用?有哪些要素?流程是怎样的?
Message:主要功能是进行消息的封装,同时可以指定消息的操作形式;?
Looper:消息循环泵,用来为一个线程跑一个消息循环。每一个线程最多只可以拥有一个。?
MessageQueue:就是一个消息队列,存放消息的地方。每一个线程最多只可以拥有一个。?
Handler:消息的处理者,handler 负责将需要传递的信息封装成 Message,发送给 Looper,继而由 Looper 将 Message 放入 MessageQueue 中。当 Looper 对象看到 MessageQueue 中含有 Message,就将其广播出去。该 handler 对象收到该消息后,调用相应的 handler 对象的 handleMessage()方法对其进行处理。?
Q:为什么系统不建议在子线程访问 UI?
可能在非 UI 线程中刷新界面的时候,UI 线程(或者其他非 UI 线程)也在刷新界面,这样就导致多个界面刷新的操作不能同步,导致线程不安全。
Q:一个 Thread 可以有几个 Looper?几个 Handler?
①一个线程中只能有一个 Looper,只能有一个 MessageQueue,可以有多个 Handler,多个 Messge;?
②一个 Looper 只能维护唯一一个 MessageQueue,可以接受多个 Handler 发来的消息;?
③一个 Message 只能属于唯一一个 Handler;?
④同一个 Handler 只能处理自己发送给 Looper 的那些 Message;
Q:如何将一个 Thread 线程变成 Looper 线程?Looper 线程有哪些特点?
新建一个 Looper,添加 looper.prepare 和 looper.loop 即可
特点:可以创建 handler,可以拥有自己的消息队列
Q:可以在子线程直接 new 一个 Handler 吗?那该怎么做?
不可以,会报错线程中不存在 Looper,新建一个 Looper,添加 looper.prepare 和 looper.loop 即可
Q:Message 可以如何创建?哪种效果更好,为什么?
public class MainActivity extends Activity {
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Toast.makeText(MainActivity.this, "hanlder", Toast.LENGTH_SHORT).show();
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
// Message msg = new Message(); //直接初始化一个 Message 对象,很普通的方法
// Message msg = Message.obtain();
Message msg = handler.obtainMessage();
//后面两个是从整个 Messge 池中返回一个新的 Message 实例,通过 obtainMessage 能避免重复 Message 创建对象。
//一般在用到线程池的时候就会用到这 2 种。
msg.arg = 1;
handler1.sendMessage(msg);
}
}).start();
}
}
Q:ThreadLocal 有什么作用?
ThreadLocal 采用了类似于哈希表的形式轻松实现 Looper 在线程中的存取,可以在多个线程中互不干扰地存储和修改数据。
Q:主线程中 Looper 的轮询死循环为何没有阻塞主线程?
卡死主线程的操作是在回调方法 onCreate/onStart/onResume 等操作时间过长,会导致掉帧,甚至发生 ANR,looper.loop 本身不会导致应用卡死。
Q:使用 Hanlder 的 postDealy()后消息队列会发生什么变化?
https://blog.csdn.net/qingtiantianqing/article/details/72783952
线程
==
Q:Android 中还了解哪些方便线程切换的类?
AsyncTask:底层采用了线程池
HandlerThread:底层使用了线程
IntentService:底层使用了线程
Q:AsyncTask 相比 Handler 有什么优点?不足呢?
AsyncTask 是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新 UI。从实现上来说,AsyncTask 封装了 Thread 和 Handler。但是 AsyncTask 并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池
Q:使用 AsyncTask 需要注意什么?
https://blog.csdn.net/qq_30379689/article/details/53203556
AsyncTask 的类必须在主线程中加载,这就意味着第一次访问 AsyncTask 必须发生在主线程中
AsyncTask 的对象必须在主线程中创建
execute 方法必须在 UI 线程中调用
不能直接在程序中调用着四个方法:
onPreExecute():异步任务开启之前回调,在主线程中执行
doInBackground():执行异步任务,在线程池中执行
onProgressUpdate():当 doInBackground 中调用 publishProgress 时回调,在主线程中执行
onPostExecute():在异步任务执行之后回调,在主线程中执行
onCancelled():在异步任务被取消时回调
一个 AsyncTask 对象只能执行一次,即只能调用一次 excute 方法,否则会报运行时异常。
Q:AsyncTask 中使用的线程池大小?
https://www.cnblogs.com/Doing-what-I-love/p/5532984.html
1.corePoolSize=CPU 核心数+1;
2.maximumPoolSize=2 倍的 CPU 核心数+1;
3.核心线程无超时机制,非核心线程在闲置时间的超时时间为 1s;
4.任务队列的容量为 128。
处理任务的优先级为:
核心线程 corePoolSize、任务队列 workQueue、最大线程 maximumPoolSize,如果三者都满了,使用 handler 处理被拒绝的任务(一般为抛出 java.util.concurrent.RejectedExecutionException 异常)。
Q:HandlerThread 有什么特点?
https://www.jianshu.com/p/e9b2c0831b0d
https://blog.csdn.net/lsmfeixiang/article/details/42213119
HandlerThread 本质上就是一个普通 Thread,只不过内部建立了 Looper。
HandlerThread 将 loop 转到子线程中处理,说白了就是将分担 MainLooper 的工作量,降低了主线程的压力,使主界面更流畅。
开启一个线程起到多个线程的作用。处理任务是串行执行,按消息发送顺序进行处理。
相比多次使用 new Thread(){…}.start()这样的方式节省系统资源。
但是由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
HandlerThread 拥有自己的消息队列,它不会干扰或阻塞 UI 线程。
通过设置优先级就可以同步工作顺序的执行,而又不影响 UI 的初始化;
Q:快速实现子线程使用 Handler
https://www.cnblogs.com/lang-yu/p/6228832.html
使用 HandlerThread 即可,如果单独在子线程中创建 Looper,有可能程序执行到 Handler 时,Looper 还没初始化完成,造成程序崩溃。所以最好用 HandlerThread,因为其内部确保了 Looper 的初始化完成。
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
Q:IntentService 的特点?
IntentService 是 Service 的子类,比普通的 Service 增加了额外的功能。优先级比普通的线程高。
先看 Service 本身存在两个问题:Service 不会专门启动一条单独的进程,Service 与他所在应用位于同一个进程中。 Service 也不是专门一条新进程,因此不应该在 Service 中直接处理耗时的任务。
特点: IntentService 会创建独立的 worker 线程来处理所有的 Intent 请求; 会创建独立的 worker 线程来处理 onHandleIntent()方法实现的代码,无需处理多线程的问题;其内部也是封装了 Handler。 所有请求处理完成后,IntentService 会自动停止,无需调用 stopSelf()方法停止 Service; 为 Service 的 onBind()提供默认实现,返回 null; 为 Service 的 onStartCommand 提供默认实现,将请求 Intent 添加到队列中;
Q:为何不用 bindService 方式创建 IntentService?
IntentService 本身设计就不支持 bind 操作。查看 IntentService 源码,其中的 onBind()函数被实现,而且返回 null。这从侧面就证明了以上结论。再者,IntentService 本身就是异步的,本身就不能确定是否在 activity 销毁后还是否执行,如果用 bind 的话,activity 销毁的时候,IntentService 还在执行任务的话就很矛盾了。
Q:线程池的好处、原理、类型?
https://blog.csdn.net/mine_song/article/details/70948223
管理重复线程,避免创建大量的线程增加开销。
除了降低开销以外,线程池也可以提高响应速度。
Q:ThreadPoolExecutor 的工作策略?
(1)如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
(2)如果线程池中的线程数量已经达到或超过了核心线程的数量,那么任务会进入任务队列中排队等待。
(3)如果在步骤(2)中无法将任务插入到任务队列,这往往是因为任务队列已满,这时候如果线程数量未达到线程池规定的最大值,那么会立即启动一个非核心线程来执行任务。
(4)如果步骤(3)中线程的数量已经达到了线程池规定的最大值,那么就拒绝执行此任务。
Q:什么是 ANR?什么情况会出现 ANR?如何避免?在不看代码的情况下如何快速定位出现 ANR 问题所在?
应用程序无响应,即应用程序 5 秒内没有反应就会提示应用程序无响应,应避免在主线程中进行耗时操作。
评论