本人之前从事的是嵌入式相关行业,主要是数字座舱相关操作系统底层以及中间层的开发,主要是基于Android、qnx、以及linux,语言的话用的比较多的是c/c++、java、python这些。最近面试的一些总结如下。
面试中问到的Android相关的问题
-
Android的四大组件?
Activity:Activity之间通过Intent进行通信,Android应用中每一个Activity都必须要在AndroidManifest.xml配置文件中声明,否则系统将不识别也不执行该Activity。
Service:主要处理一些比较复杂的业务,前台或者后台都会有。
startService()与bindService()区别?
startService()(启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。
使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。
Broadcast:
同一个App内具有多个进程的不同组件之间的消息通信
不同App内的通信
广播接收者的注册有两种方法,分别是程序动态注册和AndroidManifest文件中进行静态注册。
动态注册广播接收器特点是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕app本身未启动,该app订阅的广播在触发时也会对它起作用。
(1)普通广播:Context.sendBroadcast();
(2)有序广播:Context.sendOrderBroadcast();
(3)本地广播:仅在App内广播(内部使用handle机制,比binder高效)
ContentProvider:
只有需要在多个应用程序间共享数据时才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。
ContentProvider实现数据共享。ContentProvider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区。
开发人员不会直接使用ContentProvider类的对象,大多数是通过ContentResolver对象实现对ContentProvider的操作。 -
为什么要用 ContentProvider ?
ContentProvider 屏蔽了数据存储的细节 , 内部实现对用户完全透明,ContentProvider 可以实现不同 app之间 共享
Sql 也有增删改查的方法, 但是 sql 只能查询本应用下的数据库
而 ContentProvider 还可以去增删改查本地文件. xml 文件的读取等
如何实现数据共享的?
使用的时候首先自定义 一个类继承 ContentProvider , 然后覆写 query 、insert 、update 、delete 等 方法
把自己的数据通过 uri 的形式共享出去 android 系统下 不同程序 数据默认是不能共享访问 需要去实现一个类去继承 ContentProvider -
Android的JNI具体的应用?
主要是用于Java和C++之间互相转换的一种方式,相当于是Java层和native层的一个桥梁。
init -> Zygote -> AndroidRuntime调用startVm创建虚拟机,VM创建完成后调用startReg完成JNI方法注册。
Java文件完成的工作:1)加载JNI库 2)初始化Native层 3)声明native方法
初始化流程:调用loadLibrary,loadLibrary调用JNI_OnLoad,再调用registerNativeMethods(JNIHelp.cpp)方法注册JNI方法。
JNI函数注册分为两种:静态注册和动态注册
静态注册的话需要现在java文件中定义native方法,使用javah生成.h文件,再到native层编写具体的实现,编译成so
缺点:运行时找函数,效率不高;更改方法时需要重新生成头文件,灵活性不高
动态注册的话就是利用JNINativeMethod方法记录java和native的对应关系,调用JNI_OnLoad动态注册。java层到native层通过JavaVM(每个进程一个)获取env,可以通过AttachCurrentThread获取JniEnv,然后通过env调用反射函数GetLongField,或者从native到JNI层的话调用CallStaticVoidMethod之类的反射函数调用到Java层。 -
Android Binder的应用?
binder机制是一种Android里面很常用的一种进程间通信的方式。传统的进程间通信主要是有消息队列,管道,共享内存,信号量,信号,socket通信,这些都各自有优缺点。Android里面自定了一套binder通信的机制,主要是内核拷贝到用户空间只需要一次,因为client的数据可以直接放到server的内核,然后通过mmap映射到server的用户空间;其次binder也实现了一套C/S的架构,使得使用上更加简单,易于管理。
Client端需要和server进行通信的话,只需要server端注册service到servicemanager,然后client可以通过servicemanager拿到一个binderservice的代理对象,通过这个对象实现和对端的通信。
5.Android为何需要自定义一套binder的机制?
我觉得主要是两个原因吧。第一个是出于安全性的考虑,因为binder机制Android系统会为每一个进程分配自己的UID和PID,这样利用binder可以在server端进行权限的控制;第二个的话出于传输性能的角度,binder传输数据过程中只需要经历一次内存拷贝就能完成,传统的方式则需要两次内存拷贝;第三个的话是binder采用的是C/S的架构,客户端和服务端相对比较独立,架构比较清晰,一端出现问题也不会对另一端又很大的影响。
-
linktodeath死亡监听?
实际上就是监听binder出现问题的话通知到server端,然后进行重新连接的处理
面试遇到的问题:
1.c++ 11 新特性,常用的
const int b = 10;
auto a = b;
const类型auto不会转,因此还是a的类型还是int型。 -
MediaCodec和AudioTrack初始化的时候需要设置哪些参数?
AudioTrack设置的时候,设置采样率,量化格式,声道数;内部函数设置流模型(Music、Ring等)、载入模式(Static、Stream)、音频格式、缓冲区大小等
MediaCodec初始化时,设置MediaCodec的looper,通过MediaFormat存储一个map,设置解码的音视频格式,设置视频的配置帧sps,pps,视频的分辨率,调用configure的时候还要传入surface;通过getInputBuffer获取编码器缓冲区的大小,通过mpFormat设置缓冲区的大小。 -
视频帧SPS PPS的作用?
主要是用于MediaCodec的初始化,并且在视频传输的时候需要先判断视频过来的是sps pps,之后才可以进行MediaCodec的初始化。
SPS即Sequence Paramater Set,又称作序列参数集。SPS中保存了一组编码视频序列(Coded video sequence)的全局参数。所谓的编码视频序列即原始视频的一帧一帧的像素数据经过编码之后的结构组成的序列。而每一帧的编码后数据所依赖的参数保存于图像参数集中。一般情况SPS和PPS的NAL Unit通常位于整个码流的起始位置。
在H264码流中,都是以"0x00 0x00 0x01"或者"0x00 0x00 0x00 0x01"为开始码的,找到开始码之后,使用开始码之后的第一个字节的低5位判断是否为7(sps)或者8(pps), 及data[4] & 0x1f == 7 || data[4] & 0x1f == 8.然后对获取的nal去掉开始码之后进行base64编码,得到的信息就可以用于sdp.sps和pps需要用逗号分隔开来.
SDP中的H.264的SPS和PPS串,包含了初始化H.264解码器所需要的信息参数,包括编码所用的profile,level,图像的宽和高,deblock滤波器等。 -
Android AudioTrack 原理表述和MediaPlayer的区别?
AudioTrack主要是用来播放音频数据的。实现的原理就是在new AudioTrack的时候会set一些参数,并且通过binder和AudioFlinger进行交互,写数据时AudioTrack有两种方式一种是MODE_STATIC,这种是将数据直接全部写进去的,还有一种是MODE_STREAM,这种类型的话就是来多少数据写多少数据。写入的数据会通过共享内存的方式传给AudioFilnger,AudioFlinger那边再将数据转发给对应的hardware进行播放。
区别:没有解码音频流的模块,只能接受PCM数据流进行播放;MediaPlayer的话可以解码不同格式的音频进行播放;实际上MediaPlayer的底层就是创建的AudioTrack,然后通过AudioFlinger进行播放的。 -
Android整体架构表述
Android的架构大概就是最下层是Loader层,主要就是开机启动时加载bootloader,初始化一些硬件参数;然后就是到kernel层,也就是linux的内核,内核会启动idle进程是内核的零号进程,主要是初始化进程管理、内存管理、设备驱动等;然后在上面就是到了native层,也就到了用户层,这时候就需要启动用户进程中第一个进程,init进程。init进程会启动servicemanager,开机动画的进程,以及通过init.rc孵化出zygote进程,zygote是Java Framework的第一个进程,随后zygote进程再fork出systemserver(管理powerManager、AcativityManager)等Java Framwork中比较重要的进程,并且需要加载虚拟机和资源。mediaserver管理c++层的Framework,主要就是AudioFlinger,CameraServer等,由init进程派生。最后App层也是zygote进程fork出来的,第一个是launcher,主要负责桌面的App等。 -
视频的软硬件解码区别?
软件解码比较吃CPU,最常见的就是FFmepg。适配性强,但是耗电,耗CPU。
硬件解码使用专门的GPU进行解码的工作,减少CPU的运算,常见的MediaPlayer,MediaCodec可以配置不同的参数实现自定义硬件解码。
硬件解码的好处就是比较省电,可以支持长时间的播放或视频录制,提高系统整体的流畅度。缺点就是设置比较复杂,并且需要有硬件支持。 -
Http的应用?get和post区别?
从用途上讲:get用于传递地址,post用于提交;get的数据是一次tcp传输完成的,post是两次;因此get传输数据包是包头和包体在一起,并且会将传输的数据内容暴露在消息体中,但post是消息头和消息体分开的,数据比较安全;get只是历史记录的缓存,post不支持 -
说一下http的特点?
http协议首先是协议简单,只需传送请求方法和路径,所以传输比较高效;还有就是http 0.9/1.0是无连接,也就是一次请求完之后,就会断开,这个连接方式在http1.1中修改,改成持续连接,但需要设置模式为keep-alive;同时http传输的数据不要求类型,运用比较灵活,现在用的比较多的格式有JSON和protobuf这些;还有个特点就是无状态,这个意思就是http不会记录之前事务处理,数据如果没有收到的话就会重传;最后就是http支持B/S和C/S模型。 -
http的工作流程是什么样的?
首先,客户端和服务器需要建立连接,连接建立之后,客户端向服务器请求数据,请求消息包含URL、协议版本、MIME信息(包括请求修饰符、客户及信息和内容);服务器收到请求之后,反馈给客户端,消息包含协议信息、状态码以及MIME信息(括请求修饰符、客户及信息和内容);客户端收到服务器的回复之后,将内容显示在浏览器上,然后客户端和服务器就断开连接。断开连接时如果connection模式已经为close,则服务器端主动断开连接,客户端被动断开连接,如果模式为keep alive的话,则连接会持续一段时间,这段时间内可以继续接受连接。 -
protobuf的使用?
先定义一个.proto文件,然后再用protobuf的库编译,编译完之后生成.cc和.h文件,
syntax = “proto3”;message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}修改字段的注意事项:
必须不可以改变已经存在的标签的数字。
必须不可以增加或删除必须(required)字段。
可以删除可选(optional)或重复(repeated)字段。
可以添加新的可选或重复字段,但是必须使用新的标签数字,必须是之前的字段所没有用过的。
注意:
1 需要注意的是新的可选消息不会在旧的消息中显示,所以你需要使用 has_ 严格的检查他们是否存在
2 或者在 .proto 文件中提供一个缺省值。如果没有缺省值,就会有一个类型相关的默认缺省值:对于字符串就是空字符串;对于布尔型则是false;对于数字类型默认为0。
3 其他:
如果你添加了新的重复字段,你的新代码不会告诉你这个字段为空(新代码)也不会,也不会(旧代码)包含 has_ 标志。如果希望向后兼容,必须遵循:
a、不必更改tag数
b、不必添加或删除任何required字段
c、可以删除optional或repeated字段
d、可以添加新的optional或repeated字段,但你必须使用新的tag数。 -
Android的四种启动模式
Standard:标准的启动模式,如果需要启动一个activity就会创建该activity的实例。也是activity的默认启动模式。
SingeTop:如果启动的activity已经位于栈顶,那么就不会重新创建一个新的activity实例。而是复用位于栈顶的activity实例对象。如果不位于栈顶仍旧会重新创建activity的实例对象。
SingleTask:设置了singleTask启动模式的activity在启动时,如果位于activity栈中,就会复用该activity,这样的话,在该实例之上的所有activity都依次进行出栈操作,即执行对应的onDestroy()方法,直到当前要启动的activity位于栈顶。一般应用在网页的图集,一键退出当前的应用程序。
SingleInstance:如果使用singleInstance启动模式的activity在启动的时候会复用已经存在的activity实例。不管这个activity的实例是位于哪一个应用当中,都会共享已经启动的activity的实例对象。使用了singlestance的启动模式的activity会单独的开启一个共享栈,这个栈中只存在当前的activity实例对象。 -
handler message MessageQueue loop之间的区别?
想要使用handler的话,必须要先创建looper,因为主要的循环就需要靠loop完成。
创建loop的时候调用Looper.prepare(),内部会new出loop对象,并且会new出MessageQueue,调用loop进入循环dispatch消息。handle和loop之间用的是一个message;handler会调用sendMessage将消息加到queue中,loop循环执行dispatchMessage,然后会回调handleMessage函数。HandleMessage函数是可以让用户重写的。
MessageQueue主要负责存放相应的message并进行排序,按照延时时间和进入的先后顺序。
Message内部有一个单链表形成的消息池,Message销毁的话只是放到池子中,不会真正销毁,因为可以再利用。 -
AndroidManifest何时加载?
Activity可以被其他应用利用全类名或者action启动,就算此时要启动activity的应用没有被启动呢,
所以AndroidManifest在系统启动时或者应用安装完毕后已经被系统加载。
从系统代码级别解释就是系统启动过程中PMS会扫描特定目录下的apk进行安装,此时就会解析AndroidManifest文件。
源码解析AM.xml
PackageManagerService服务启动之后会扫描多个目录,下面存放着apk,调用scanDirLI对apk进行解析,经过一系列调用会调用到函数parseBaseApk,内部会利用XmlPullParser解析AndroidManifest文件,解析出信息会被放入Settings中,删除软件时信息会被删除,安装新的apk会重复调用scanDirLi。具体的详细过程大家可以参考源码。
总结
以上只是我之前在面试以及在学习的过程中整理的一些问题,可能会比较跳跃但是都是面试时候可能会被问到的问题,系统的学习Android还需要自己阅读源码或者相关的书籍。