0%

高级进阶课

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.5
1.7版本),volatile读CAS写(1.7~1.8版本)

总结:如何进行锁优化

长锁不如短缩:尽可能只锁必要的部分
大锁不如小锁:对加锁的对象拆分
分离读写锁,读写的频次是不一样的
消除无用锁,尽量不加锁,使用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的窗口展示流程

在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
2
3
4
5
6
7
8
<activity android:name=".MainActivity"
android:permission="com.examle.permit">

<intent-filter>
<action android:name="zzz" />
<category android:name="zzzzz" />
</intent-filter>
</activity>

那么,如果外部app要启动这个Activity,它要在 AndroidManifest 文件中声明这个权限。之后,再通过 action 和 category 方式启动。不过,这个要求含有目标Activity 的app先行安装。

拒绝服务漏洞

如果你的Activity 暴露出去了,那么攻击者可以来攻击,让你的Activity 拒绝服务。具体的原理如下:

  1. 我们知道,如果Activity A 启动另一个App 的 Activity B 的时候,往Intent 里面传入一个 实现了序列化接口的对象 serializableObject ,是可以传递过去的。
  2. 在 B 中,只要你去访问了 intent.getExtra() ,那bundle 就会把序列化的数据反序列化过来。
  3. 好了,如果serializableObject 对象对应的类只在 A 中有,但是在 B 应用中并没有,这时候反序列化就会产生异常(ClassNotFoundException,类找不到异常)。

拒绝服务异常一般发生在 Activity 启动过程中 或者在 onNewIntent 回调过程中。解决方法包括: 1、使用try-catch 2、不要暴露Activity

6-4、在代码任意位置为当前 activity 添加 view

点击看答案

如何在任意位置获取当前Activity

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 和腾讯云有,可以默认支持几个

谢谢你的鼓励