Android SDK 面试题

1.1 请简要谈谈Android系统的架构组成?

android系统分为四部分,从高到低分别是:

1、Android应用层

Android会同一系列核心应用程序包一起发布,该应用程序包包括email客户端,SMS短消息程序,日历,地图,浏览器,联系人管理程序等。所有的应用程序都是使用JAVA语言编写的。

2、Android应用框架层

开发人员也可以完全访问核心应用程序所使用的API框架。该应用程序的架构设计简化了组件的重用;任何一个应用程序都可以发布它的功能块并且任何其它的应用程序都可以使用其所发布的功能块(不过得遵循框架的安全性限制)。同样,该应用程序重用机制也使用户可以方便的替换程序组件。

3、Android系统运行层

Android 包含一些C/C++库,这些库能被Android系统中不同的组件使用。它们通过 Android 应用程序框架为开发者提供服务。

4、Linux内核层

Android 的核心系统服务依赖于 Linux 2.6 内核,如安全性,内存管理,进程管理, 网络协议栈和驱动模型。
Linux 内核也同时作为硬件和软件栈之间的抽象层。

1.2 SharedPreferences 是线程安全的吗?它的 commit 和 apply 方法有什么区别?

1.SharePreferences是线程安全的,里面的方法有大量的synchronized来保障
2.SharePreferences不是进程安全的,即使你用了MODE_MULTI_PROCESS
3.第一次getSharePreference会读取磁盘文件,异步读取,写入到内存中,后续的getSharePreference都是从内存中拿了
4.第一次读取完毕之前,所有的get/set请求都会被卡住,等待读取完毕后再执行,所以第一次读取会有ANR风险
5.所有的get都是从内存中读取
6.提交都是写入到内存和磁盘中,apply跟commit的区别在于apply是内存同步,然后磁盘异步写入任务放到一个单线程队列中等待调用,方法无返回,即void
commit内存同步,只不过要等待磁盘写入结束才返回,直接返回写入成功状态 true or false
7.从 Android N 开始, 不再支持 MODE_WORLD_READABLE & MODE_WORLD_WRITEABLE, 一旦指定,会抛异常 。也不要用MODE_MULTI_PROCESS 迟早被放弃。
8.每次commit/apply都会把全部数据一次性写入到磁盘,即没有增量写入的概念 。 所以单个文件千万不要太大 否则会严重影响性能。

1.3 Serializable和Parcelable的区别?

Android中序列化有两种方式:Serializable以及Parcelable。其中Serializable是Java自带的,而
Parcelable是安卓专有的。
Seralizable相对Parcelable而言,好处就是非常简单,只需对需要序列化的类class执行就可以,不需要手动去处理序列化和反序列化的过程,所以常常用于网络请求数据处理,Activity之间传递值的使用。
Parcelable是android特有的序列化API,它的出现是为了解决Serializable在序列化的过程中消耗资源严重的问题,但是因为本身使用需要手动处理序列化和反序列化过程,会与具体的代码绑定,使用较为繁琐,一般只获取内存数据的时候使用。

1.4 请简述一下 Android 7.0 的新特性?

  1. 低电耗功能改进
  2. 引入画中画功能
  3. 引入“长按快捷方式”,即App Shortcuts
  4. 引入混合模式,同时存在解释执行/AOT/JIT,安装应用时默认不全量编译,使得安装应用时间大大缩短
  5. 引入了对私有平台库限制,然而用一个叫做Nougat_dlfunctions的库就行
  6. 不推荐使用file:// URI传递数据,转而推荐使用FileProvider
  7. 快速回复通知

