0%

第16课-网络优化<中>

对于速度、弱网络以及安全的优化,该从哪些方面入手,首先我们要弄清楚一个网络请求的整个过程,示意图如下:

网络请求的完整流程

可以看出,整个流程分为 发起请求->DNS解析->创建连接->发送/接收数据->关闭连接

第17课-网络优化<下>-大数据下网络如何监控

插桩

为了兼容性考虑,首先考虑插桩。如360开源的性能监控工具 ArgusAPM ,就是利用 Aspect 切换插桩,实现监控系统和 OkHttp 网络请求库的请求。

系统网络库的插桩实现可以参考TraceNetTrafficMonitor,主要利用Aspect的切面功能,关于OkHttp的拦截可以参考OkHttp3Aspect,它会更加简单一些,因为OkHttp本身就有代理机制。

Native Hook

hook 本地的代码,需要考虑兼容性

统一网络库

ios 和Android 都统一使用同一套网络库,统一监控

小技巧:1、我们可以通过Android官方的 TrafficStats 类来获取整个手机或者某个 UID 从开机算起的网络流量; 2、Android 和 Iphone 都有一个网络测试模式,可以尝试下:

Android手机:打开拨号界面,输入 ##4636## ,然后按拨号键(可进入工程测试模式,部分版本可能不支持)
iPhone手机:打开拨号界面,输入 3001#12345# ,然后按拨号键。

自己注:在网上找到的微信弱网优化方法

Serializable 的原理

Serializable 的原理是通过 ObjectInputStream 和 ObjectOutputStream 来实现的,通过 Android 6.0 的源码可以看到 ObjectOutPutStream 的部分源码实现:

