4-1 停止线程
点击看答案
有两种方法:
- 调用 interruput 方法,通过抛异常的方式触发
- 使用boolean 标志位,注意,要使用volatile 标志位保证可见性;
一般而言,如果你使用了 Thread.sleep() 这样的系统方法,那么使用 interrupt 方法来停止线程,否则一律使用 boolean 标志位。
建议转移到线程安全问题上去
为什么停止线程会有问题
suspend 方法,但是现在已经被废弃。线程 t1 和 t2 ,如果t1被暂停了,然后一直持有锁 L1,那么t2一直在等待L1,而且不知道什么时候会恢复,这就很糟心了;并且如果t2持有t1需要的锁 L2 ,那就更麻烦了,可能导致死锁。所以线程的暂停(suspend方法)方法被废弃了。停止线程t1,马上释放锁,这时候线程t2 获取锁,接着读取这块加锁的内容,此时,如果t1在这个过程中只写入部分数据,t2 拿到的就是 非法数据,所以停止线程(stop方法)是不安全的。
为什么Thread 的run方法中执行 sleep 方法要try-catch 捕捉 InterruptedException ,这是因为在sleep 过程中,有可能这个thread 会被调用 thread.interrupt() ,此时就会触发 这个异常,我们应该在 catch 语句中 关闭打开的文件之类的。
4-2、如何写出线程安全的代码
点击看答案
每个线程都有一个自己的内存副本,这是java的内存模型。
要么不共享,要么不可变。其次就是采取一定手段了
final 其实还有一个功能就是禁止重排序。某些情况下,构造函数调用完了,非final的成员变量还没初始化完(final 是已经初始化完了),这就会有问题了。
为什么 a++ 不是原子性的,因为这个操作首先要将 a 赋值进来,int temp = a; 之后再对temp 操作:temp += 1; 最后再将 temp 的值赋回给a。
ThreadLocal 的使用建议
ThreadLocal 对对象也是弱引用
- 声明为全局静态final 成员,避免初始化多个ThreadLocal 对象(因为设置value的时候是以ThreadLocal 为key的)
- 避免存储大量对象,因为hash冲突使用的是开放定址 法,这并不适合大量数据
- 用完后及时移除,因为它只能靠主动移除和线程退出时移除,否则线程声明周期长的时候,迟迟得不到释放。
禁止重排序的方法:
- final 关键字
- volatile关键字
- 加锁(synchronized/Lock)
保证可见性:
- final 关键字
- volatile
- 加锁(释放锁之前强制刷新到主内存 )
保证原子性的方法:
- 加锁
- 使用CAS 指令
- 使用原子类型 ,如 AtomicInteger
- 使用原子属性更新器
还可以参考简书上的博客
4-3、ConcurrentHashMap
点击看答案
从1.5 到 1.8 一直在优化
jdk5:诞生,分段锁(segment),对段加锁,必要时加锁。它的hashCode 计算方法就是,对key 求hashCode ,之后对这个hashCode 再散列得到散列码 code,之后,根据这个code 的高位 来计算segment 的值,根据低位计算在segment 中的值。
1.6:二次hash算法。Integer 的hashCode() 函数返回的就是它的value值。3万多以下的整数高位都是15,于是都集中在一个segment中,退化成 HashTable 了(因为对这个segment 加锁也就相当于对整个ConcurrentHashMap加锁了)
1.7:段(segment)懒加载,volatile & CAS 操作避免加锁。一开始并不会把segment 通通new出来,需要哪个。使用volatile 来修饰 segment[] 数组,防止可见性导致的问题,比如,在某个segment 创建后,另一个线程不能马上知道。
8、摒弃段(segment),基于Hashmap 原理并发。 使用
求和
分段计数
CHM 是弱一致性
- 添加元素不一定马上读到。你读过这个段,然后再往这个段添加数据,那么就遗漏了
- 同以上的理,清空之后可能还有元素;
HashTable 的问题
- 大锁,对整个HashTable 加锁
- 长锁,直接对方法加锁
- 读写公用一把锁,读写不能同时进行
CHM 解法
小锁: 分段锁(1.51.7 版本使用segment),后来(1.8版本)桶节点锁1.7版本),volatile读CAS写(1.7~1.8版本)
短锁:先尝试获取,失败再加锁
分离读写锁:读写失败再加锁(1.5
总结:如何进行锁优化
长锁不如短缩:尽可能只锁必要的部分
大锁不如小锁:对加锁的对象拆分
分离读写锁,读写的频次是不一样的
消除无用锁,尽量不加锁,使用volatile 或者 CAS
4-5 Android 中写出优雅的代码
点击看答案
异步,不一定涉及到多线程,比如你setOnclickListener,它不是马上执行,而是等点击再相响应。异步不一定快,如果你线程一直在做运算,其实就无需太多线程,如果有很多io,被阻塞在那,那多开线程是有用的。
避免回调地狱。
Rxjava 要注意的问题:
- 很多时候Rxjava 请求完数据后,要更新UI,其实本质上这还是匿名内部类,会持有 Activity 引用。更新ui的时候,ui可能已经没有了,可能出现空指针;
- 声明将所有任务的句柄放在 List
列表中,在需要停止的时候,遍历执行 Disposable.dispose();
kotlin 协程
协程将异步逻辑同步化,取消协程,其实是跟 view 绑定,监听它的attach 状态,在dettach 的时候,取消协程。
5-1 cpu 架构适配
点击看答案
注意问题:
- native 开发才关注cpu架构
- 不同架构兼容性如何
- so 库太多如何优化 apk 体积
- sdk 开发者应该提供哪些so 库
armeabi 可以兼容x86 和 arm 架构,但是不兼容 mips(mips 目前已经被Android废弃了)。但是兼容方式无法获得最优性能,性能不敏感的话,可以这么干。有哪个架构的目录,那么所有so库就要有一份这个架构。
目前用得最多的可能就是 armabi-v7a 了,考虑实际情况可能使用 armabi-v7a 就可以了。
但是如果你有某个compute.so 针对计算比较多,那么你可以在 armeabi 目录下存在一个 compute.so 以及 compute_v8a.so ,其他的abi 只要提供一份就可以了(其实,微信就是这么干的)。非启动加载的so可以云端加载,从后台拉取。
在gradle 中指定需要的so类型,减小apk体积
sdk 开发要提供完整abi
5-2 Java native 方法怎么与Native 函数绑定
第5章的,都是jni,后面再看
6-1,Activity 启动流程
点击看答案
考察什么:
- 与ams 如何交互
参数结果如何展示 - Activity 如何实例化
- Activity 生命周期如何流转
- Activity 窗口如 何展示
- Activity转场动画实现机制
Activity 跨进程启动
所有进程Zygote fork 出来,预加载到启动资源,加快速度。
插件化,要有占位Activity才能实现。只能Activity 在进程内启动另一个Activity 的时候可以,在请求ActivityManagerProxy 之前和在ActivityThread 中开始回调Activity生命周期之前进行相应处理。
Activity 跨进程启动Activity ,会涉及到Binder 通信,这个Binder 缓冲区是有大小限制的(貌似是4M),没办法传递的。如果要实现数据传递,如果在同一个进程,可以使用单例,否则呢,不在同一个进程中,可以使用Provider(而AIDL 使用的也是Binder ,同样有大小限制吧)。
由于Activity 是通过反射构建出来的对象: (Activity)cl.loadClass(className).newInstance() ,它是通过无参的构造器构造的,所以呢,我们不要去写Activity 的构造函数,这是不应该的;尤其不要只写有参数的构造函数,这样的话,就没法启动这个Activity 了,会崩溃的。
Fragment 也不要自己写有参数的构造器,虽然我们自己可以把它new出来。但是当Activity 被回收,之后恢复Activity 的时候,会恢复Fragment ,当然,这个恢复也是通过反射的方式,通过无参的构造函数来构建Fragment实例的。如果你是通过有参数的构造函数自己启动Fragment的,那么在恢复的时候可能会丢失信息,这也是我们要求使用 arguments 方式而不是构造函数的参数给fragment传递数据的原因。个人猜测,这可能也是不让我们自己new Activity 的方式去构造它的原因吧。
Activity的窗口展示流程
在Activity 启动过程中,ActivityThread 执行 handleLaunchActivity,在这个方法中,attach 的时候就会 createPhoneWindow,之后调用 create、start、restoreState、postCreate、resume 过程中,其实都在installDecor (个人觉得是初始化 DecorView),在resume回调之后,马上调用 makeVisible 才将DecorView 展示出来(layout啊,绘制啊),我们才能看到显示的内容。这也就是为什么在resume 之前我们无法准确地获取View 的高度的原因。
转场动画
前一个页面调用 ExitTransiton,在新页面没打开之前,执行这个退出动画,进入新页面时,执行进入动画,这样就衔接上了。
6-2 如何跨App 启动Activity?有哪些注意事项?
点击看答案
如何启动外部应用的 Activity
- 共享uid的app(即在App的AndroidManifest文件中都注明了 android:sharedUserId=”xxx”,这个xxx 在两个应用中都是相同的) ,给 Intent 设置包名和Activity的全路径名,即可启动。
- 目标Activity 在AndroidManifest.xml中 设置为 exported=true,之后给Intent 设置 包名 和Activity全路径名即可。
- 给目标Activity 设置 action 和 category ,使用 IntentFilter 隐式启动
为允许外部启动的 Activity 添加权限
为目标Activity 添加权限,如:
1 | <activity android:name=".MainActivity" |
那么,如果外部app要启动这个Activity,它要在 AndroidManifest 文件中声明这个权限。之后,再通过 action 和 category 方式启动。不过,这个要求含有目标Activity 的app先行安装。
拒绝服务漏洞
如果你的Activity 暴露出去了,那么攻击者可以来攻击,让你的Activity 拒绝服务。具体的原理如下:
- 我们知道,如果Activity A 启动另一个App 的 Activity B 的时候,往Intent 里面传入一个 实现了序列化接口的对象 serializableObject ,是可以传递过去的。
- 在 B 中,只要你去访问了 intent.getExtra() ,那bundle 就会把序列化的数据反序列化过来。
- 好了,如果serializableObject 对象对应的类只在 A 中有,但是在 B 应用中并没有,这时候反序列化就会产生异常(ClassNotFoundException,类找不到异常)。
拒绝服务异常一般发生在 Activity 启动过程中 或者在 onNewIntent 回调过程中。解决方法包括: 1、使用try-catch 2、不要暴露Activity
6-4、在代码任意位置为当前 activity 添加 view
6-5、微信右滑返回效果
点击看答案
Fragment中实现相对简单,Activity 实现起来复杂
fragment实现: view 跟随手势移动的效果,不涉及window控制
用Activity实现,前一个activity 要搞成透明的效果,windowIsTranslucent = true,window的background 设置为透明
多个Task情况,比如顺序启动 a、b、c 三个,a 和 c 在同一个栈,因此可以先获取b的照片放做背景
透明对Activity生命周期影响,设置透明的话,下面的Activity 只能是 onPause 状态,不可能stop
所以我们只有在滑动的时候,才要求透明,其他的时候不透明,因为透明会影响绘制效率啊。。。。
7-1、为什么不能在UI线程绘制
点击看答案
ui线程是什么?ActivityThread 的main函数所运行的线程
为什么ui要设计成非线程安全的?因为加锁开销大,ui对时间敏感,ui具有高频可变性
非ui线程一定不能更新吗?不是,还有surfaceView,它会对canvas 加锁 : lockCanvas ,其次再draw ,最后 unLockCanvas,绘制放在子线程,效率提高。所以,可能app已经anr了,但是地图绘制界面还在绘制,很奇怪,这是因为地图绘制使用了 surfaceview。不过现在官方推荐使用TexutureView了,不推荐使用 SurfaceView。
Handler 的delay 可靠吗?
不可靠
7-3、ANR 类型
点击看答案
原理,就是处理事物之前 postDelay 一个事件,这个事件会导致anr窗口弹出,如果在delay的时间内完成事物,则会removeMessage ,把这个postDelay的message移除,就不会产生anr了。
Looper为什么不会导致cpu占用过高?因为 epoll多路复用机制
7-4、自己实现Handler
略
8-1、如何避免oom
点击看答案
native heap 在内存不够的时候,也会抛oom
使用合适的数据结构
避免枚举-影响内存和编译出来的文件(都会增大)
Bitmap: 选选择合适的分辨率,如果作为背景不要太清晰
不要使用帧动画
对Bitmap的重采样
谨慎使用多进程,新进程会带有一些公共资源,是会消耗内存的。
谨慎使用largeHeap = true,不同的手机上不一定能实现,还有,内存太大了,gc会困难
使用NDK,Native Heap 优雅避免java 堆内存限制
8-2、如何对图片缓存?
点击看答案
网络/磁盘/内存 进行缓存
缓存算法-LRU算法,或者最少使用频率的算法
如何验证算法的效果(命中率)
如何计算图片占用内存的大小
点击看答案
根据实图手动计算它在内存中占用的内存。
首先了解dip,mdpi、hdpi、xhdpi、xxhdpi、xxxhdpi
canvas 就相当于一个层,这个层根据不同手机进行缩放
assets(sd卡上的文件也是一样的):
如果png,采用ARGB_8888 的话呢,那就是 宽 × 高 × 4 ,因为每个像素要有4个字节,这里 4个8,每个8代表8个比特
如果是jpg,你没有指定加载格式,它默认还是使用 ARGB_8888,计算方式还是:宽 × 高 × 4。 其实jpg它没有透明度,那个A是没有用的,使用 RGB_565 即可,5 + 6 + 5 = 16 ,2个字节,所以就是 宽 × 高 × 2
从hdpi:
假设图片的大小为 112px * 131px ,格式png,如果你图片在 hdpi 中,那么系统会认为你这个图片的密度本身就是1.5,如果你这时候有个2.75密度屏幕的手机,那么它就会换算,比如说宽度 : 112 / 1.5 * 2.75 = 205.333…. ,系统会四舍五入取 205 ,同理高度会换算成: 131/1.52.75 = 240,所以如果使用 ARGB_8888 ,那占用内存就是 205240 * 4。同理啊,如果手机屏幕大点,比如说是3,那就是 131/1.5*3 和 112 / 1.5 * 3 了。
在把它放到xxhdpi,这时候密度是 3 了,这时候如果手机是 3,那就得是 131/3*3 和 112 / 3 * 3 了
所以如果在 mdpi中,那默认图片密度是 1了,那么在 dpi 是 3的手机上,图片宽高应该是 131*3 和 112 * 3
以此类推,其实在drawable,后面没有带dpi 的,那效果和 mdpi 一样,默认图片是 1
如果是nodpi ,告诉系统不会缩放,就按照原始的像素,131 和 112 了
所以宽高的计算方式 : 图片/dpi * 屏幕dpi
图片体积优化
跟图片存储格式无关。跟采用 ARGB_8888 或者 RGB_565
根据需要的尺寸
采样
使用矩阵变换来放大图片
不透明采用 RGB_565
.9 图片
使用VectorDrawable
9-1、Android p 规避访问私有api的限制
点击看答案
访问私有api,我们使用的是 反射 方式
反射时 setAccessible(true) ,只是绕过语言层面,并不会更改它的 final 或者 private 属性,不会说把private 改成public。
Android p中设置了 API 名单,有白名单、黑名单之类的,私有api在黑名单中,通过限制反射来限制访问 私有api
9-2、换肤的原理
略
9-3、virtualApk 插件化原理
略
Tinker 怎么实现热修复
略
10-1、如何开展优化工作
点击看答案
对整个目标是否有清晰认识
重点问题拆解
优化前期花 20% 的时间就能解决80%问题,剩下的 20%优化很难
对比业内(tester 测试其他app的打开页面方式)
指标监控,前后对比
10-x
都略
11-1、如何设计一个系统
点击看答案
保持和面试官沟通,多确认
需求-> 关键流程 -> 细节,明确边界
细节和边界的通常问题:
如何处理并发,是否有频繁io
网络怎么接入-短连接、长连接、连接池化,是否频繁与服务端交互(项目中的大接口)、是否有推送啊
保障安全性-数据是否需要加密、加密算法、
11-2、插件化
略
11-3、设计一个短视频app
点击看答案
视频如何处理?
视频来源?自有还是第三方?
视频由用户上传还是专业供应平台
是否需要建立用户关系链
支持分享?
支付系统打赏啊?
社交。聊天
播放器比较耗电
视频防止对手获取
防止广告被劫持
11-4、设计一个网络框架
点击看答案
不局限于http,还可以 websococket
单向请求还是双向请求啊?
支持异步请求?使用Rxjava 还是kotlin 协程?
要考虑可移植性啊?
缓存策略,多大啊?如何淘汰?
全局数据拦截器,对所有请求ip替换啊,对公共结果处理啊
日志输出,json,pb 转换为可视化
重试机制,3s、6s 之后再重试,最多重复多少次,防止死循环
参数组装,bean?hashMap,或者类似Retrofit 使用注解配置
协议体可以使用 Builder 模式
数据传输与拦截使用责任链模式
数据序列化
DNS 增强-httpDnsServer ,比如google的,还有aliyun 和腾讯云有,可以默认支持几个