1.5 谈谈ArrayMap和HashMap的区别?

  1. 查找效率
    HashMap因为其根据hashcode的值直接算出index,所以其查找效率是随着数组长度增大而增加的。
    ArrayMap使用的是二分法查找,所以当数组长度每增加一倍时,就需要多进行一次判断,效率下降
  2. 扩容数量
    HashMap初始值16个长度,每次扩容的时候,直接申请双倍的数组空间。
    ArrayMap每次扩容的时候,如果size长度大于8时申请size*1.5个长度,大于4小于8时申请8个,小于4时申请4个。这样比较ArrayMap其实是申请了更少的内存空间,但是扩容的频率会更高。因此,如果数据量比较大的时候,还是使用HashMap更合适,因为其扩容的次数要比ArrayMap少很多。
  3. 扩容效率
    HashMap每次扩容的时候重新计算每个数组成员的位置,然后放到新的位置。
    ArrayMap则是直接使用System.arraycopy,所以效率上肯定是ArrayMap更占优势。
  4. 内存消耗
    以ArrayMap采用了一种独特的方式,能够重复的利用因为数据扩容而遗留下来的数组空间,方便下一个ArrayMap的使用。而HashMap没有这种设计。 由于ArrayMap之缓存了长度是4和8的时候,所以如果频繁的使用到Map,而且数据量都比较小的时候,ArrayMap无疑是相当的是节省内存的。
  5. 总结
    综上所述,数据量比较小,并且需要频繁的使用Map存储数据的时候,推荐使用ArrayMap。 而数据量比较大的时候,则推荐使用HashMap。

1.6 简要说说 LruCache 的原理?

androidx.collection.LruCache初始化的时候, 会限制缓存占据内存空间的总容量maxSize;
底层维护的是 LinkedHashMap, 使用 LruCache 最好要重写 sizeOf 方法, 用于计算每个被缓存的对象, 在内存中存储时, 占用多少空间;
在 put 操作时, 首先计算新的缓存对象, 占多少空间, 再根据key, 移除老的对象,占用内存大小 = 之前占用的内存大小 + 新对象的大小 - 老对象的大小;
put 操作最后总会根据 maxSize, 在拿到LinkedHashMap.EntrySet 中链表的头节点, 循环判断, 只要当前缓存对象占据内存超出 maxSize, 就移除一个头节点, 一直到符合要求;

lruCache 和 LinkedHashMap 的关系:
LinkedHashMap 中维护一个双向链表, 并维护 head 和tail 指针, lruCache 使用了 LinkedHashMap accessOrder为 true 的属性, 只要访问了某个 key,包括 get 和 put, 就把当前这个 entry 放在链表的尾节点,所以链表的头节点, 是最老访问的节点, 尾节点是最新访问的节点,
所以, lruCache 就很巧妙的利用了这个特点, 完成了 LeastRecently Used 的需求;

1.7 为什么推荐用SparseArray代替HashMap?

并不能替换所有的HashMap。只能替换以int类型为key的HashMap。
HashMap中如果以int为key,会强制使用Integer这个包装类型,当我们使用int类型作为key的时候系统会自用装箱成为Integer,这个过程会创建对象一想效率。
SparseArray内部是一个int数组和一个object数组。可以直接使用int减少了自动装箱操作。

1.8 PathClassLoader和DexClassLoader有何区别?

根据art源码来看,两者都继承自BaseDexClassLoader,最终都会创建一个DexFile,不同点是一个关键的参数:
optimizedDirectory,PathClassLoader为null,DexClassLoader则使用传递进来的
然后会根据optimizedDirectory判断对应的oat文件是否已经生成(null则使用/data/dalvik-cache/),如果有且该oat对应的dex正确则直接加载,否则触发dex2oat(就是这家伙耗了我们宝贵的时间!!),成功则用生成的oat,失败则走解释执行

注意:貌似Q版做了优化,不会再卡死在dex2oat里了,根据加载外部dex的实验,DexClassLoader会触发dex2oat,而PathClassLoader不会

1.9 说说HttpClient与HttpUrlConnection的区别?并谈谈为何前者会被替代?

1,android2.3之前,HttpUrlConnection具有一些bug,例如关闭输入流时可能导致连接池关闭。
2,android2.3之后,HttpUrlConnection才相对成熟。特点是,轻量、api少
3,HttpClient一直很强大,支持get、post、delete等其他协议。
具体可参考表格:[Android] HttpURLConnection & HttpClient & Socket