1
2
3
4
5
private void writeFieldValues(Object obj, ObjectStreamClass classDesc)  {
for (ObjectStreamField fieldDesc : classDesc.fields()) {
...
Field field = classDesc.checkAndGetReflectionField(fieldDesc);
...

整个序列化过程使用了大量反射(比如说获取整个类的所有属性,反射意味着效率低)和临时变量(临时变量意味着GC),并且,在序列化对象时,不仅会序列化当前对象本身,还需要地柜序列化对象引用的其他对象,可以参考别人的博客

Serializable 的进阶

Serializable 序列化支持使用自定义 writeObject和readObject,他会先反射判断是否存在我们自己实现的 序列化writeObject方法和反序列化readObject 方法。通过这两个方法,我们可以对某些字段做修改,也能实现序列化的加密功能。

还有,Serializable 的反序列默认是不会执行构造函数的

Parcelable 的永久存储

一般来说,我们使用 parcelable 只是会在内存中序列化操作,并不会存储到磁盘。其实,我们也可以存储到磁盘的:通过 Parcel.java 中的 marshall 接口获取byte 数组,然后存在文件中,从而实现 Parcelable 的永久存储。

1
2
3
4
5
6
7
8
// Returns the raw bytes of the parcel.
public final byte[] marshall() {
return nativeMarshall(mNativePtr);
}
// Set the bytes in data to be the raw bytes of this Parcel.
public final void unmarshall(byte[] data, int offset, int length) {
nativeUnmarshall(mNativePtr, data, offset, length);
}

不过,一般不推荐这样做,因为可能会有系统版本兼容性问题,因为我们无法保证所有的 Android 版本的 Parcel.cpp 实现完全一致。还有就是数据前后的兼容性,这里并没有类似 Serializable 里面有个 serialVersionUID 来保证版本一致性。

14-数据库优化

SQLite 默认支持多进程并发操作,它通过文件锁来控制多进程的并发,但是SQLite 的锁粒度并没有非常细,针对的是整个DB文件,简单来说,多进程可以同事获取 SHARED 锁来读取数据,但是只有一个进程可以获取 EXCLUSIVE 锁来写数据库

数据库使用注意:

  • 防止注入
  • 防止窃取
  • 小表无需维护索引,因为索引是需要一直维护的,有代价
  • 慎用 “select * “ 需要多少列,就取多少列
  • 定期删除无用数据

启动优化<上>-第7讲

启动分析

着手优化之前,首先分析启动过程。

  1. 首先预览窗口,系统拉起App进程之前,会根据app的Theme属性创建预览窗口。当然,我们禁用预览窗口或者预览窗口为透明时,用户依然可以看到桌面。
  2. 进程和闪屏页创建完毕,可以看到广告图片界面
  3. 主窗口创建完成可以看到首页了
  4. 首页加载完,才能操作起来

启动遇到的问题

  • 点击图标很久都不响应。可能是禁用了预览窗口或者透明皮肤
  • 首页显示太慢。闪屏广告、其它准备工作都要在启动阶段完成,如果耗时太多就慢
  • 首页显示后无法操作。工作异步延后之后,首页就会出现白屏,或者首页出来无法操作

启动优化

优化工具:综合看来,在卡顿优化中提到的 “systrace + 函数插桩” 是比较理想的方案,而且还能看到一些关键事件:GC、SystemServer 、CPU 调度等

准确的评估之后,才能指引优化的方向

优化方式

  • 闪屏优化。今日头条把预览窗口与是县城闪屏效果,用户在很短时间内看到“预览闪屏”,不过这种实现对于低端机型会把总的闪屏时间拉长。所以比较推荐在 6.0及以上的版本才采用“预览闪屏”
  • 业务梳理。清楚启动过程中每一个运行的模块,哪些是一定需要的,哪些是可以砍掉,可以懒加载的。还有,懒加载要防止集中化,容易出现首页用户无法操作的情况
  • 线程优化。主要在于减少cpu调度带来的波动,让应用启动更加稳定。具体做法是,一方面控制县城的数量,要有线程池(我们是采用Rxjava,统一管理线程池);还有一个就是管理线程的锁,比如业务有先后顺序,或者优先级不同,这一点可以采用第三方启动框架来解决,比如阿里开源的Alpha微信内部使用的mmkernel

启动优化<下>-第8讲

启动进阶方法

启动过程不建议出现网络io

还有,就是数据结构选择问题,在启动时,只需要读写很少量的 sp文件,如果与很大的sp文件一起解析,这个解析时间可能就要超过 100ms了(可以说我们的application中的sp解析是花了很长时间的)。

黑科技

应用加固对启动速度来说是灾难

启动监控

  • 实验室监控,如果客观地反映启动耗时,视频录制是非常好的选择,尤其是我们拿不到竞品的数据(通过分析竞品,我们制定了秒开的标准,可以取平均值、最大值、最小值)
  • 线上监控。我们使用talkingdata来监测。(但是我们要注意监测的耗时),最终衡量指标呢,使用平均法容易忽略掉性能差的手机,可以使用快开慢开比:比如2秒快开比,5秒快开比;另一种就是 90% 用户启动时间,如果90%用户启动时间都小于5秒,那我们90%区间启动耗时就是5秒。

卡顿优化<上>

基础知识

获取cpu信息:

1
2
3
4
5
// 获取 CPU 核心数
cat /sys/devices/system/cpu/possible

// 获取某个 CPU 的频率
cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq

卡顿指标

出现卡顿,首先应该看cpu使用率,可以通过 /proc/stat 查看系统的cpu使用情况:

1
2
3
4
5
proc/self/stat:
utime: 用户时间,反应用户代码执行的耗时
stime: 系统时间,反应系统调用执行的耗时
majorFaults:需要硬盘拷贝的缺页次数
minorFaults:无需硬盘拷贝的缺页次数

如果cpu使用率长期大于60%,表示系统处于繁忙状态,就需要进一步分析用户时间和系统时间的比例。普通应用程序系统时间不会长期高于 30% ,如果高于这个值,就应该检查是否是I/O过多,还是其他的系统调用问题

另外,top 命令可以查看哪个进程是cpu消耗大户。

内存优化<上>

Android中Bitmap 内存分配的变化过程

  1. 3.0以前,Bitmap 对象放在Java堆,像素数据存放在 Native内存中,如果不手动调用 recycle ,那么Native内存中的像素数据完全依赖于 finalize 函数,而这个函数是不可靠的
  2. 3.0~7.0,Bitmap 对象和像素数据统一放到 Java 堆,这样就算不 recycle 操作,像素数据也会随着一起回收,不过,这导致大量内存占用,引起大量GC。一起放在内存也有可能即使系统有大量内存没利用,但是却会导致oom了(比如给分配的最大堆只到 300M,但是用得差不多了,此时系统却还空闲有3G内存,却要引起oom了)
  3. Android 8.0 中,NativeAllocationRegistry 可以满足Bitmap 对象存放在Java 堆,像素数据在 Native 中,还能实现一并回收对象和像素数据。(8.0还提供硬件位图(Hardware Bitmap),减少内存占用并提升绘制效率)

关于内存优化的两个误区

  • 内存占用越小越好。不是这样的,更多的内存意味着更好的动画效果和更快的缓存命中,只需要做到系统内存充足时多用,系统内存紧张时少用(释放不是必须的内存)即可
  • Native 的内存不用管。其实,当系统内存不足的时候,就会开始依次清理 后台、桌面、服务、前台,一步步来,直到内存足够,最后直到重启手机

内存观测方法

  1. 观察Log,查看GC 回收的原因
  2. 使用如下命令查看:

adb shell dumpsys meminfo <package_name|pid> [-d]

内存优化<下>

内存优化探讨

Bigmap 优化、内存泄露优化、设备分级几个层次入手

设备等级就是,在某些低性能的机器上,动画不要了,进程也不预先启动了。安装包大小也是有要求的,比如有很多app就有极速版这个概念,它们的apk很小,动画也不酷炫。apk大小跟内存的关系可以参考如下图:

安装包大小和内存关系

Bitmap 可以用第三方框架来做,比如Fresco,实在要自己管理,可以向后台要求指定宽度,bitmap的inbitMap内存复用,inSample降低采样

内存泄露:使用LeakCanary自动化检测方案,只可以做到Activity和Fragment的泄漏检测,如何检测其他的内存泄露呢?如何监测疑似的内存泄漏呢?

GUOBIN

GUOBIN-1

  1. 项目,做了什么优化
  2. 如果有一个首页 横向有很多东西、纵向也有很多东西 你怎么处理?(ViewPager2 + RecyclerView) 我讲了500的首页动态化方案给他听
  3. 浏览器输入地址到显示干了什么
  4. https
  5. 项目网络框架(视频传输用什么?我说udp,并且给出了原因跟说了下http3)
  6. 二叉搜索树的删除
  7. 背书 死锁、多线程访问临界资源问题
  8. 有一个Button有ABC三种样式 然后屏蔽View的其他方法怎么做(我说了三种方法1. 编译时发现访问了View方法报错 2. 继承并且复写方法为空实现,内部提供super访问方法 3.面向对象思想 返回一个只有ABC三个方法的接口)
  9. 逻辑题 烧绳子 1小时得出45分钟 有桶里有3种无限的球 一次拿两个 问至少拿多少次才能出现重复的方案 排列组合共6种 所以至少7次

GUOBIN-2

  1. voliate关键字的作用,加上是否能保证原子操作的原子性?如何保证原子性
  2. recyclerview缓存机制
  3. tcp三次握手、项目网络库实现,为什么要用上udp?
  4. hashmap插入操作、什么时候转红黑树,为什么要转?线程安全么?ConcurrentHashMap如何保证安全
  5. binder优点、对比其他IPC机制,为什么要引入binder?
  6. OSI模型,每层作用跟有什么协议
  7. 概率题:一个家庭有三个孩子,已知其中一个为男,求另外两个至少有一个女的概率
  8. 算法:旋转链表LeetCode 61

GUOBIN-3

https相关
项目的mvvm,redux等、为什么不用databinding?(难找问题、编译慢)
jni如何获取JNIEnv、Jvm结构体、native如何保持jobject的引用 local reference需要每次都delete么?
项目网络库的实现 为何不用http?对比优缺点、除了C2S 有S2C功能么(push功能)怎么实现的。 保活链接呢,断了怎么办?(我们项目是自研的网络库,使用一个tcp socket、两个udp socket链接,tcp socket使用类似http2
的方式发送请求)
有没搞过windows开发?
有没搞过音频开发?
kotlin特点、优缺点、协程怎么实现?为什么不在java实现?协程的含义?dart的协程大致是怎么实现的(跟kotlin差不多)?(java原本是用在服务上的,不是搞移动的)

GUOBIN-4

(1)自我介绍
(2)你主要负责什么模块、奖金优化是什么东西(奖金优化的前提是如果能中奖,中奖金额要怎么样)、如何将所有组合列出来、如何将注数分配出去、奖金范围怎么算的
(3)APP的架构是怎么样的,为什么要这样子架构?MVP每块你们是怎么分工的
(3)首屏怎么调优的、加快启动速度的,内存泄漏怎么检测,LeakCanary原理是什么
(4)HashMap源码相关、为什么要将链转红黑树?红黑树特点、插入删除
(5)消息循环源码、如何做到任务切换线程、想要提交一个任务有几种方式
(6)Kotlin协程原理,怎么切换线程的,为什么要换Kotlin及其优缺点
(6)一个无序、实际上是逆序的数组排序用什么算法好 我说的是快排、实际上应该用归并排序能获得最好的时间复杂度

GUOBIN-5

(1)还是奖金优化
(2)还是性能调优、怎么瘦身、打包流程、模块化
(3)开发者GPUInfo中的每条柱状图颜色代表含义、命令dumpsys meminfo中VSS、PSS、GSS、USS代表什么意思
(4)Android绘制三部曲、Canvas是怎么最终显示在屏幕中的、是以什么样子的数据结构传输的
(5)物理内存与虚拟内存的关系、Android Heap的结构是怎么样的、如果要进行垃圾回收,会收集那些区域
(6)binder源码、其原理是什么,发起一次请求的过程,如何根据文件描述符找到对应的binder实体
(7)广度、深度搜索,拓朴排序、最短路径算法
(8)ELF文件格式、反编译器原理
(9)Kotlin优缺点

GUOBIN-6

(1)项目架构是怎么样的
(2)画一下结构图
(3)为什么要迁移项目到Kotlin
(4)线程 协程 为什么需要线程池 他的状态变化是怎么样的 在Android中的应用
(5)如何设计一个App的架构 该考虑什么
(6)Flutter怎么从平台到Dart的 如何渲染的
(7)你觉得面得怎么样

GUOBIN-7

讲个项目你负责的模块
多个Fragment在销毁后重建之后重叠怎么办
设计一个图片缓存框架
缓存算法用什么
有个ListView快速滑动 如何优化Bitmap的显示
Bitmap的复用听过没有
如何进行内存优化 减少内存消耗

GUOBIN-8

讲下项目的架构
负责的模块
算法 最大子序和
try catch finally关于return的执行结果
hashmap源码 treemap如何对两个元素进行比较 非compareble对象比较
hashcode equals区别
object有什么方法
讲讲Android存在的设计模式
android的消息机制
android的事件分发
讲个android的源码 自选
LeakCanary

GUOBIN-9

android消息机制
android事件分发
设计模式讲讲有什么认识的 代理模式优缺点
图片 缓存机制
首屏调优 内存调优

GUOBIN-SHOPEE

用https本地证书验证,基于这个点描述下。为什么做?技术方案?业界的技术方案?

新证书放在sp里面,sp在使用的过程中会有什么问题?新证书存进去要进行简单的对称加密?

插件化方案描述一下

React 的原理讲一下?

工作这么长时间,觉得哪些方面对自己挑战比较大?

在团队里面是什么样的角色

除了工作以外,还会接触哪些框架原理之类的,对 Fresco 比较熟是吧,它是怎么做的图片缓存的?

GUOBIN-FUTU

富途有一个概率问题,3个人去打天上的飞机,每个人打中的概率都是0.6,一起打,飞机被打中的概率是多少

超大图片加载到app预览,占用内存很大,实现方案上需要注意什么,怎么解决这个问题

微信朋友圈刷列表卡顿,让你定位你如何入手,怎么安排怎么开展工作

还一个,一堆钻石,每个大小可能不一样,你一次只能看一个,看完拿了就结束了,没拿就看下一个,不能回头。怎么保证能拿到一个比较大的钻石,钻石数量无限,可以认为是百万级

笔试(1h):

笔试主要是做一些基础题,主要涉及基础数学、操作系统、算法等基本知识

1:数独游戏,在9*9的数独上填充数字

2:逻辑电路的概率题

3:猴子吃桃问题

4:页面置换算法为FIFO,求缺页中断的次数

5:有7g和2g砝码,如果利用砝码和天平在3次之内将140g的面粉分为90g和50g

第一次:将140g面粉分为70g和70g
第二次:将70g面粉分为35g和35g
第三次:利用砝码将35g面粉分为20g和15g。由此35+15 = 50g; 35+35+20 = 90g
6:编程题 : 根据中序遍历结果和先序遍历结果建树

7:? 编程题:将字符串“l.am.happy”反转为“yapph.ma.I”

/**
 * 两次翻转,先对每个单词进行翻转,再对整体进行翻转 
 */
public String reverseString(String str){

    if(str == null || str.length() == 0){
        return null;
    }
    // 分割单词
    String []wordList = str.split("/.");
    for(int i = 0; i < wordList.length; i++){
        // 对每个单词进行翻转
        wordList[i] = convert(wordList[i]);
    }

    int low = 0;
    int high = wordList.length - 1;
    while(low < high){
        String tmp = wordList[low];
        wordList[low] = wordList[high];
        wordList[high] = tmp;
        low++;
        high--;
    }

    StringBuffer sb = new StringBuffer();
    for(int i = 0; i < wordList.length - 1; i++){
        sb.append(wordList[i]).append(".");
    }
    sb.append(wordList[wordList.length - 1]);
    return sb.toString();
}

// 对单个单词进行翻转
public String convert(String str){
    int low = 0;
    int high = str.length() - 1;
    char[]tmpStr = str.toCharArray();
    while(low < high){
        char temp = tmpStr[low];
        tmpStr[low] = tmpStr[high];
        tmpStr[high] = temp;
        low++;
        high--;
    }
    return String.valueOf(tmpStr);
}

8:? 编程题:不能使用系统函数,计算任意两个日期的天数差

9:? 推导题:A、B、C、D四个人分别带着一顶帽子。共两顶黑帽子,两顶白帽子。其中D和A、B、C三个隔了一堵不透明的墙。A可以看到B、C帽子的颜色。B可以看到C帽子的颜色。只要能判断自己的帽子颜色,就可以立刻说出来。他们四人沉默了几分钟,这时候一个人说到,它知道自己帽子的颜色是什么了,请问这个人是谁?

答案:应该是B。A能看到B、C两人的帽子。如果B、C两个人的帽子颜色相同,那么A立刻就能说出来自己帽子的颜色。否则,如果A沉默,我们就能断定B、C带了不同颜色的帽子。因为B能看到C帽子的颜色,B和C帽子的颜色不同,所以,我们能够推断出B。

?

一面:

一面主要是针对刚才的笔试题进行提问,以及抓着简历和项目经历来问

1:解释一下MVC、MVP、MVVM架构模式

2:解释一下Android组件化(LiveData + ViewModel)

3:? ?对ios架构有了解吗?(只了解过Viper)

4:Kotlin的特点、优点和缺点

优点:

完全兼容Java
Null safe
支持lambda表达式(比Java8更好)
支持扩展
支持高阶函数
体验一致的开发工具链
代码简洁
缺点:

可读性差
编译速度慢
操作不当容易引出大错误
学习资料少
IDEA 自动转换工具,把 Java 转换成 Kotlin,转换质量比人工转的要差的多。
5:? 抓项目细节,问项目的实现

6:Android如何进行优化,如内存优化、布局优化、性能优化

布局优化:

尽量减少布局文件的层级
标签,主要用于布局重用,提高布局的复用性
标签,一般和标签配合使用,可以减少布局的层级
标签,提供了按需加载的功能,当需要时才会将ViewStub中的布局加载到内存,这提高了程序的初始化效率
绘制优化:

避免在View的onDraw方法中执行大量的操作

onDraw中不要创建新的局部变量,这是因为onDraw方法可能会被频繁的调用,这样就会在一瞬间产生大量的临时对象
onDraw方法中不要做耗时的任务,也不能执行成千上万次的循环操作。否则这回导致View的绘制流程不流畅
内存优化:

尽量使用Android特有的数据容器。如SparseArray、SparseBooleanArray
不要使用过多的枚举类,枚举占用的内存空间比整形大
适当使用软引用和弱引用
尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄露

二面:

1:? 重载和重写的区别

方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载是一类中多态性的一种表现。

override(重写):

   1、方法名、参数、返回值相同。

   2、子类方法不能缩小父类方法的访问权限。

  ?3、子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。

   4、存在于父类和子类之间。

   5、方法被定义为final不能被重写。

 overload(重载):

  1、参数类型、个数、顺序至少有一个不相同。?

  2、不能重载只有返回值不同的方法名。

  3、存在于父类和子类、同类中。

2:Android应用的启动过程

时序图

3:TCP建立连接的过程

建立连接:

TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。

(1)第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。

(2)第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。

(3)第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。

完成三次握手,客户端与服务器开始传送数据

释放链接:

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个?FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

?CP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。

(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。?

(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。?

(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。?

(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。

4:传输层和网络层的联系

网络层负责ip数据报的产生以及ip数据包在逻辑网络上的路由转发。

传输层提供端到端通信服务层次,提供可靠及非可靠连接。

网络层只是根据网络地址将源结点发出的数据包传送到目的结点(点到点),其主要任务是:通过路由选择算法,为报文或分组通过通信子网选择最适当的路径。该层控制数据链路层与传输层之间的信息转发,建立、维持和终止网络的连接。具体地说,数据链路层的数据在这一层被转换为数据包,然后通过路径选择、分段组合、顺序、进/出路由等控制,将信息从一个网络设备传送到另一个网络设备。

而传输层则负责将数据可靠地传送到相应的端口(端到端),传输层提供了主机应用程序进程之间的端到端的服务。传输层利用网络层提供的服务,并通过传输层地址提供给高层用户传输数据的通信端口,使高层用户看到的只是在两个传输实体间的一条端到端的、可由用户控制和设定的、可靠的数据通路。

5:25匹马,5条跑道。要选出最快的前三名,最少要跑几次?

7次。将25匹马分成五批,跑五次,决胜出每批马的第一名。再让每批第一名的马跑一次,决定前三名,设为A,B,C。此时可确认跑得最快的为A,但是第二名和第三名不确定。于是可以让A1,A2, B,B1,C跑一次。(A1代表A组的第二名),决胜出前二名。就是最快的第二和第三名的马。

6:老王卖鞋,一双进价30元,老王赔本卖,只卖20元。有个骗子来买,给老王50元假钞。老王未能识别,又没有零钱,把这假钞拿到隔壁铺子的老李换了50元零钱,回来找了骗子30。隔壁很快发现问题,拿假钞来换,老王只好把自己的家底真钞50元换给隔壁。问老王损失了多少钱?

损失了60元,由题目可知老李没有任何损失。老王找了骗子30元,同时又得到了一双价值30的鞋子。赚了60元,因此可以得出老李亏了60元。

7:死锁产生的必要条件

互斥条件:请求的资源为临界资源
请求和保持条件:申请新资源,保持旧资源
不剥夺条件:已获得的资源,在使用完之前,不被外力剥夺
环路等待条件:互相等待资源
8:多少个进程竞争多少个临界资源会产生死锁?

两个或以上进程需要两个或以上资源

9:你怎么规划你以后的发展?

(接下来巴拉巴拉一大堆产品的问题,产品的优势、竞争力等等)

?

Hr面(20min)

cici

1、数组实现队列

2、gc的流程

点击看答案

可以查看 虚拟机 第5题

3、java软引用与弱引用区别

点击看答案

参考以前的读书笔记

4、java中的this编译时的原理

点击看答案

我们知道,this关键字主要有三个用途:

  • 调用本类中的属性
  • 调用本类中的其他方法
  • 调用本类中的其他构造函数

this编译时的原理:

  1. this指代的一定是对象(所以静态方法不能使用this),且该对象的静态类型就是就是当前类
  2. 实例方法以及 构造方法的第一个参数都是this(在构造方法之前,jvm其实已经给对象在堆中分配好了内存了,构造方法的作用是对类初始化
  3. this一般出现在方法中;如果this出现在方法体外部,如:在类A中定义了成员变量 A a = this;最终这行代码仍然会被放入 A 类的构造函数中执行

参考他人的博客

5、final变量用反射修改

6、HashMap的内部结构,给定一个key,如何找到对应的value,使用equal

7、volatile

8、Java线程池有什么作用

9、Java动态代理

10、handler机制

点击看答案

查看Android基础 第4题即可

11、android跨进程通信的方式

点击看答案

阅读以前的读书笔记

12、自定义控件方式

13、Canvas绘制过什么 手写功能

14、断点续传的实现

15、如何设计图片加载库

16、有看过哪些安卓的源码

  • Activity启动
  • handler
  • ThreadHandler
  • IntentThread

17、看过哪些开源项目

  • LeakCanary
  • Alpha
  • okhttp

18、app 启动速度的优化做过哪些

19、fresco加载图片原理 优势是什么

20、写程序时,堆和栈有什么优化点 内存回收时机 如何判断对象可被回收

21、引用计数法和gc root法

22、事件分发 cancel事件一般在什么时候被触发

点击看答案

参考源码理解中的12、13题

23、touchdelagate 一个父view只能设置一个delegate,如何解决设置多个

24、App整个架构了解么

25、mvvm data binding

26、webview

27、fragment startactivity

28、动画的原理

黄油计划 vsync

设计一个离线视频下载功能

Activity 启动流程

android app签名原理

Android查询资源文件layout原理

设计一个decode bitmap方法

启动Activity A后,按home键,再从桌面启动activity A , Activity A的生命周期

handler原理

onSaveInstanceState调用时机

Fragment View区别

Java内存管理和内存回收

Android scheme

Activity怎么管理自己的生命周期,ActivityThread怎么运作

消息事件分发

进程之间通讯

线程锁

touch事件分发原理scrollview和viewpager之间的滑动如何防止冲突,里面listitem也需要支持滑动怎么办

插件化的了解情况动态代理实现(自由发挥的)

如何实现一个拥有取出最小值方法的堆栈,要求算法的事件复杂度是O(E)如何算

二叉树节点之间的最小距离

两个链表,可能很长,实现求和(大数求和),结果也是单向链表

ActivityTask的使用

onNewIntent的调用时机

checkbox,up事件和down事件的区别

怎么用Standard方式来实现一个SingleTop启动的Activity

Http文件上传的具体过程

其实证书的本地校验还有一个步骤就是,将获取的新证书与老证书对比下,对比新旧证书

在kotlin中各个部分的执行顺序:

companion > init > constructor

自己写代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class KotlinTest {
init {
println("init");
}

constructor(){
println("constructor init");
}

companion object {
init{
println("companion init");
}
}
}

会输出:

companion init

init

constructor init

对称加密哪些

三次、四次握手

注意postInvalidate的使用

关于如何排查ANR

获取日志文件

首先,获取日志文件:

adb bugreport buglog

之后,就会显示进度生成,有个时候会生成不成功,没关系,再来一次

生成之后,这个文件被导入到当前项目的路径下,解压这个 buglog.zip 文件即可,打开 [机型]xxx.txt 查看里面的日志。

ANR类型和日志关键字

KeyDispatchTimeout-主要类型按键或触摸事件,input事件在5S内没有处理完成发生ANR

日志关键字:Reason: Input dispatching timed out xxxx

ServiceTimeout-bind,create,start,unbind等在主线程处理耗时,前台Service在20s内,后台Service在200s内没有处理完成发生ANR

日志关键字:Timeout executing service:/executing service XXX

3)BroadcastTimeout- BroadcastReceiver onReceiver处理事务时前台广播在10S内,后台广播在60s内没有处理完成发生ANR

日志关键字:Timeout of broadcast XXX/Receiver during timeout:XXX/Broadcast of XXX

4)ProcessContentProviderPublishTimedOutLocked-ContentProvider publish在10s内没有处理完成发生ANR
日志关键字:timeout publishing content providers

造成ANR的常见原因

  • 主线程耗时操作,如复杂的layout,庞大的for循环,IO等。
  • 主线程被子线程同步锁block
  • 主线程被Binder 对端block
  • Binder被占满导致主线程无法和SystemServer通信
  • 得不到系统资源(CPU/Memory/IO)

以上内容参考自简书上的博客简书博客二

频繁GC、OOM 的排查

Android 的profile 功能->memory -> dump ,得到 hprof 文件

AS 现在直接能看到各个对象了,或者按照以前的,将 hprof 文件转一道,使用 MAT 打开

两个人轮流抛硬币,正面朝上的赢,正反面概率一样,你应该先抛还是后抛

设先抛先吃的概率为p1, 后抛先吃的概率为p2

那么有:

p1 = 1/2 + 1/2 * p2

p1 + p2 = 1

解方程可得,

p1 = 2/3

参考自牛客网

查看运行的service

如果要查看当前手机上运行有哪些service(不可以先 adb shell 再执行后面的,否则显示的进程就不对了,至少我的是这样的):

adb shell dumpsys activity services

当然,肯定是可以加 grep 关键字过滤的:

adb shell dumpsys activity services | grep MiniAppPreService

同理,我们也可以通过 grep 过滤查看当前一组进程(下面示例是过滤出名字中包含 com.example.io 的):

adb shell ps | grep com.example.io

8.6 使TCP 连接安全,SSL

SSL (Secure Socket Layer )安全套接字层, SSL 版本 3的一个稍加修改的版本被称为 TLS(Transport Layer Security)

在 http 之下,tcp 之上,有 ssl 层。

ssl 握手流程:

总体来说,ssl 握手,首先要建立一个tcp 连接 ,其次在验证server 的真实性 ,最后 client 再将对称加密所需要的key通过公钥加密交给server。此后,两端通过对称加密来完成通信。整个过程如下图所示:

ssl握手整体步骤

抛开tcp连接的建立,详细步骤如下:

  1. client发送支持的算法列表以及一个随机数 x
  2. server 从接收的列表中选择一种对称算法、一种非对称算法 和 一种摘要算法,连同自己的证书 以及 随机数 y 一起返回
  3. client 验证该证书,提取公钥,并生成一个前主密钥,并用服务器的公钥加密这个 前主密钥,之后发送给server
  4. server 解密获得 主密钥
  5. client 发送自己这边所有握手的报文的一个 摘要
  6. server 端发送自己这边握手报文的一个 摘要

最后两个步骤保证了握手免受篡改危害。比如,在观察第一步的时候,客户端提供的算法列表,有些算法强,有些弱,因为还未协商,所以这张列表是以明文发送的。如果中间攻击者从中删除了较强的算法,迫使server 最后只能选择较弱的算法。

3.2 因特网提供的运输服务

TCP 协议提供的拥塞控制不一定能给通信进程带来直接好处,但能为因特网带来整体好处。

3.3 无连接运输:UDP

Dns 是一个通常使用 UDP 的应用层协议的例子。一台主机中的DNS应用程序想要进行一次查询时,它构造一个报文并将其交给UDP。如果没有查询到,要么告知应用程序不能响应,要么试图向另一个dns服务器发送查询。

为什么不是总选择TCP这种可靠连接呢?有以下几个原因:

  • 速度快。无需建立连接,不用像TCP 哪样三次握手然后建立连接(想想如果dns运行在tcp上,则会慢很多)
  • 更精细控制。使用UDP ,应用层可以更精细控制 何时、发送什么数据
  • UDP 无需维护连接状态。没有维护连接状态,也就不需要发送和跟踪 缓存、拥塞控制、序号确认 等工作,响应更快
  • 分组的首部开销小,TCP 有20个字节,而UDP 仅仅 8个字节

首先要提一下,UDP 是可以实现可靠数据传输的,可以通过在应用程序中建立可靠性机制来保证。这样无需受制于TCP 拥塞控制机制限制传输速率。

UDP 首部只有 源端口号,目的端口号,长度(首部+数据) 以及 校验和。校验和用于防止传输过程中引入的差错(路由器内存可能引入比特差错)

3.4 可靠传输的原理

TCP 是在不可靠的 IP 层之上实现可靠数据传输协议。

需要三种协议来处理比特差错的情况:

  • 差错检测。要有检测手段
  • 接收方反馈。
  • 重传

回退N步:允许发送放发送多个分组,而不需要等待确认,但是也受到滑动窗口的限制,不能超过最大分组数。如果出现超时,发送方重传所有已发送但还未被确认过的分组。当接收到一个连续的ack时,窗口就向前滑动(因为接收方是要求按序接收的,比如n还没收到,但是来了n+1,将会把n+1丢掉的)。
选择重传:按照滑动窗口发送时,接收方将确认一个正确接收的分组而不管其是否按序,时序的分组将被缓存直到所有丢失分组都受到位置,这样才一并交给上一层。避免丢弃导致无谓的重传

3.5 面向连接的传输:TCP

快速重传: 一旦收到3个冗余的ACK,TCP就会执行快速重传,即在该报文段的定时器过期之前重传丢失的报文段。

TCP让发送方维护一个接收窗口的变量来提供流量控制。

流量控制服务:限制发送速率,避免发送方使接收方缓存溢出的可能性。

如果客户端故意不发送ACK来完成第三次握手,那就是所谓的SYN洪范攻击。

拥塞控制:如果TCP发送方感知到网络没什么拥塞,则TCP发送方增加其发送速率;如果发送方感知有拥塞,则降低发送速率。

确定拥塞: 超时或者 收到3个冗余的 ACk,就认为发生了拥塞
如何控制:可以使用拥塞窗口(congestion window,用cwnd 来表示) 去控制发送方向网络中发送流量的速率。

TCP拥塞控制算法:

  • 慢启动:TCP 连接开始时,cwnd 通常设置一个较小的值,之后,每发送成功一次,cwnd就翻番。因此,tpc开始时较慢,但在慢启动过程中以指数增长。
  • 拥塞避免: 一旦进入拥塞避免状态,cwnd的值是上次遇到拥塞时的值的一半,即距离拥塞并不远。因此,tcp 每发送成功一次不再是翻番,而是增加一定的量,先行规律缓慢增长,比慢启动速率慢很多。
  • 快速恢复: 拥塞窗口减半cwnd=cwnd/2,之后执行拥塞避免算法,使拥塞窗口慢慢增大。

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