2020高级安卓面试总结- 安卓基础篇
Viewed绘制过程,自定义View
View绘制主要分为三步。分别是
- onMersure 计算View的尺寸大小,可能会触发多次
MeasureSpec
- UNSPECIFIED 父容器不对子布局有任何限制,要多大给多大(如: scrollview,listView)
- EXACTLY 父容器已经测量出子布局大小
- AT_MOST 父窗口限定了一个最大值给子布局。
- onLayout 计算View的布局, 只会触发,执行一次
- Ondraw 用于对视图进行绘制;主要通过canvas,paint,matrix去绘制
界面 16毫秒 刷新一次。会调用
View事件分发
事件的分发主要涉及到三个方法,一个是Dispatch,一个是intercept,一个是ontouch去,当一个事件产生的时候,会顺序调用这三个方法,如果。某个方法否为true 的话,事件就不会一层一层往下传,如果都没有处理,那么事件的响应 又会一层层的往上传
Handler 原理
类似一个生产者消费者模式, Handler将Message发消息队列MessageQueue里面,然后Looper会循环的读取Message,(然后调用Message的target,即附属的Handler的dispatchMessage()方法),拿到Message后将该消息的handleMessage()方法中,在里面完成更新UI操作。
BlockingQueue(阻塞队列) 生产者消费者模式 先进先出,
int 最大容量: 2^32 次方 20亿左右
long 2^64 次方:
生产者消费者模式
- 生产者生产对象放入缓冲区内,消费者从缓冲区内读取数据
- 当缓冲区满后,生产者不在生产
- 当缓冲区为零后,消费者不在消费
- 实现有 ,await/notify, blockingQueue 等
BlockingQueue是一个接口,它的实现类有ArrayBlockingQueue、DelayQueue、 LinkedBlockingDeque、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等,它们的区别主要体现在存储结构上或对元素操作上的不同,但是对于take与put操作的原理,却是类似的。
内存泄漏的场景和解决办法
- 静态对象导致的内存泄漏。 比如 单例类里面传入Activity
- 静态属性导致的; 比如把Activity当做静态属性
- 内部类导致的内存泄漏,内部类默认有持有外部类的引用。因此Activity的内部类会持有Activity
- 资源未释放导致的内存泄漏,通常有 IO流读写,数据库读写,Bitmap对象
总的来说就是: 短周期的对象持有了长周期的对象导致
内存优化
- Bitmap优化,
a 尺寸压缩,Bitmap.Option 里面的 InSampleSize
b 颜色质量:可以设置合适格式:比如;ARGB_8888 RGB_555 - 使用安卓提供的更省内存的数据结构:比如; 稀疏数组(SparseArray),ArrayMap
SparseArray <Interger,Object>这样的Hashmap而专门写的类,直接用单个数组来存储元素,使用二分法来查找
- 自定义View在类似onDraw这样的方法中创建对象,因为它会迅速占用大量内存,引起频繁的GC,导致内存抖动
Java 垃圾回收
Java里面的垃圾回收简称为,gc,是分为两个步骤,
- 第一个是查找垃圾内存
- 第二个是内存回收算法。
查找内存通常有两个方法,
- 一种是对象的引用计数 当这个对象有被人引用的时候,计数加一 。反之,计数器减一。Java里面并没有采用这种方法。
- 另外一种是可达性分析。就是从根节点gc root一层一层的往下找,如果这个对象没有被找到,那么就认为这个对象是可回收。
回收算法又分为以下几种,
- 标记清除。
- 复制
- 标记整理
- 分代收集。 把内存分为新生代和年老代。新生代又分为Eden和survivor。创建对象的时候会先使用Eden区, 当Eden区触发容量阈值,minior Gc,回收Eden区的对象。使用的时候复制回收算法,对象复制到 suvivor区
新生 代使用复制回收
年老代使用标记整理。
运行内存 分区
-
堆 方法区
-
栈 本地方法栈 程序计数器
-
堆原来存储类的实例和数组
-
方法区原来存储类的信息,静态变量,常量等。
HashMap
O(1)
- 采用数组+链表的结构来实现, Java 1.8开始,当链表的长度,超过8 的时候,链表改成 平衡二叉树
- 初始容量默认为16,负载因子默认是0.75, 扩容方法:翻倍
- getHashCode() 返回 对象的内存地址+ 算法
- 查找的使用根据 hashCode 和 容量进行取模。
- 但是由于 取模 只有低位区的数据才会进行计算,这样导致冲突会比较大,因此hashmap里面又加入了干扰函数,
- 把高位区的和低数据位区的数据在跟自身 异或,这样混合后数据就包含高位和低位数据,冲突就比较少了。
-
key不能为基本数据类型,则是因为基本数据类型不能调用其hashcode()方法和equals()方法,进行比较,所以HashMap集合的key只能为引用对象
-
equals当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的语义一致性,该协定声明相等对象必须具有相等的哈希码。
-
在集合类(HashMap,HashSet等)中判断两个对象是否相等有如下规则:
-
如果两个对象哈希值不同,那么这两个对象不相等。如果相同,则调用equals()方法判断,如果equals()方法返回true,则这两个对象相等,否则不相等。
-
值传递和对象传递
ConcurrentHashMap 线程安全且高效的HashMap, 默认是:
= 1.7 数组+链表, 使用Segment 分段锁,来实现同步,
- 1.8 CAS + synchronized ,红黑树
- Hashtable线程安全,锁住整个对象,数组+链表 ,因为是整体的锁,基本被淘汰了
CAS:
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术,其实就是一个事务操作,是不可拆分的原子操作
是一种有名的无锁算法,即不使用锁的情况下实现多线程之间的变量同步
- 在CAS中有三个参数:内存值V、旧的预期值A、要更新的值B,当且仅当内存值V的值等于旧的预期值A时才会将内存值V的值修改为B,否则什么都不干
CAS可以保证一次的读-改-写操作是原子操作。
ReentrantLock 可重入锁
- Java除了使用关键字synchronized外,还可以使用ReentrantLock实现独占锁的功能
- synchronized是独占锁,加锁和解锁的过程自动进行,,易于操作,但不够灵活
- ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
- synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
- ReenTrantLock可以指定是公平锁还是非公平锁
悲观锁:每次访问数据线程都是处于阻塞的状态,这样提高安全性,像synchronize 读锁,写锁都是用到了悲观锁
乐观锁:每次访问数据线程都是运行状态,这样可以提高数据吞吐量,就假定不会发生并发冲突,最后通过比较数据来查看是否真的发生了冲突,并交由用户来决定。最典型的实现方式是CAS(compare and swap):
自旋锁的定义:当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。这种采用循环加锁 -> 等待的机制被称为自旋锁(spinlock)。
自旋锁与互斥锁
- 自旋锁与互斥锁都是为了实现保护资源共享的机制。
- 获取互斥锁的线程,如果锁已经被占用,则该线程将进入阻塞状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。
- 互斥锁 线程被阻塞后便进入(Linux)内核态,这个会导致系统在用户态与内核态之间来回切换,开销比较大,会影响性能.
- 自旋锁可以让线程一直处于用户态,减少不必要的上下文切换
- 斐波那契数列 来实现等待
volatile 原理
-
CPU的处理效率和 内存的处理效率不是一个数量级。这样就可能出现CPU值跟内存的值不一致, 因此在多线程的时候,可能会出现A线程更新的值,B 线程去取的时候不是最新的。
-
在多线程的时候,使用valatie 可以保证CPU的值可以马上刷新到内存里面,这样取到的数据就是一致的。
软、弱引用
- 强引用 > 软引用 > 弱引用 > 虚引用
- 软引用 则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存,软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
- 弱引用- 拥有更短暂的生命周期,GC扫描到就会回收
ArrayList小结
- ArrayList是List接口的一个可变大小的数组的实现
- ArrayList的内部是使用一个Object对象数组来存储元素的
- 初始化ArrayList的时候,可以指定初始化容量的大小,如果不指定,就会使用默认大小,为10
- 当添加一个新元素的时候,首先会检查容量是否足够添加这个元素,如果够就直接添加,如果不够就进行扩容,扩容为原数组容量的1.5倍
- 当在index处放置一个元素的时候,会将数组index处右边的元素全部右移
数组,数组扩容 System.arraycopy
冷启动与热启动是什么
- 后台没有该应用的进程,系统会重新创建一个新的进程分配给该应用 会先调用 Application.onCreate方法,再调用 Activity的创建方法。
如果启动时间比较久,就会一样白屏效果: 因此要减少 Application的任务。 可以采用异步,或者延迟的方式实现。
- 热启动是指 应用 从后台切换到前台。只会重新 初始化 Activity的创建方法
LRU是原理
LRU Least Recently Use, 最近最少使用的数据就会被移除的算法
- 底层是用哪个 LinkedHashMap 来实现,使用了一个双向链表来存储Map中的 对象顺序关系,
- 每次调用get,则将该对象移到链表的尾端。
- 调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。
java中equals,hashcode和==的区别
1.基本数据类型,也称原始数据类型,, 2.引用类型(类、接口、数组)
- 三个操作默认都是比较内存的地址。
1.(==)进行比较的时候,比较的是他们在内存中的存放地址
- equal 内部是通过 “==”来实现的。
- equal 和 hashCode可以被重写。
- hashcode 在对象的内存地址基础上经过特定算法返回一个hash码
- 覆盖equals时总要覆盖hashCode
加密:
1 对称 DES, AES
2. 非对称 RSA
单例模式:
https://www.jianshu.com/p/6043b3af3598
1.懒汉式单例
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁synchronized 才能保证线程安全,(如果两个线程同时调用getInstance方法)但加锁会影响效率。
2.饿汉式单例
3.holder模式()- 静态内部类
3、String、StringBuffer、StringBuilder区别
String: 使用final 实现的字符串变量,值不可以改变,每次创建相当于生成一个新的对象
StringBuffer:字符串变量 (线程安全)
StringBuilder:字符串变量(线程不安全) 确保单线程下可用,效率略高于StringBuffer
Serializable 和Parcelable的对比
- Parcelable 性能高,Parcelable是直接在内存中读写,把一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的基本数据类型
- Serializable代码量少,写起来方便,使用了反射,序列化的过程较慢。这种机制会在序列化的时候创建许多的临时对象。
onSaveInstanceState 时机
- onSaveInstanceState方法在onPause方法之后执行在onStop方法之前执行
- 一句话概括就是,不是用户主动退出某个Activity或者跳转到其他Activity的情况下就会触发onSaveInstanceState
场景
- 启动一个新的Activity时;
- 1.当用户按下Home键时;
- 3.按下电源键(关闭屏幕显示)时;
- 5.屏幕方向切换时,如从横屏切换到竖屏;
##5、进程和线程的区别
进程是cpu资源分配的最小单位,线程是cpu调度的最小单位。
进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。
一个进程内可拥有多个线程,进程可开启进程,也可开启线程。
一个线程只能属于一个进程,线程可直接使用同进程的资源,线程依赖于进程而存在。
16、说说你对Java反射的理解
JAVA反射机制是在运行时根据 类路径,创建类的实例,获取类的完整信息,包含属性和方法
我的研发成就
-
模块间路由,模块间调用 router
a. 根据注解,收集所有的路由信息,包括路由和具体类的路径。
b. 把路由信息写在Java源代码里面
c. 初始化路由表
d. 根据路由地址找到对应的类路径,使用反射实例类并调用 -
性能监测工具:APM, AOP
-
APT 注解处理器
-
数据埋点
RecyclerView 缓存
https://www.jianshu.com/p/efe81969f69d
- LayoutManager负责 布局
- Adapter,默认实现ViewHolder
- ItemDecorter
- ItemAnimator
缓存
- mAttachedScrap 存储的是当前还在屏幕中的ViewHolder
- mCachedViews 存储屏幕外预取的ViewHolder ,
- mRecyclerPool 根据ViewType来缓存ViewHolder
Glide
- 三层缓存, 内存- 硬盘- 网络
- 内存友好,内存缓存更小图片,图片默认使用默认 RGB565 而不是 ARGB888,支持 Gif、WebP、缩略图。甚至是 Video
异步框架
- RxJava
- AsycTask
- Thread+ Handler
- IntentService
设计模式
https://juejin.im/post/5b3cddb6f265da0f8145c049
– 23种
- 分为3类, 创建型,结构型 ,行为型
- 创建型 : (工厂方法)getSystemService,(建造者)AlertMessage(单例)
- 结构型:(适配器)Adapter (装饰者)IO流(组合),(代理)
- 行为型: Chain of Responsibility(职责链),Command(命令),Iterator(迭代器),Observer(观察者)Strategy(策略)
HashMap;ArrayList,LinkedList用法有什么要注意的
https://www.cnblogs.com/gossip/p/8745656.html
- ArrayList:数组集合。 查询、修改、新增(尾部新增)快,删除、新增(队列中间)慢,适用于查询、修改较多的场景。
构造ArrayList时尽量指定容量,减少扩容时带来的数组复制操作,如果不知道大小可以赋值为默认容量10。
ArrayList的所有方法都没有进行同步,因此它不是线程安全的。
- LinkedList:双向链表集合。查询、修改慢(需要遍历集合),新增,删除快(只需要修改前后节点的链接即可),适用于新增、删除较多的场景。
- HashMap:结合数组和链表的优势,期望做到增删改查都快速,时间复杂度接近于O(1)。当hash算法较好时,hash冲突较低。适用于增删改查所有场景。
注解介绍下
注解 类似是给类或者方法贴上标签,标签带有额外的数据。 在编译和运行的时候可以根据这个注解做一些额外的功能。
APT 介绍
泛型中类型擦除是什么
Java中的泛型是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。编译的时候,泛型的代码和类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List和List等类型,在编译之后都会变成List。JVM看到的只是List。
当然在这之前,JVM会自己帮我们做类型检查和类型转换
OkHttp原理?
https://www.cnblogs.com/Android-Alvin/p/12530226.html
https://blog.csdn.net/u012881042/article/details/79759203
- OkHttp设置了默认的最大并发请求量 maxRequests = 64 和单个host支持的最大并发量 maxRequestsPerHost = 5。
- response.body().string()只能调用一次,本质是输入流的读操作
- 回调接口的onFailure方法和onResponse执行在子线程
- 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接
- 内置连接池,支持连接复用,减少延迟
- 透明的GZIP压缩减少响应数据的大小
- 缓存响应内容,避免一些完全重复的请求
- 请求失败时自动重试主机的其他ip,自动重定向
OkHttp3的最底层是Socket,而不是URLConnection
socket发起网络请求的流程一般是:
(1). 创建socket对象;
(2). 连接到目标网络;
(3). 进行输入输出流操作。 okio
Okhttp同步和异步使用
同步和异步流程
Dispatcher
拦截器
缓存
连接池复用
okio
https://www.jianshu.com/p/2fff6fe403dd
- inputStream, outputStream
- -> 从输入流读出到缓冲区
-> 从输入流缓冲区copy到 b[]
-> 将 b[] copy 到输出流缓冲区
-> 输出流缓冲区读出数据到输出流
== 上面情况存在冗余copy操作,Okio应运而生。
addInterceptor与addNetworkInterceptor的区别
二者通常的叫法为应用拦截器和网络拦截器,两者层次不一样。
- 应用拦截器因为只会调用一次,而网络拦截器一次调用代表了一定会发起一次网络通信,一旦发生错误重试或者网络重定向,网络拦截器可能执行多次
常用的拦截器
- RetryAndFollowUpInterceptor 重试
- BridgeInterceptor 拼接请求头
- CacheInterceptor 缓存
- ConnectInterceptor 连接
- CallServerInterceptor 请求服务器
- LogInterceptor
- StechoIntceptor
HTTP
- GET 请求资源,参数都在URL中。
- HEAD 与GET基本一致,不过其不返回消息体,通常用于速度或带宽优先的场景,比如检查资源有效性,可访问性等等。
- POST 提交表单,修改数据,参数在body中。
- PUT 与POST基本一致,最大不同是幂等操作。
- DELETE 删除指定资源。
RxJava
- 使用可观测序列组成的一个异步库
- 响应式编程
- 链式结构的执行顺序
- flatmap :把几个小的list转换到一个大的list。
- Map 把数组流中的每一个值,使用所提供的函数执行一遍,一一对应。得到元素个数相同的数组流
Activity四种启动模式
- standard 模式,默认是会创建一个新实例
- singleTop 如果在栈顶的话会复用,否则创建新实例,适合推送消息详情页
- singleTask 如果栈里面有 话,会复用,在其上所有Activity全部出栈,使得其位于栈顶,app首页
- singleInstance, 实例会单独创建在 独立一个栈里面,全局会复用。
杀掉应用
- Application里面创建列表弱引用所有Activity
- 类似EventBus,注册一个统一的观察者
- System.exit() ,停止程序的虚拟机;是通过PID去杀死进程。
- Process.killProcess(pid)
- 上面两种 生命周期都不会被调用,直接退出应用。
Kotlin :协程
- 线程是CPU调度的单位,新开个线程需要额外的资源空间,线程切换或线程阻塞的开销都比较大。
- 协程是组织好的代码流程,开发者控制的,用户态的线程,非常轻量级,可以关联到任意线程执行,也可以是当前线程执行,使用了时间复用的方式
LeakCanary
通过监听 Activity.onDestroy() 回调之后,通过弱引用(WeakReference)对象、ReferenceQueue和 GC来观测Activity引用的内存泄露情况,如果发现了未被回收的Activity对象,在找到该Activity对象是否被其他对象所引用,如果被其他对象引用,就进行 heap dump生成完整的内存引用链(最短引用链),然后notify出来
LeakCanary通过ApplicationContext统一注册监听的方式,来监察所有的Activity生命周期,并在Activity的onDestroy时,执行RefWatcher的watch方法,该方法的作用就是检测本页面内是否存在Activity内存泄漏问题。
流程
- 判定是否回收(KeyedWeakReference是否存在该引用), Y -> 退出, N -> 向下执行
- 手动触发GC
- 判定是否回收, Y -> 退出, N-> 向下执行
- 两次未被回收,则分析引用情况,dumpHeap : 这个方法是生成一个文件,来保存内存分析信息
但并不能100%保证;因此LeakCanary是存在极小程度的误差的
2.0 不需要初始化原理 :
- 初始化代码放在 ContentProvider.onCreate里面
- Application->attachBaseContext =====>ContentProvider->onCreate =====>Application->onCreate =====>Activity->onCreate
广播 BroadcastReceiver
- 静态注册 配置在 xml中
- 动态注册
- 本地广播,只是本APP能收到: LocalBroadcastManager
- 为标准广播 和 有序广播 sendOrderBroadcast
AIDL
- AIDL是跨进程的通信协议,分为客户端和服务端
- Binder其实是跨进程通信的一种。在linux 上跨进程IPC通信还有很多种:包括: 管道、消息队列、共享内存和 Socket
- Socket 作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,缺乏安全性,难以使用。Binder 只需要一次数据拷贝,性能上仅次于共享内存。
MVVM,MVP
- 先从MVC开始: C层是通常是Activity或者Fragment,里面包含各种事件和逻辑,各个数据的更新,各种View的更新,当逻辑复杂的时候,C层会很庞大和臃肿,
- MVP C在抽离出P层,之间通过协议接口进行调用,这样P相当于Activity的代理,里面包含各种逻辑和界面更新,P层部分可以复用。P层就比较内聚和低耦合,
缺点是:需要多写很多的协议类,比较笨重,工作量大,不好维护 - MVVM。 一个View对应一个VIewModel。 对ViewModel的变更会马上自动更新到View上。 这样我们的程序只需要关心数据和ViewModel.提高了代码的可复用性,也可以做单元测试,而且也做到了响应式编程。
- Android里面是通过 DataBinding来实现
- React, Vue, Flutter
- React 组件化,面向状态编程 Reflux
其实: 不管架构怎么变换: 本质其实一直没有变化,主要是以下两点
- 外部低耦合,内部高内聚
- 关注点分离原则
二叉树深度
JobShedule 8.0后推荐用 Workmanager
- Google推荐的后台运行API
- JobScheduler和WorkManager都只能在APP存活的时候执行,但是定时器是一直工作的。
- 关闭APP再启动,JobScheduler并不能够直接继续运行,但是WorkManager可以。
数据库,Room, greendao
- 对象关系映射(ORM)库, 如果有后端的基础就很容易理解:类似 Hibernate 和 ibatis
- 这样后,简单的CRUD完全可以通过直接变成 对象 的处理,不需要写SQL语句。
retrofit 是一个封装了Restfull 请求的框架,仅负责 网络请求接口的封装
原理
- BaseURL多个
- 调用原理
- URL服务端发下发起请求
Protocol Buffers
- Google推出的一种更更小,更高效的 序列化结构数据。 我们传输数据通常是json,xml,但是也可以使用这个
- 他的读写和编码解码会比Json 效率更快。
静态代码块执行顺序
- 父类静态代码块
- 子类静态代码块
- 父类构造代码块
- 父类构造函数
- 子类构造代码块
- 子类构造方法
final
- 当用final修饰一个类时,表明这个类不能被继承
- final修饰变量只能被赋值一次,赋值后不再改变。
- final修饰方法禁止该方法在子类中被覆盖
数据库三大范式通俗理解
- 第一范式就是属性不可分割,每个字段都应该是不可再拆分的
- 第二范式就是要求表中要有主键,表中其他其他字段都依赖于主键
- 第三范式外键约束就好了,表中不能有其他表中存在的、存储相同信息的字段
工作项目难点,如何克服。
- ARouter 源码阅读
- Javassit,ASM java字节码操纵框架,编写插件. 全新的领域
3.ASM 跟传说中的AOP三剑客APT、aspectJ、Javassit有什么关系?
Javassist & ASM 对比
- Javassist源代码级API比ASM中实际的字节码操作更容易使用
- Javassist在复杂的字节码级操作上提供了更高级别的抽象层。Javassist源代码级API只需要很少的字节码知识,甚至不需要任何实际字节码知识,因此实现起来更容易、更快。
- Javassist使用反射机制,这使得它比运行时使用Classworking技术的ASM慢。
- 总的来说ASM比Javassist快得多,并且提供了更好的性能。Javassist使用Java源代码的简化版本,然后将其编译成字节码。这使得Javassist非常容易使用,但是它也将字节码的使用限制在Javassist源代码的限制之内。
- 总之,如果有人需要更简单的方法来动态操作或创建Java类,那么应该使用Javassist API 。如果需要注重性能地方,应该使用ASM库
onNewIntent
- 当Activity被设以singleTop模式启动,处于当前应用的Activity任务栈中;
- SingleTask和SingleInstance(且已经在任务栈中存在实例)的情况下,再次启动它们时才会调用
常用的测试框架
- JUnit4
- AndroidJUnitRunner
- Mockito
- Espresso
客户端是怎么和前端页面交互的
- javascriptInterface
- webViewClient.shouldOverrideUrlLoading()
- webChromeClient.onconsoleMessage()
- webChromeClient.onJspompt()
==
WebView注册一个名叫“jsInterface”的对象,然后在JS中可以访问到jsInterface这个对象,就可以调用这个对象的一些方法,最终可以调用到Java代码中,从而实现了JS与Java代码的交互。
EventBus
EventBus是一个事件发布/订阅总线,使用观察者实现,弱引用
Service和IntentService 区别和使用
- :Service服务是长期运行在后台;
- :它不是单独的进程,因为它和应用程序在同一个进程;
- :也不是单独的线程,它跟线程没有任何关系,所以不能进行耗时操作;
- :如果直接把耗时操作放在Service中的onStartCommand()中,可能发生ANR,如果有耗时操作,就必须开启一个单独的线程来处理;
为了解决这样的问题,就引入了IntentService;
1>:启动方式和Service一样,都是startService();
2>:继承于Service,包含Service所有特性,包括生命周期,是处理异步请求的一个类,;
3>:一般自定义一个InitializeService继承Service,然后复写onHandleIntent()方法,在这个方法中初始化这些第三方的,来执行耗时操作;
6>:不需要像在Service中一样,手动开启线程,任务执行完成后不需要手动调用stopSelf()方法来停止服务,系统会自动关闭服务;
Service与Activity之间通信
- 通过broadcast(广播)的形式
- 通过Binder对象具体实现两个组件通信
Android动态?简单说一下新版本的特性
Android10
- Foldables(折叠屏)
- 黑暗主题
- 手势导航
- 用户隐私
- 默认使用TLS 1.3
Android 9.0
- 刘海屏支持
- 多摄像头支持
- ImageDecoder 替换 BitmapFactory
- 神经网络API
- Http网络请求 都必须加密
Oreo 8.0
- 画中画模式
- 快捷菜单
- 自适应图标(应用图标适配)
Nougat 7.0
- 多窗口支持(分屏显示)
- VR支持
Marshmallow 6.0
- 运行时权限
ANR 是什么
Android Not Response -->应用程序无响应
-
按键或触摸事件在特定时间内无响应(5s)
-
BroadcastReceiver在特定时间内无法处理完成(10s)
-
Service在特定的时间内无法处理完(20s)
避免: -
避免在activity里面做耗时操作
-
耗时操作(数据库操作,I/O操作,网络操作等)在work thread中执行,利用handler处理UI线程与work thread的数据交互
四种常见 ExecutorService
- Executors.newCacheThreadPool()
- Executors.newFixedThreadPool(int n):
- Executors.newScheduledThreadPool(int n)
- Executors.newSingleThreadExecutor()
https://www.jianshu.com/p/c8d265c75ab5
TCP
https://blog.csdn.net/songzi1228/article/details/80653713
三次握手:
== 面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式
跟接打电话一样,只有双方都具备了发送和接受能力,我们才能进行接下来的通话。
- 第一次的握手,是为了确认客户端的发送没有问题;客户端发送syn包
- 第二次的握手,是为了确认服务端的接受和发送没问题; 服务端发送SYN+ACK包
- 第三次的握手,是为了确认客户端的接受没有问题; 客户端发送确认包ACK
四次挥手
关闭TCP连接:四次挥手
- 主机1(可以使客户端,也可以是服务器端),向主机2发送一个FIN报文段
- 主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,我“同意”你的关闭请求
- 主机2向主机1发送FIN报文段,请求关闭连接
- 主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段就关闭连接。
Activity生命周期
几个特殊需要注意生命周期示例
1.父Activity启动子Activity,父Activity调用顺序如下
onCreate() -> A
onStart() -> A
onResume() ->A
onPause() -> A
onCreate() -> B
onStart() -> B
onResume() ->B
onStop() -> A (A OnStop是在B”可操作”后执行)
2。 子Activity关闭
onPause() -> B
onStop()->B
onStart() -> A
onResume() ->A
onDestory() ->B
面向对象的四个基本特征
- 抽象:
- 封装:
- 继承:
- 多态性:
心跳包
3连接保持(即心跳机制)
- 一般APP实现连接保持的方式无非是采用应用层的心跳,通过心跳包的超时和其他条件(网络切换)来执行重连操作。那么问题来了:为什么要使用应用层心跳和如何设计应用层心跳。众所周知TCP协议是有KEEPALIVE这个设置选项,设置为KEEPALIVE后,客户端每隔N秒(默认是7200s)会向服务器发送一个发送心跳包。
但实际操作中我们更多的是使用应用层心跳。原因如下:
KEEPALIVE对服务器负载压力比较大(服务器大大是这么说的…);
socks代理对KEEPALIVE并不支持; - 部分复杂情况下KEEPALIVE会失效,如路由器挂掉,网线(移动端没有网线…)直接被拔除。
移动端在实际操作时为了节约流量和电量一般会在心跳包上做一些小优化: - 精简心跳包,保证一个心跳包大小在10字节之内;
- 心跳包只在空闲时发送;
- Android微信智能心跳方案。为了简单,此处规定应用在前台时,8秒发送一个心跳包,切换到后台时,30秒发送一次,
消息可达(即QoS机制)
在移动网络下,丢包,网络重连等情况非常之多,为了保证消息的可达,一般需要做消息回执和重发机制。参考易信,每条消息会最多会有3次重发,超时时间为15秒,同时在发送之前会检测当前连接状态,如果当前连接并没有正确建立,缓存消息且定时检查(每隔2秒检查一次,检查15次)。所以一条消息在最差的情况下会有2分钟左右的重试时间,以保证消息的可达。
TCP
TCP的优点体现在稳定、可靠上,在传输数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完之后,还会断开连接用来节约系统资源。
UDP的优点体现在快,比TCP稍安全,UDP没有TCP拥有的各种机制,是一个无状态的传输协议,所以传递数据非常快,没有TCP的这些机制,被攻击利用的机制就少一些,但是也无法避免被攻击。
进程保活
- 前台进程>可见进程>service进程>后台进程>空进程