被替代原因:太重了,api太多;针对Android系统,不便于向后维护

1.10 什么是Lifecycle?请分析其内部原理和使用场景?

Jetpack 的 Lifecycle 库:它可以有效的避免内存泄漏,解决 Android 生命周期的常见难题。
内部原理:ComponentActivity 的 onCreate 方法中注入了 ReportFragment,通过 Fragment 来实现生命周期监听。
使用场景:给 RecyclerView 的 ViewHolder 添加 Lifecycle的能力。自己实现 LifecycleHandler,在 Activity 销毁的时候,自动移除 Handler 的消息避免 Handler 导致的内存泄漏。

1.11 谈一谈Android的签名机制?

当面试题要考察你某个技术的理解时,除了他们两个在技术上的区别和特点之外。更希望听到的是在业务上的应用,技术是为业务服务的,展现你的业务能力远比展现你的技术能力要重要。
现在我将从技术和业务应用两个方面来剖析android v1和v2签名的区别。
关于v1和v2两种签名的技术上的区别上面已经有很详细的回答了,那么我就说一说他们在业务应用方面的区别:
我们有时候做app推广,需要记录这个APP是通过谁的推广链接安装的,那么我我们只需要在每次通过服务器下发apk的时候对apk文件动一些手脚,在apk中标示这个apk是哪个用户分享的,然后当apk安装好打开时就可以读到apk事先存储好的标示,从而实现我们需要的业务需求。
给apk文件动手脚,v1支持,v1+v2的签名方式就不支持了。

1.12 谈谈安卓apk构建的流程?

  1. 使用aapt处理资源文件,如编译AndroidManifest.xml,编译生成resources.arsc,生成R.java等
  2. 使用javac等工具编译java文件,生成class格式文件
  3. 使用dx等工具将.class和项目依赖的jar编译成.dex
  4. 将生成的这些文件压缩进一个zip中
  5. 签名
    这只是最简单的过程,实际还会涉及到multidex,使用如proguard的工具处理生成的字节码,需要依赖aar文件,需要编译kotlin,使用jack,使用jni,使用d8/r8等情况~

1.13 简述一下Android 8.0 至12.0 分别增加了哪些新特性?

Android 12.0

  • 原生的ImageDecoder支持GIF和WebP格式
  • 支持圆角,Display.getRounderCorner()获取屏幕圆角的详细信息
  • 更易用的模糊、色彩滤镜等特效,View.setRenderEffect(RenderEffect) 将特效直接应用于视图
  • 限制对MAC地址的访问
  • 应用覆盖控制,可以控制是否允许在自己的内容上显示这些覆盖图层,调用Window#setHideOverlayWindows(),表明不允许TYPE_APPLICATION_OVERLAY的窗口显示
  • 应用无法关闭系统对话框,弃用了 ACTION_CLOSE_SYSTEM_DIALOGS intent 操作
  • Activity/BroadcastReciver/Service 声明了Filter,则必须显示设置android:exported属性
  • 必须为每个PendingIntent设置可变性
  • 后台应用无法再启动前台服务

Android 11.0

  • 短信更新改进,提供更加友好的交互
  • 权限和隐私,在Android10的用户隐私基础上,新增了位置、麦克风和摄像头的一次性权限许可
  • 内置屏幕录制
  • 适配不同设备,折叠屏支持优化,增加铰链角度传感器API等;高刷新率支持
  • 网络优化,新增『动态计量API』,如果检测到连接到无限5G信号,将可以访问最高质量的视频和图片

Android 10.0

  • 5G支持
  • 支持可折叠设备
  • 暗黑主题
  • 手势导航,全面屏手势操作
  • ART优化
  • 用户隐私,给用户更多应用程序控制权
  • 机器学习

Android 9.0

  • 刘海模式,手机可以直接设计刘海模式
  • 夜间模式
  • 默认使用https
  • 非 SDK 接口的限制
  • 全面屏
  • 后台应用:
    您的应用不能访问麦克风或摄像头。
    使用连续报告模式的传感器(例如加速度计和陀螺仪)不会接收事件。
    使用变化或一次性报告模式的传感器不会接收事件。
    如果您的应用需要在运行 Android 9 的设备上检测传感器事件,请使用前台服务。
  • 电话信息现在依赖设备位置设置 如果用户在运行Android 9 的设备上停用设备定位,则以下函数不提供结果:
    TelephonyManager.getAllCellInfo()
    TelephonyManager.listen()
    TelephonyManager.getCellLocation()
    TelephonyManager.getNeighboringCellInfo()
  • Build.SERIAL 始终设置为 "UNKNOWN" 以保护用户的隐私,如果您的应用需要访问设备的硬件序列号,您应改为请求 READ_PHONE_STATE权限,然后调用getSerial()。
  • 多进程 webview 信息访问限制
  • 对使用非 SDK 接口的限制:NoSuchMethodError/NoSuchFieldException
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    // Android P or above
} else {
    // below Android P
}
  • 检测是否使用了非SDK接口 工具veridex
  • Apache HTTP 客户端弃用,需要自定义classloader
  • 针对 Android 9 或更高版本并使用前台服务的应用必须请求 FOREGROUND_SERVICE 权限。 这是普通权限,因此,系统会自动为请求权限的应用授予此权限。

Android 8.0

  • 通知中心
  • 画中画(PIP)支持
  • 未知来源应用
  • 通知渠道
  • 应用无法使用其清单注册大部分隐式广播(即,并非专门针对此应用的广播)

1.14 请简述Apk的安装过程?

复制APK安装包到/data/app目录下,解压缩并扫描安装包,向资源管理器注入APK资源,解析AndroidManifest文件,并在/data/data目录下创建对应的应用数据目录,然后针对Dalvik/ART环境优化dex文件,保存到dalvik-cache目录,将AndroidManifest文件解析出的组件、权限注册到PackageManagerService并发送广播。

1.15 Java与JS代码如何互调?有做过相关优化吗?WebView?

java调js:

可以用loadUrl指定 javascript: 协议,然后带上js代码,如

webView.loadUrl("javascript:alert('hello!')")

4.4以后还提供了一个execJavascript方法,更方便调用

js调java:

可以先写好一个互调接口类,用addJavascriptInterface绑定好,然后js代码里调用

注意:api17以后希望被js调用的需要添加@JavascriptInterface注解,因为api17以前存在漏洞,通过互调接口的getClass()方法可以拿到Class对象,之后通过Class.forName()等一系列反射api可以调用任何方法

也可以在java层通过shouldOverrideUrlLoading拦截跳转请求,js代码里通过跳转页面传递给java

1.16 什么是JNI?具体说说如何实现Java与C++的互调?

  • 在Java类中声明native方法
  • 使用javac命令将Java类生成class文件
  • 使用javah文件生成头文件
  • 编写cpp文件实现jni方法
  • 生成so库,
    到此为止,java就可以通过调用native方法调用c++函数了,对于在c++中调用java方法,通过完整类名获取jclass
  • 根据方法签名和名称获取构造方法id
  • 创建对象(如果要调用的是静态方法则不需要创建对象)
  • 获取对象某方法id
  • 通过JNIEnv根据返回值类型、是否是静态方法调用对应函数即可

1.17 请简述从点击图标开始app的启动流程?

  1. 点击app图标,Launcher进程使用Binder IPC向SystemServer进程发起startActivity请求;
  2. SystemServer进程收到1中的请求后,向zygote进程发送创建新进程的请求;
  3. zygote进程fork出新的App进程
  4. App进程通过Binder IPC向SystemServer进程发起attachApplication请求;
  5. SystemServer进程收到4中的请求后,通过Binder IPC向App进程发送scheduleLauncherActivity请求;
  6. App进程的ApplicationThread线程收到5的请求后,通过handler向主线程发送LAUNCHER_ACTIVITY消息;
  7. 主线程收到6中发送来的Message后,反射创建目标Activity,回调onCreate等方法,开始执行生命周期方法,我们就可以看到应用页面了。

你可能感兴趣的:(Android SDK 面试题)