美文网首页
Android面试复习点

Android面试复习点

作者: 唯吾知足_c35c | 来源:发表于2020-08-30 14:22 被阅读0次

Synchronized

配合 wait/notify/notifyAll synchronized (thisLock){thisLock.notify()}

1.每个对象都有一个监视器锁:monitor,同步代码块会执行 monitorenter 开始,motnitorexit 结束

2.Wait/notify 就依赖 monitor 监视器,所以在非同步代码块中执行会报 IllegalMonitorStateException 异常

3.锁升级 偏向锁 自旋锁 轻量级锁 重量级锁 。当有其他线程竞争,首先是自旋锁,循环竞争锁(浪费cpu) 一定次数后 会进入阻塞锁(进入阻塞队列)

简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)

  1. 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
  2. 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized

synchronized实现可见性的原理

1.获得互斥锁

2.清除工作内存

3.从主存中拷贝内容到工作内存中

4.执行代码

5.更新到主存中

6.释放锁

synchronized具有原子性

volatile具有可见性,没有原子性

synchronized 修饰静态方法和非静态方法的区别

一个锁的是类对象,一个锁的是实例对象。

若类对象被lock,则类对象的所有同步方法(static synchronized func)全被lock。

若实例对象被lock,则该实例对象的所有同步方法(synchronized func)全被lock

Lock

https://blog.csdn.net/qpzkobe/article/details/78586619

可配合Condition (signal()/await())

同样也会偏向锁

AbstractQueuedSynchronizer会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态,经过调查线程的显式阻塞是通过调用LockSupport.park()完成,而LockSupport.park()则调用sun.misc.Unsafe.park()本地方法,再进一步,HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞

lock 和 synchronized
  • synchronized 是 jvm实现;Lock 是代码层级(CASAQSLockSupport等去实现)
  • synchronized 会自动释放锁;lock 需要手动释放,所以需要写到 try catch 块中并在 finally 中释放锁
  • synchronized 无法中断等待锁;lock 可以中断
  • synchronized 无公平锁,lock默认非公平锁,但可以设置公平锁
  • 竞争资源激烈时,lock 的性能会明显的优于 synchronized

可中断的含义

如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁

b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;

c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到 锁定 或者当前线程被别的线程中断(其他线程持有当前线程的thread t1 执行 t1.interrupt())

死锁

1.互斥 2 占有且等待 3.不可抢占 4.循环等待

乐观锁和悲观锁
  • 悲观锁:线程一旦得到锁,其他线程就挂起等待,适用于写入操作频繁的场景;synchronized 就是悲观锁
  • 乐观锁:假设没有冲突,不加锁,更新数据时判断该数据是否过期,过期的话则不进行数据更新,适用于读取操作频繁的场景
  • 乐观锁 CAS:Compare And Swap,更新数据时先比较原值是否相等,不相等则表示数据过去,不进行数据更新
  • 乐观锁实现:AtomicInteger、AtomicLong、AtomicBoolean

签名机制

消息摘要:在消息数据上,执行一个单向的 Hash 函数,生成一个固定长度的Hash值

数字签名:一种以电子形式存储消息签名的方法,一个完整的数字签名方案应该由两部分组成:签名算法和验证算法

数字证书:一个经证书授权(Certificate Authentication)中心数字签名的包含公钥拥有者信息以及公钥的文件

V2 apksigner 对zip压缩包的整个文件验证, 签名后不能修改压缩包(包括zipalign), 对V2签名的apk解压,没有发现签名文件,重新压缩后V2签名就失效, 由此可知: V2签名是对整个APK签名验证

​ V2签名优点很明显: 1. 签名更安全(不能修改压缩包) 2. 签名验证时间更短(不需要解压验证),因而安装速度加快

V1 jarsigner 对zip压缩包的每个文件进行验证, 签名后还能对压缩包修改(移动/重新压缩文件) 对V1签名的apk/jar解压,在META-INF存放签名文件(MANIFEST.MF, CERT.SF, CERT.RSA), 其中MANIFEST.MF文件保存所有文件的SHA1指纹(除了META-INF文件), 由此可知: V1签名是对压缩包中单个文件签名验证

在v1版本的签名中,签名以文件的形式存在于apk包中,这个版本的apk包就是一个标准的zip包,V2和V1的差别是V2是对整个zip包进行签名,而且在zip包中增加了一个apk signature block,里面保存签名信息

v2版本签名块(APK Signing Block)本身又主要分成三部分:

  • SignerData(签名者数据):主要包括签名者的证书,整个APK完整性校验hash,以及一些必要信息
  • Signature(签名):开发者对SignerData部分数据的签名数据
  • PublicKey(公钥):用于验签的公钥数据

v3版本签名块也分成同样的三部分,与v2不同的是在SignerData部分,v3新增了attr块,其中是由更小的level块组成。每个level块中可以存储一个证书信息。前一个level块证书验证下一个level证书,以此类推。最后一个level块的证书,要符合SignerData中本身的证书,即用来签名整个APK的公钥所属于的证书。优点:apk密钥轮替 ,这使应用能够在 APK 更新过程中更改其签名密钥

ANR

http://gityuan.com/2019/04/06/android-anr/

  • anr 分类

    • 主线程 5s 内没有处理完输入事件
    • service 阻塞 20s
    • 前台广播阻塞 10s 或后台广告阻塞 20s
    • ContentProvider publish 在 20s 内没有处理完
  • 监控 anr

    • 1.Android 5.0 以下监听 traces.txt 文件写入
    • 2.每隔 5s 向主线程发送消息判断主线程是否阻塞

anr收集过程

  • 将am_anr信息输出到EventLog,也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息

  • 收集以下重要进程的各个线程调用栈trace信息,保存在data/anr/traces.txt文件

    • 当前发生ANR的进程,system_server进程以及所有persistent进程(persistent进程越多越耗时)
    • audioserver, cameraserver, mediaserver, surfaceflinger等重要的native进程
    • CPU使用率排名前5的进程
  • 将发生ANR的reason以及CPU使用情况信息输出到main log

  • 将traces文件和CPU使用情况信息保存到dropbox,即data/system/dropbox目录

  • 对用户可感知的进程则弹出ANR对话框告知用户,对用户不可感知的进程发生ANR则直接杀掉

service超时 onstartcommand 会等待sp的持久化

receiver超时 只有静态receiver 会等待sp的持久化(sp未持久化时,会通过sp(queue-work-loop)中转汇报任务).静态注册的广播, 以及发送的本身就是串行广播, 都会采用串行方式处理(会anr)

provider超时 provider的超时是在provider进程首次启动的时候才会检测,当provider进程已启动的场景,再次请求provider并不会触发provider超时

input 超时 扫雷机制, 后续新生成的输入事件,检查前面的事件是否anr

有哪些路径会引发ANR? 答应是从埋下定时炸弹到拆炸弹之间的任何一个或多个路径执行慢都会导致ANR(以service为例),可以是service的生命周期的回调方法(比如onStartCommand)执行慢,可以是主线程的消息队列存在其他耗时消息让service回调方法迟迟得不到执行,可以是SP操作执行慢,可以是system_server进程的binder线程繁忙而导致没有及时收到拆炸弹的指令.

发生ANR时从trace来看主线程却处于空闲状态或者停留在非耗时代码的原因有哪些?可以是抓取trace过于耗时而错过现场,可以是主线程消息队列堆积大量消息而最后抓取快照一刻只是瞬时状态,可以是广播的“queued-work-looper”一直在处理SP操作

广播

并行广播 无超时情况 是通过 binder oneway的方式

串行广播 有anr超时 前台队列 10s 后台队列60s

Binder

接收端(server)已和内核空间映射,只有发送端(client)需要copy from user 到内核空间

binder_proc是binder驱动自己定义的结构体,是一个进程在binder驱动内的信息保存者, 每个进程只会open binder一次,binder进而将这个特殊的结构体和该进程打开binder时的fd对应的file结构体对应起来

将申请出来的binder_proc对象指针保存在打开/dev/binder对应的file结构的private_data中,这是binder驱动能够识别不同进程的关键, binder驱动在进程打开他时给其一个特殊的binder_proc对象,进程将此对象保存在打开binder文件的file结构体中, 后面只要还使用这个file结构体和binder驱动交互, binder启动就可以根据private_data中的binder_proc对象来识别该进程

通过serviceManager获取和注册binder服务。当应用获取ServiceManager服务的代理时,它的handle句柄固定为0,所以才不需要去查找

BpBinder(客户端): Binder的代理对象,内部有一个成员变量mHandle

BBinder: Binder 本地服务对象

binder_ref与binder_node都存在于内核空间

binder_node是实体对象、binder_ref是引用对象

binder_node指向BBinder,被binder_ref引用,binder_ref被BpBinder引用

在binder call完成之后,调用Parcel.recycle来完成释放内存的

Binder线程池中的线程数量是在Binder驱动初始化时被定义的; 进程池中的线程个数上限为15个,加上主Binder线程,一共最大能存在16个binder线程,当Binder线程都在执行工作时,也就是当出现线程饥饿的时候,从别的进程调用的binder请求如果是同步的话,会在todo队列中阻塞等待,直到线程池中有空闲的binder进程来处理请求

Exception

DeadObjectException最常见的原因就是Binder服务端进程已经死亡.

服务端此时的缓存内存空间(1016K)已经被占满了,Binder驱动就认为服务端此时并不能处理这个调用,那么就会在C++层抛出DeadObjectException到Java层

当发生RemoteException, RuntimeException, OutOfMemoryError, 对于非oneway的情况下都会把异常传递给调用者,RuntimeException被Binder服务端线程捕捉,随后将异常信息写入到reply中,发回Binder客户端进程,最后会客户端binder线程抛出这个异常,如果没有捕捉这个RuntimeException,那么Binder客户端进程会Crash

协议

BC_TRANSACTION BR_REPLY BR_TRANSACTION BC_REPLY

BC_: 全称Binder Command, 进程发送给Binder驱动数据时携带的协议

BR_: 全称Binder Return,Binder驱动发送给进程数据时携带的协议

死亡机制

当Binder服务无效时,驱动程序会发送死亡通知给给各个已注册服务的客户端进程,以方便客户端做些销毁之类的操作

透传fd

https://blog.csdn.net/fyfcauc/article/details/50765372###

ART和Dalvik区别

Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。ART则完全改变了这套做法,在应用安装的时候就预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。

ART优点:

系统性能的显著提升应用启动更快、运行更快、体验更流畅、触感反馈更及时。更长的电池续航能力支持更低的硬件

ART缺点:

更大的存储空间占用,可能会增加10%-20%更长的应用安装时间.

在程序运行过程中Dalvik虚拟机不断的进行将字节码转换为机器码的工作。

而Art引入了AOT这种预编译技术,在应用程序的安装过程中已经将所有的字节码编译为了机器码,在运行的时候直接调用。Art极大的提高了程序的运行效率,同时减少了手机的耗电量,在垃圾回收机制上也有很大的优化,但是Art模式下应用程序的安装需要消耗更多的时间,同时也需要跟多的安装空间。

  • Dalvik 是Android4.4及以下平台的虚拟机。
  • Art 是在Android4.4以上平台使用的虚拟机。

Android 7.0后 。 app安装后,会随着应用的运行,逐步增量编译应用的热函数。

JVM 和Dalvik虚拟机的区别

  • JVM:.java -> javac -> .class -> jar -> .jar架构: 堆和栈的架构.
  • DVM:.java -> javac -> .class -> dx.bat -> .dex架构: 寄存器(cpu上的一块高速缓存)

数据库

库锁

SQLite 使用锁逐步上升机制,为了写数据库,连接需要逐级地获得排它锁。SQLite 有 5 个不同的锁状态:未加锁(UNLOCKED)、共享锁(SHARED)、保留(RESERVED)、未决(PENDING)和排它(EXCLUSIVE)。每个数据库在同一时刻只能处于其中一个状态。每种状态(未加锁状态除外)都有一种锁与之对应。锁的状态以及状态的转换如下图所示:

最初的状态是未加锁状态,在此状态下,连接还没有存取数据库。当连接到了一个数据库,甚至已经用 BEGIN 开始了一个事务,连接都还处于未加锁状态。

未加锁状态的下一个状态时共享状态。为了能够从数据库中读(不写)数据,连接必须首先进入共享状态,也就是说首先要获得一个共享锁。多个连接可以同时获得并保持共享锁,也就是说多个连接可以同时从一个数据库中读数据。但哪怕是只有一个共享锁还没有释放,也不允许任何连接写数据库。

如果一个连接想要写数据库,它必须首先获得一个保留锁。一个数据库上同时只能有一个保留锁。保留锁可以与共享锁共存,保留锁是写数据库的第一阶段。保留锁既不阻止其它拥有共享锁的连接继续读数据库,也不阻止其它连接获得新的共享锁。一旦一个连接获得了保留锁,它就可以开始处理数据库修改操作了,尽管这些修改只能在缓冲区中进行,而不是实际地写到磁盘。对读出内容所做的修改保存在内存缓冲区中。当连接想要提交修改(或事物)时,需要将保留锁提升为排它锁。为了得到排它锁,还必须首先将保留锁提升为未决锁。获得未决锁之后,其它连接就不能再获得新的共享锁了,但已经拥有共享锁的连接仍然可以继续正常读数据库。此时,拥有未决锁的连接等待其它拥有共享锁的连接完成工作并释放其共享锁。一旦所有其它共享锁都被释放,拥有未决锁的连接就可以将其锁提升至排它锁,此时就可以自由地对数据库进行修改了。所有以前对缓冲区所做的修改都会被写到数据库文件。

数据库查询优化

1.事务 假设此时有N条数据,那么数据的执行流程是 创建事务 -> 执行插入或更新操作 -> 提交事务,这样的流程会执行N次。如果我们手动创建了事务,则执行流程为: 创建事务 -> 执行N条SQL数据操作 -> 提交事务,这样的流程会执行1次

2.对于批量处理插入或者更新的操作,我们可以使用显示编译来做到重用SQLiteStatement

3.索引 建立索引

4.分库分表 大表联合小表

5.查询时,只返回自己需要的值和结果

6.如果主键不是整数值,可以without rowid建表

7.使用wal模式,加快并发

8.EXPLAIN QUERY PLAN 测试 SQL 语句的查询计划,是全表扫描还是使用了索引,以及具体使用了哪个索引等

wal

写入者仅将新内容附加到 WAL 文件的末尾。因为写入不做任何会干扰读取数据行为的事情,所以写和读可以同时运行。但是,由于只有一个 WAL 文件,所以一次只能有一个写入器

每当发生写操作时,写入者都会检查指针取得了多少进展,如果整个 WAL 已传输到数据库并进行了同步,并且如果没有读取正在使用 WAL,则写入者将 WAL 重新从 0 开始,并在 WAL 开始时进行新交易。这种机制可以防止 WAL 文件无限制地增长

在读数据库时,SQLite 将在 WAL 文件中搜索,找到最后一个写入点,记住它,并忽略在此之后的写入数据(这保证了读和写可以并发执行);随后它确定所要读的数据所在页是否在 WAL 文件中,如果在,则读 WAL 文件中的数据,如果不在,则直接读数据库文件中的数据

优点:

  1. 读操作不会阻塞写操作,同时写操作也不会阻塞读操作。写和写依然隔离
  2. 在大多数操作场景中,与回滚日志相比,WAL 相当快。

缺点:

  1. 为满足 WAL 和相关共享内存的需要,使用 WAL 引入了里两个额外的半持久性文件-wal 和-shm。对于那些使用SQLite 数据库作为应用程序文件格式是不具有吸引力的。这也影响了只读环境,因为-shm文件必须是可写的,并且/或数据库所在目录也必须是可写的。
  2. 对于非常大的事务,WAL 的性能将会降低。虽然 WAL 是一个高性能选项,但是非常大或运行时间非常长的事务会引入额外的开销。
Cursor

的实现是分配一个固定 2MB 大小的缓冲区 Cursor Window,这在查询数据量较小时可能不一定划算;对于结果集大于 2MB 的情况,遍历途中还会引发 Cursor 重查询,这个消耗就相当大了,而且数据的获取中间要经历两次内存拷贝

View

其排版效率:LinearLayout = FrameLayout >> RelativeLayout(要遍历2次)

setContentView

https://www.jianshu.com/p/7e7eff8ff64a

1.getWindow() 为phoneWindow

2.installDecor(),在phoneWindow中创建decorview。 generateLayout()中,加载R.layout.screen_simple(上面的ViewStub就是咱们的appBar,下面的FrameLayout的id为content)

3.以content为父布局,inflate我们自己的布局文件

fragment

setArguments Activity重新创建时(横竖排切换),会重新构建它所管理的Fragment,原先的Fragment的字段值将会全部丢失,但是通过Fragment.setArguments(Bundle bundle)方法设置的bundle会保留下来。所以尽量使用Fragment.setArguments(Bundle bundle)方式来传递参数

  • FragmentManager

    • 是一个抽象类,它定义了对一个 Activity/Fragment 中 添加进来的 Fragment 列表Fragment 回退栈的操作、管理方法
    • 还定义了获取事务对象的方法
    • 具体实现在 FragmentImpl 中
  • FragmentTransaction

    • 定义了对 Fragment 添加、替换、隐藏等操作,还有四种提交方法
    • 具体实现是在 BackStackRecord 中(通过链表的形式管理)
recycleview

https://mp.weixin.qq.com/s/8xNiW1zOF6K5AsU_iByXnQ

  • 一级缓存:mAttachedScrap 和 mChangedScrap ,用来缓存还在屏幕内的 ViewHolder

    • mAttachedScrap 存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找 ViewHolder
    • mChangedScrap 表示数据已经改变的 ViewHolder 列表, 存储 notifyXXX 方法时需要改变的 ViewHolder
  • 二级缓存:mCachedViews ,用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则移除旧 ViewHolder

  • 三级缓存:ViewCacheExtension ,开发给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存。

  • 四级缓存:RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,就会把 ViewHolder 存入RecyclerViewPool 中。

    • 按照 Type 来查找 ViewHolder
    • 每个 Type 默认最多缓存 5 个
    • 可以多个 RecyclerView 共享 RecycledViewPool
view绘制机制和加载过程

https://www.jianshu.com/p/060b5f68da79

1.ViewRootImpl会调用performTraversals()->内部会调用performMeasure()、performLayout、performDraw()。

performMeasure测量xml中所有控件的宽高 对于view:它的宽高由自己和父布局决定

比如: 如果父布局是 wrap_content,就算子布局是 match_parent,测量模式也是 AT_MOST;

如果父布局是 match_parent,子布局是 match_parent,测量模式 EXACTLY;

对于ViewGroup:先测量每个子view宽高,然后根据子view宽高计算自己宽高;

从measure()方法的源码中我们可以知道,只有以下两种情况之一,才会进行实际的测量工作:

  • forceLayout为true:这表示强制重新布局,可以通过View.requestLayout()来实现;

  • needsLayout为true,这需要specChanged为true(表示本次传入的MeasureSpec与上次传入的不同),并且以下三个条件之一成立:

    • sAlwaysRemeasureExactly为true: 该变量默认为false;
    • isSpecExactly为false: 若父View对子View提出了精确的宽高约束,则该变量为true,否则为false
    • matchesSpecSize为false: 表示父View的宽高尺寸要求与上次测量的结果不同

performLayout()使用setFrame()设置本View的四个顶点位置。在onLayout(抽象方法)中确定子View的位置,如LinearLayout会遍历子View,循环调用setChildFrame()—>子View.layout()(摆放子view)

(自定义viewgroup时,measure只能管到本层 ,再内部的view要layout去摆放)

getWidth()和getMeasureWidth()的区别?

getMeasureWidth()在measure()后可获得,getWidth()在layout()后获得

getMeasureWidth()的值是通过setMeasureDimension()设置,getWidth()通过视图的右坐标减去左坐标计算出来的

performDraw()会调用最外层ViewGroup的draw():其中会先后调用background.draw()(绘制背景)、onDraw()(绘制自己)、dispatchDraw()(绘制子View)、onDrawScrollBars()(绘制滚动条)。根据代码看只有dirtyOpaque为false时才进行绘制

5.MeasureSpec由2位SpecMode(UNSPECIFIED、EXACTLY(对应精确值和match_parent)、AT_MOST(对应warp_content))和30位SpecSize组成一个int,DecorView的MeasureSpec由窗口大小和其LayoutParams决定,其他View由父View的MeasureSpec和本View的LayoutParams决定。ViewGroup中有getChildMeasureSpec()来获取子View的MeasureSpec。

MeasureSpec:UNSPECIFIED 一般不会对子View做任何限制,用于ListView

MeasureSpec:EXACTLY 父视图对子视图指定了一个确定尺寸,比如我们子View的MATCH_PARENT,或者精确具体值

MeasureSpec:AT_MOST 父视图对子视图指定了一个最大尺寸,比如我们子View的WRAP_CONTENT

6.三种方式获取measure()后的宽高:

  • 1.Activity#onWindowFocusChange()中调用获取
  • 2.view.post(Runnable)将获取的代码投递到消息队列的尾部。
  • 3.ViewTreeObservable.
RequestLayout和Invalidate

https://www.jianshu.com/p/a6ea264a07b8

requestLayout方法会导致View的onMeasure、onLayout、onDraw方法被调用;invalidate方法则只会导致View的onDraw方法被调用

在View的requestLayout方法中,首先会设置View的标记位,PFLAG_FORCE_LAYOUT表示当前View要进行重新布局,PFLAG_INVALIDATED表示要进行重新绘制。requestLayout方法中会一层层向上调用父布局的requestLayout方法,设置PFLAG_FORCE_LAYOUT标记,最终调用的是ViewRootImpl中的requestLayout方法.

由于requestLayout方法设置了PFLAG_FORCE_LAYOUT标记位,所以measure方法就会调用onMeasure方法对View进行重新测量。在measure方法中最后设置了PFLAG_LAYOUT_REQUIRED标记位,这样在layout方法中就会执行onLayout方法进行布局流程.在onLayout最后清除PFLAG_FORCE_LAYOUT和PFLAG_LAYOUT_REQUIRED标记位

(1)requestLayout方法会标记PFLAG_FORCE_LAYOUT,然后一层层往上调用父布局的requestLayout方法并标记PFLAG_FORCE_LAYOUT,最后调用ViewRootImpl中的requestLayout方法开始View的三大流程,然后被标记的View就会进行测量、布局和绘制流程,调用的方法为onMeasure、onLayout和onDraw。并不是所有的view,只是这一条链上的从父到子view

(2)invalidate它的过程和requestLayout方法方法很像,但是invalidate方法没有标记PFLAG_FORCE_LAYOUT,所以不会执行测量和布局流程,而只是对需要重绘的View进行重绘,也就是只会调用onDraw方法,不会调用onMeasure和onLayout方法。scheduleTraversals方法中调用了mChoreographer.postCallback,Vsync回调后执行performTraversals

https://www.jianshu.com/p/f2b51180b705

View事件分发

https://www.jianshu.com/p/238d1b753e64

mFirstTouchTarget 链表 存储 已获得点击事件的view

子view 获得点击事件前提

1.如果View可见并且没有播放动画canViewReceivePointerEvents方法判断

2.点击事件的坐标落在View的范围内isTransformedTouchPointInView方法判断(调用View的pointInView方法进行判断坐标点是否在View内)

当子View处理了事件则mFirstTouchTarget 被赋值,并终止子View的遍历

对于View(注意!ViewGroup也是View)而言,如果设置了onTouchListener,那么OnTouchListener方法中的onTouch方法会被回调。onTouch方法返回true,则onTouchEvent方法不会被调用(onClick事件是在onTouchEvent中调用)所以三者优先级是onTouch->onTouchEvent->onClick

View 的onTouchEvent 方法默认都会消费掉事件(返回true),除非它是不可点击的(clickable和longClickable同时为false),View的longClickable默认为false,clickable需要区分情况,如Button的clickable默认为true,而TextView的clickable默认为false

View的setOnClickListener会默认将View的clickable设置成true。 View的setOnLongClickListener同样会将View的longClickable设置成true。

拦截Down的后续事件

假设ViewGroup B没有拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件。

  • DOWN事件传递到C的onTouchEvent方法,返回了true。
  • 在后续到来的MOVE事件,B的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给B;这个MOVE事件将会被系统变成一个CANCEL事件传递给C的onTouchEvent方法
  • 后续又来了一个MOVE事件,该MOVE事件才会直接传递给B的onTouchEvent()
  1. 后续事件将直接传递给B的onTouchEvent()处理
  2. 后续事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
  • C再也不会收到该事件列产生的后续事件。

案例:一个大的listiew内有一个小的listview,当内部的listview滑倒底部,大的listview开始滑动。

​ 小的listview滑倒底部后,不要再消费ontouchevent事件,touch事件会返回到上级

Android 绘制原理

https://www.jianshu.com/p/81a9b891c5e3

软件绘制刷新逻辑:

  • 默认情况下,View的clipChildren属性为true,即每个View绘制区域不能超出其父View的范围。如果设置一个页面根布局的clipChildren属性为false,则子View可以超出父View的绘制区域。

  • 当一个View触发invalidate,且没有播放动画、没有触发layout的情况下:

    • 对于全不透明的View,其自身会设置标志位PFLAG_DIRTY,其父View会设置标志位PFLAG_DIRTY_OPAQUE。在draw(canvas)方法中,只有这个View自身重绘。
    • 对于可能有透明区域的View,其自身和父View都会设置标志位PFLAG_DIRTY
    • clipChildren为true时,脏区会被转换成ViewRoot中的Rect,刷新时层层向下判断,当View与脏区有重叠则重绘。如果一个View超出父View范围且与脏区重叠,但其父View不与脏区重叠,这个子View不会重绘。
    • clipChildren为false时,ViewGroup.invalidateChildInParent()中会把脏区扩大到自身整个区域,于是与这个区域重叠的所有View都会重绘。

Context

https://www.jianshu.com/p/f499afd8d0ab

[图片上传失败...(image-349526-1598768489439)]

Context数量 = Activity数量 + Service数量 + Application数量(单进程时为1)

activity的context 被赋值了 ActivityThread、LoadedApk、activityToken、Configuration

ContextImpl.createActivityContext(ActivityThread , LoadedApk , IBinder , int displayId , Configuration)

service的context 被赋值了 ActivityThread、LoadedApk

ContextImpl.create App Context(ActivityThread , LoadedApk)

ContextWrapper 的成员变量 mBase 可以用来存放系统实现的 ContextImpl,这样我们在调用如 Activity 的 Context 方法时,都是通过静态代理的方式最终调用到 ContextImpl 的方法。我们调用 ContextWrapper 的 getBaseContext 方法就能拿到 ContextImpl 的实例

ContextThemeWrapper 提供一些关于主题,界面显示的能力,有自己的 Theme 以及 Resource。凡是跟 UI 有关的,都应该用 Activity 作为 Context 来处理,否则要么会报错(dialog要依附于activity),要么 UI 会使用系统默认的主题(inflate)

Dialog 是应用窗口类型,Token必须是Activity的Token。Application或者Service的Context没有attach wms,token为空

broadcastReceiver中的context

静态注册 ReceiverRestrictedContext 封装了application的context,限制使用,不允许bindservice

动态注册 哪里注册 哪里传入

Activity

Activity启动
  • ** ActivityManagerService** 组件通信系统核心管理类 (ActivityManagerNative)IPC通信
  • ** ActivityStackSupervisor** 管理整个手机的Activity任务栈
  • ActivityStack Activity栈(任务栈)
  • ** PackageManagerService** 主要负责对系统的apk进行管理,不管是系统apk(/system/app),还是我们手工安装上去的,系统所有的apk都是由其管理的。
  • ** ActivityThread** Activity的入口是onCreate方法,Android上一个应用的入口是ActivityThread。和普通的Java类一样有一个main方法。用于控制与管理一个应用进程的主线程的操作,包括管理与处理activity manager发送过来的关于activities、广播以及其他的操作请求

ActivityManagerService和ActivityStack位于同一个进程中,而ApplicationThread和ActivityThread位于另一个进程中。其中,ActivityManagerService是负责管理Activity的生命周期的,ActivityManagerService还借助ActivityStack是来把所有的Activity按照后进先出的顺序放在一个堆栈中;对于每一个应用程序来说,都有一个ActivityThread来表示应用程序的主进程,而每一个ActivityThread都包含有一个ApplicationThread实例,它是一个Binder对象,负责和其它进程进行通信。

1.Activity中最终到startActivityForResult()(mMainThread.getApplicationThread()传入了一个ApplicationThread检查APT)->Instrumentation#execStartActivity()和checkStartActivityResult()(这是在启动了Activity之后判断Activity是否启动成功,例如没有在AM中注册那么就会报错)->ActivityManagerNative.getDefault().startActivity()(类似AIDL,实现了IAM,实际是由远端的AMS实现startActivity())->ActivityStackSupervisor#startActivityMayWait()->ActivityStack#resumeTopActivityInnerLocked

->ActivityStack#startPausingLocked

->ApplicationThread#schedulePauseActivity

->ActivityThread#handlePauseActivity()

​ ->Instrumentation#callActivityOnPause()

​ ->Activity#performPause() 内部会调到 onPause()

->ActivityManagerNative.getDefault().activityPaused(token)

->ActivityManagerService#activityPaused

->ActivityStack#activityPausedLocked()

->ActivityStack中#resumeTopActivityUncheckedLocked()

->ActivityStackSupervisor#realStartActivityLocked()->ApplicationThread#scheduleLaunchActivity()(这是本进程的一个线程,用于作为Service端来接受AMS client端的调起)->ActivityThread#handleLaunchActivity()(接收内部类H的消息,ApplicationThread线程发送LAUNCH_ACTIVITY消息给H)->最终在ActivityThread#performLaunchActivity()中实现Activity的启动完成了以下几件事:

2.从传入的ActivityClientRecord中获取待启动的Activity的组件信息

3.创建类加载器,使用Instrumentation#newActivity()加载Activity对象

4.调用LoadedApk.makeApplication方法尝试创建Application,由于单例所以不会重复创建。

5.创建Context的实现类ContextImpl对象,并通过Activity#attach()完成数据初始化和Context建立联系,因为Activity是Context的桥接类,最后就是创建和关联window,让Window接收的事件传给Activity,在Window的创建过程中会调用ViewRootImpl的performTraversals()初始化View。

6.Instrumentation#callActivityOnCreate()->Activity#performCreate()->Activity#onCreate().onCreate()中会通过Activity#setContentView()调用PhoneWindow的setContentView()更新界面。

Activity.startActivity -> Instrumentation.execStartActivity -> ActivityManagerNative.getDefault().startActivity -> Binder ->ActivityStarter.startActivityMayWait -> startActivityLocked -> startActivityUnChecked -> ActivityStackSupervisor.resumeFocusedStackTopActivityLocked -> ActivityStatk.resumeTopAcitivityUncheckLocked -> resumeTopActivityInnerLocked -> ActivityStackSupervisor.startSpecificActivityLocked -> realStartActivityLocked -> Binder -> ApplictionThread.scheduleLauchActivity -> H -> ActivityThread.handleLaunchActivity ->performLaunchActivity -> Instrumentation.newActivity 创建 Activity -> callActivityOnCreate 一系列生命周期

img img

Service

http://gityuan.com/2016/03/06/start-service/

bindService_flow.png
IntentService

IntentService是继承Service的一个抽象类,它在onCreate()方法中创建了一个HandlerThread,并启动该线程。HandlerThread是带有自己消息队列和Looper的线程,根据HandlerThread的looper创建一个Handler,这样IntentService的ServiceHandler的HandleMessage()方法就运行在子线程中。HandleMessage中调用了onHandleIntent()方法,它是一个抽象方法,继承IntentService类需要实现该方法,把耗时操作放在onHandleIntent()方法中,等耗时操作运行完成后,会调用stopSelf()方法,服务会调用onDestory()方法消毁自己。如果onHandleIntent()方法中的耗时操作未运行完前就调用了stopSelf()方法,服务调用onDestory()方法,但耗时操作会继续运行,直至运行完毕。如果同时多次启动IntentService,任务会放在一个队列中,onCreate()和onDestory()方法都只会运行一次

BroadCastReceiver

http://gityuan.com/2016/06/04/broadcast-receiver/

  1. 当发送串行广播(ordered=true)的情况下:
    • 静态注册的广播接收者(receivers),采用串行处理;
    • 动态注册的广播接收者(registeredReceivers),采用串行处理;
  2. 当发送并行广播(ordered=false)的情况下:
    • 静态注册的广播接收者(receivers),依然采用串行处理;
    • 动态注册的广播接收者(registeredReceivers),采用并行处理;

静态注册的广播往往其所在进程还没有创建,而进程创建相对比较耗费系统资源的操作,所以 让静态注册的广播串行化,能防止出现瞬间启动大量进程的喷井效应

ContentProvider

http://gityuan.com/2016/07/30/content-provider/

contentprovider加载和创建都是在主线程完成,并且还都是在应用启动过程完成,ContentProvider 的生命周期默认在 Application onCreate 之前。这也验证了文章开头为大家介绍的启动性能,在使用 ContentProvider 需要注意的“暗坑”,自定义 ContentProvider 类的构造函数、静态代码块、onCreate 函数都尽量不要做耗时的操作,会拖慢启动速度

ContentProvider.query bind+ashmem

内存adj

adj 算法 https://www.jianshu.com/p/73db4dece5d3

http://gityuan.com/2018/05/19/android-process-adj/

ams中的lowermemorykiller 会选择adj最大的(adj相同的话 选择内存最大)杀掉

Android 特性

android P
  • 室内WIFI定位
  • “刘海”屏幕支持
  • 安全增强等等优化很多
android Q
  • 限制后台拉activity
  • 非系统应用无法获取到IMEI
  • 外部存储的隔离存储;公共媒体文件的存储
  • 后台地理位置权限
  • 适配折叠屏
  • 全屏手势导航,应用充分利用全面屏,建议应用不要覆盖系统手势,否则可能导致手势冲突,影响用户使用习惯
  • 应用使用黑色主题,灵活动态变化
  • Android Q,受限制的非SDK接口数量增多了

Handle

Loop

loop.prepare 准备loop,在threadlocal中插入 loop对象

loop.mylooper 获得线程唯一的loop对象

loop.loop 开启消息分发循环,哪个线程调用这个函数,就在哪个线程处理handle消息

HandleThread

在线程的run()中执行了loop.prepare loop.loop();

切换线程

MessageQuene#enqueueMessage

把消息按照时间顺序放入MessageQuene中(此时是在主线程执行)等待被Looper.loop()的无限循环获取方法取出并传给handler处理(此时是在HandlerThread线程中执行

数据结构

红黑树

和平衡二叉树比,深度会保证是O(logn)

​ 1、每个节点不是红色就是黑色。

2、根节点为黑色。

3、每个叶子节点都是黑色的。

4、每个红色节点的子节点都是黑色。

5、任意节点,到其任意叶节点的所有路径都包含相同的黑色节点

每次插入的节点都是红色(然后再旋转),是为了快速满足 1 2 3 5 这个4个条件

直播协议

hls 苹果公司的 基于ts 和 m3u8

优点: 1. 通过http协议,不需要考虑代理和防火墙

​ 2.ios有天然的支持

​ 3.短时间的分片,切换码率播放平滑

​ 4.适合有点播 回放需求

缺点: 1 延迟大,由于ts文件是一段一段的,所以一般有2-3个ts文件时间长的延迟 20s走有

​ 2.基于http短连接,http基于tcp,hls需要与服务器不断的连接

rtmp adobe 为flash播放提供

优点 1. 延迟少,通常为1-3 秒

\2. 基于tcp长连接

缺点 1.ios上没有原生的支持

rtsp 包含rtp 和rtcp 主要用于推流端

rtp 实时传输协议 udp

​ rtcp 实时传输控制协议 tcp

网络

Socket

java.net包

阻塞通信,服务器每个socket连接,都要分配一个线程

SocketChannel/ServerSocketChannel

java.nio包

非阻塞通信,服务器多个socket连接,只有单个线程

Tcp/udp

TCP的连接是全双工(可以同时发送和接收)

socket.accept 发生在3次握手之后,从已建立的连接队列中获取

http优化

1.dns优化 使用httpdns HTTPDNS 不是使用 DNS 协议,向 DNS 服务器传统的 53 端口发送请求,而是使用 HTTP 协议向 DSN 服务器的 80 端口发送请求(1.防止dns劫持 2.降低时延 3.httpdns服务器增加流量调度,网络灰测等功能)

2.连接复用 连接池管理

3.整体压缩 gzip google_brotil facebook_Z-standard

4.body压缩 protocol buffer

5.http头压缩 类似http2

6.图片压缩 webp hevc SharpP

http2

https://www.jianshu.com/p/c7cde0077892?utm_source=desktop&utm_medium=timeline

1.二进制协议

2.多路复用 所有的请求在一个连接上通信(防止新连接的重新握手和慢启动拥塞)

3.客户端可以和服务端双向实时通信

4.http头 字典压缩

多路复用原理

http传输的request response 都是基于文本的要顺序传送

http1.1的keep-alive 默认是true,解决了多次连接的问题,但是依然有2个效率问题

1.串行的文件传输。b文件要等a文件传输完

2.连接数过多

http2内部添加了 二进制数据帧 和 流的概念

其中帧对数据的顺序进行了顺序标识,可以基于流进行并行传输,http2对同一域名所有的请求都基于 流

网络质量监控

1.使用okhttpeventlistener 通过各种情况的返回参数 进行监控

QUIC

优点:1.解决了连接复用中的队首阻塞

​ 2.基于udp,可以灵活控制拥塞协议 比如 google 的bbr算法

​ 3.连接迁 由于udp通过 connect id的特性,使得切换网络的时候不需要重连

​ 4.再次连接(rtt0)。第一次连接时候quic携带tls的密钥证书之类的,当连接成功过一次之后,那么客户端存一个 source-address token 以后再连接,通过这个token验证客户端身份。

缺点 : 1. 局域网路由,交换机,防火墙等会禁止udp 443 通行,建立连接成功率 95%

​ \2. 运营商针对udp通道支持不足

  1. 使用udp不一定比tcp更快,客户端可以同时使用tcp和quic竞速,选择更优链路
Https

[图片上传失败...(image-ec9004-1598768489439)]

客户端使用服务端返回的信息验证服务器的合法性,包括

证书是否过期

发行服务器证书的CA是否可靠

发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”

服务器证书上的域名是否和服务器的实际域名相匹配

Tls 1.3

https://www.jianshu.com/p/8c65c0f562f2

  • 删除了不安全的密钥
  • 压缩了以前的 “Hello” 协商过程,删除了 “Key Exchange” 消息,把握手时间减少到了 “1-RTT”,并且使用了0-RTT恢复
https sni

问题由来:由于服务器能力的增强,在一台物理服务器上部署多个虚拟主机已经成为十分流行的做法了。在过去的 HTTP 时代,解决基于名称的主机同一 ip 地址上托管多个网站的问题并不难。当一个客户端请求某特定网站时,把请求的域名作为主机头(host)放在 http header 中,从而服务器根据域名可以知道把该请求引向哪个域名服务,并把匹配的网站传送给客户端。但是此方式到 https 就失效了,因为 SSL 在握手的过程中,不会有 host 信息,所以服务端通常返回配置中的第一个可用证书,这就导致不同虚拟主机上的服务不能使用不同证书(但在实际中,证书通常是与服务对应。)。

为了解决此问题,产生了 SNI,SNI 中文名为服务器名称指示,是对 SSL/TLS 协议的扩展,允许在单个 IP 地址上承载多个 SSL 证书。SNI 的实现方式是将 HTTP 头插入到 SSL 的握手中,提交请求的 Host 信息,使得服务器能够切换到正确的域并返回相应的正确证书

SNI(Server Name Indication)定义在RFC 4366,是一项用于改善SSL/TLS的技术,在SSLv3/TLSv1中被启用。它允许客户端在发起SSL握手请求时(具体说来,是客户端发出SSL请求中的ClientHello阶段),就提交请求的Host信息,使得服务器能够切换到正确的域并返回相应的证书

Vsync

  • android最高60FPS,是VSYNC及决定的,每16ms最多一帧
  • VSYNC要客户端主动申请,才会有
  • 有VSYNC到来才会刷新
  • UI没更改,不会请求VSYNC也就不会刷新
  • UI局部重绘其实只是省去了再次构建硬件加速用的DrawOp树(复用上一帧的)

Classloader

https://www.jianshu.com/p/c9fd64e0b934 dex加载过程

其他的类加载器都必须属于自己的父加载器(不是以继承方式实现父子关系,而是使用组合包含关系)

Bootstrap ClassLoader 用来加载 rt.jar,rt 意为 runtime ,是 JDK 为我们提供的核心 jar 包。 Extension ClassLoader 用来加载 ext*.jar 或 Djava.ext.dirs 指定目录的 jar 包,它和 Bootstrap ClassLoader 实际上都是为了加载 JDK 中指定的特定 jar 包的。 App ClassLoader用来加载应用程序的 ClassLoader。

一个运行的android程序至少有2个classloader 一个是bootclassLoader(系统启动时创建) 一个是pathclassloader(加载本身的base.apk)

pathClassLoader 已安装的dex dexClassLoader 所有的dex

SharedPreferences

https://www.jianshu.com/p/8eb2147c328b

当我们首次创建 SharedPreferences 对象时,会根据文件名将文件下内容一次性加载到 mMap(SharedPreferencesImpl 成员) 容器中,每当我们 edit 都会创建一个新的 EditorImpl 对象,当修改或者添加数据时会将数据添加到 mModifiled (EditorImpl 成员)容器中,然后 commit 或 apply 操作比较 mMap 与 mModifiled 数据修正 mMap 中最后一次提交数据,然后写入到文件中。而 get 直接从 mMap 中读取。试想如果此时你存储了一些大型 key 或 value 它们会一直存储在内存中得不到释放

commit 同步 有返回值 存mMap 存文件

apply 异步 无返回值 存mMap QueuedWork 添加任务 异步存文件

apply anr SP 调用 apply 方法,会创建一个等待锁放到 QueuedWork 中,并将真正数据持久化封装成一个任务放到异步队列中执行,任务执行结束会释放锁。Activity onStop 以及 Service 处理 onStop,onStartCommand 时,执行 QueuedWork.waitToFinish() 等待所有的等待锁释放

解决:Activity 的 onStop,以及 Service 的 onStop 和 onStartCommand 都是通过 ActivityThread 触发的,ActivityThread 中有一个 Handler 变量,我们通过 Hook 拿到此变量,给此 Handler 设置一个 callback,Handler 的 dispatchMessage 中会先处理 callback。

1.在 Callback 中调用队列的清理工作

2.队列清理需要反射调用 QueuedWork

数据未改变不会写文件,每次写入文件,都要全量写入,强制落盘(sync)

​ 我们应用程序平时用到的 read/write 操作都属于标准 I/O,也就是缓存 I/O(Buffered I/O)。它的关键特性有:

(1)对于读操作来说,当应用程序读取某块数据的时候,如果这块数据已经存放在页缓冲中,那么这块数据就可以立即返回给应用程序,而不需要经过实际的物理读盘操作。

(2)对于写操作来说,应用程序也会将数据先写到页缓冲(Page Cache)中去,数据是否被立即写到磁盘上去取决于应用所采用写操作的机制。默认系统采用的是延迟写机制,应用程序只需要将数据写到页缓冲中去就可以了,完全不需要等数据全部被写回到磁盘,系统会负责定期地将放在页缓冲中的数据刷到磁盘上。

SharedPreferences 在写入文件时采用强制落盘机制来保证数据 “不丢失”:FileUtils.sync()。

SharedPreferences 的写入操作,首先是将源文件备份:mFile.renameTo(mBackupFile) 再写入所有数据,只有写入成功,并且通过 sync 完成落盘后,才会将 Backup(.bak) 文件删除。如果写入过程中进程被杀,或者关机等非正常情况发生。进程再次启动后如果发现该 SharedPreferences 存在 Backup 文件,就将 Backup 文件重名为源文件,原本未完成写入的文件就直接丢弃,这样最多也就是未完成写入的数据丢失,它能保证最后一次落盘(真正落盘)成功后的数据。也正是这个 BackUp 机制,导致多进程可能会丢失新写入的数据。但也不是只有多进程场景才会发生数据丢失的情况

epoll

epoll的通俗解释是一种当文件描述符的内核缓冲区非空的时候,发出可读信号进行通知,当写缓冲区不满的时候,发出可写信号通知的机制

  1. select和poll的动作基本一致,只是poll采用链表来进行文件描述符的存储,而select采用fd标注位来存放,所以select会受到最大连接数的限制,而poll不会。
  2. select、poll、epoll虽然都会返回就绪的文件描述符数量。但是select和poll并不会明确指出是哪些文件描述符就绪,而epoll会。造成的区别就是,系统调用返回后,调用select和poll的程序需要遍历监听的整个文件描述符找到是谁处于就绪,而epoll则直接处理即可。
  3. select、poll都需要将有关文件描述符的数据结构拷贝进内核,最后再拷贝出来。而epoll创建的有关文件描述符的数据结构本身就存于内核态中,系统调用返回时利用mmap()文件映射内存加速与内核空间的消息传递:即epoll使用mmap减少复制开销。
  4. select、poll采用轮询的方式来检查文件描述符是否处于就绪态,而epoll采用回调机制。造成的结果就是,随着fd的增加,select和poll的效率会线性降低,而epoll不会受到太大影响,除非活跃的socket很多。
  5. epoll的边缘触发模式效率高,系统不会充斥大量不关心的就绪文件描述符(当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你。这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符)

zygote

zygote 两个Socket区别就是:name为”zygote”的Socket是运行在64位Zygote进程中的,而name为“zygote_secondary”的Socket则运行在32位Zygote进程中

启动优化

1.利用workmanager 任务,预加载一些资源

2.系统4.4以前,a.抑制启动时候的gc. b.处理gcHeapLock锁

3.锁优化 a.loadLibrary0 锁 解决:减少加载so任务的并发执行,串行预加载so

​ b. ContextImpl锁,每个sp文件,第一次创建file的时候是有锁的 synchronized(ContextImpl.class)

​ 解决:统一在一个任务里处理所有的sp初始化

4.提前new 主activity(方法区内会提前有缓存)

任务启动管理

1.将任务统一管理

2.建立任务的关联(有向无环图DAG),将当前任务的父任务挂在这个任务的后面

3.通过aop进行任务埋点统计,统计出每个任务在首页启动后多长时间才用到

4.启动任务的加载调用过程分为3类

​ a.启动阶段调用: app启动阶段调用任务,此时app启动尚未完成,如果推迟到闲时加载,调用任务时需要等待任务加载

​ b.闲时阶段调用 1: app启动完成后,用户交互阶段调用任务, 但是调用时间较早,如果推迟至闲时加载,调用任务时,任务没有足够时间加载完成,仍需等待任务加载

​ c.闲时阶段调用2:app启动完成后,用户交互阶段调用任务,但是调用时间较晚甚至不需要调用任务,如果推迟到闲时加载,不会造成不利影响

​ 针对c这种情况,进行算法编排。对同一个设备同一个用户,进行埋点统计。重新编排任务策略

5.可通过监听丢帧数是否超过阈值,来判断是否正在空闲

JAVA内存模型和函数执行

http://gityuan.com/2015/10/17/java-memory/

img

当线程正在执行一个Java方法时,PC计数器记录的是正在执行的虚拟机字节码的地址;当线程正在执行的一个Native方法时,PC计数器则为空(Undefined)

栈帧(Stack Frame)结构

栈帧是用于支持虚拟机进行方法执行的数据结构,是属性运行时数据区的虚拟机栈的栈元素。见上图, 栈帧包括:

  1. 局部变量表 (locals大小,编译期确定),一组变量存储空间, 容量以slot为最小单位。
  2. 操作栈(stack大小,编译期确定),操作栈元素的数据类型必须与字节码指令序列严格匹配
  3. 动态连接, 指向运行时常量池中该栈帧所属方法的引用,为了 动态连接使用。
    • 前面的解析过程其实是静态解析;
    • 对于运行期转化为直接引用,称为动态解析。
  1. 方法返回地址
    • 正常退出,执行引擎遇到方法返回的字节码,将返回值传递给调用者
    • 异常退出,遇到Exception,并且方法未捕捉异常,那么不会有任何返回值。
  1. 额外附加信息,虚拟机规范没有明确规定,由具体虚拟机实现。

异常(Exception)

Java虚拟机规范规定该区域有两种异常(native线程同样也会抛出这2个异常):

  • StackOverFlowError:当线程请求栈深度超出虚拟机栈所允许的深度时抛出
  • OutOfMemoryError:当Java虚拟机动态扩展到无法申请足够内存时抛出

同时方法区,堆区也都可以抛出OutOfMemoryError,但常量区不会

java堆

  • 从内存回收角度,Java堆被分为新生代和老年代;这样划分的好处是为了更快的回收内存;
  • 从内存分配角度,Java堆可以划分出线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB);这样划分的好处是为了更快的分配内存
  • 在堆上内存分配是并发进行的,虚拟机采用CAS加失败重试保证原子操作(dalvik对gcHeapLock加锁),或者是采用每个线程预先分配TLAB内存
方法区

方法区主要存放的是已被虚拟机加载的类信息、常量、静态变量、编译器编译后的代码等数据。GC在该区域出现的比较少。

运行常量池

运行时常量池也是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。运行时常量池除了编译期产生的Class文件的常量池,还可以在运行期间,将新的常量加入常量池,比较常见的是String类的intern()方法。

  • 字面量:与Java语言层面的常量概念相近,包含文本字符串、声明为final的常量值等。

  • 符号引用:编译语言层面的概念,包括以下3类:

    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符

每个栈帧对应着每个方法的每次调用

[图片上传失败...(image-3b6ee6-1598768489439)]

https://zhuanlan.zhihu.com/p/24577273?refer=hinus

https://zhuanlan.zhihu.com/p/24596628?refer=hinus

https://cloud.tencent.com/developer/article/1333643

Dog dog= new Dog();

当虚拟机执行到new指令时,它先在常量池中查找“Dog”,看能否定位到Dog类的符号引用;如果能,说明这个类已经被加载到方法区了,则继续执行。如果没有,就让Class Loader先执行类的加载

如果dog局部变量,dog变量在栈帧的局部变量表,这个对象的引用就放在栈帧。

如果dog是实例变量,dog变量在堆中,对象的引用就放在堆。

如果dog是静态变量,dog变量在方法区,对象的引用就放在方法区

从加载到对象初始化全过程
一加载时机
  1. new关键字实例化对象/读取或者配置类的静态字段/调用类的静态方法 2) java.lang.reflect 包的方法对类进行反射调用 如果没有初始化 触发 3) 初始化类的时候,发现父类没有初始化 触发父类初始化 4)虚拟机启动需要指定一个main方法的主类 先初始化这个类

ps: 对于下面的初始化 同级别也就是相同优先级的变量的顺序是按照代码书写顺序来的

二初始化静态(类变量)

然后就是准备阶段中:

类变量 也就是static变量分配内存 并且 初始化数据默认值 注意实例对象的变量此时没有操作

另外如果是final 修饰的常量,此时一并直接赋值

三 构造器方法 <clinit>()

虚拟机会保证一个类的<clinit>()方法在多线程环境中能够被正确的加锁同步

此时所有类的构造器方法执行

而且父类早于子类,所以最早执行的肯定是Object的

此方法把所有类的静态变量也就是类变量的赋值动作执行结束,而且静态代码块也已经执行结束,而且顺序是父类早于子类

也就是说至此,所有的静态变量都已经分配内存空间,也都是已经设置好的值了,包括父类的所有静态变量

静态变量以及静态代码块的执行都是在这里,显然他们是早于构造方法的执行的

但是如果静态变量赋值或者代码块中赋值中使用到了其他的方法,那么这个方法将会提早执行

如果使用new 构建了对象,不仅仅是构造方法,实例化的步骤都会执行的

而且如果是构造方法,那么new对象实例化的时候还会再次执行

四 对象实例化

只有需要产生对象的时候才会有对象实例化,仅仅是加载类的话,上面的前三步就结束了

而且虽然说是一般最后但是也不一定,比如上面提到的如果静态变量调用new 就会提前触发

1.在堆上分配对象足够的内存空间

2.然后就是空间擦除清零,也即是设置为默认值

3.然后按照实例字段定义的顺序,顺序执行赋值初始化 初始化代码块 和直接定义变量的初始化 优先级别一样 按照定义顺序进行先后

4.实例的构造方法调用

对象的实例化是一个整体的,调用了new 就会按部就班的执行这些步骤

Final

final 修饰类 表示这个类无法被继承

开源库

mmkv

https://juejin.im/post/5d55284d6fb9a06aee362b07

写速度很快 读速度略低于sp 因为m_dict里添加的是protobuf加密好的数据

安全性 第一次启动读取文件时会进行crc验证

Glide

https://juejin.im/post/6844903753347235853#heading-2

https://juejin.im/post/6844903757671579656

Glide的生命周期绑定:可以控制图片的加载状态与当前页面的生命周期同步,使整个加载过程随着页面的状态而启动/恢复,停止,销毁.

Glide的缓存设计:通过(多级缓存,Lru算法,Bitmap复用)对Resource进行缓存设计 ,对不同的view,拆解不同大小的图片进行缓存

Glide的完整加载过程:采用Engine引擎类暴露了一系列方法供Request操作

默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:

  1. 活动资源 (Active Resources)

  2. 内存缓存 (Memory Cache)

  3. 资源类型(Resource Disk Cache)

  4. 原始数据 (Data Disk Cache)

    活动资源:如果当前对应的图片资源正在使用,则这个图片会被Glide放入活动缓存。 内存缓存:如果图片最近被加载过,并且当前没有使用这个图片,则会被放入内存中 资源类型: 被解码后的图片写入磁盘文件中,解码的过程可能修改了图片的参数(如:inSampleSize、inPreferredConfig) 原始数据: 图片原始数据在磁盘中的缓存(从网络、文件中直接获得的原始数据)

Okhttp

https://juejin.im/post/5c4682d2f265da6130752a1d

同步请求也是放在同步队列里,然后等待

Disaptcher的enqueue方法只是做了一个异步请求的逻辑判断,即判断当前异步并发执行队列的数量是否超过最大承载运行数量64和相同host主机最多允许5条线程同时执行请求,满足以上条件,则将传进来的AsyncCall添加到异步执行队列,同时启动线程池执行,反之则添加到异步就绪队列中等待

拦截器有两种:APP层面的拦截器(Application Interception)、网络拦截器(Network Interception)

RetryAndFollowUpInterceptor(重定向拦截器) 负责处理错误 重试,重定向

BridgeInterceptor(桥接拦截器)负责设置编码方式,添加头部,Keep-Alive 连接以及应用层和网络层请求和响应类型之间的相互转换

CacheInterceptor(缓存拦截器) 负责进行缓存处理

ConnectInterceptor 负责与服务器建立连接,connectPool

CallServerInterceptor 负责发起网络请求和接受服务器返回响应

React Native

默认情况下 React Native 会在 Activity 下加载 JS 文件,然后运行在 JavaScriptCore 中解析 Bundle 文件布局,最终堆叠出一系列的原生控件进行渲染

输出产物是js bundle

javaScript 是动态语言

动态语言和非动态语言都有各种的优缺点,比如 JS 开发便捷度明显会高于 Dart ,而 Dart 在类型安全和重构代码等方面又会比 JS 更稳健

[图片上传失败...(image-172ad9-1598768489439)]

flutter 和rn 异同

  • Flutter性能会更好无线接近原生的体验(Flutter 在少了 OEM Widget ,直接与 CPU / GPU 交互的特性),Dart是AOT编译的,编译成快速、可预测的本地代码
  • Flutter自己实现了一套UI框架,丢弃了原生的UI框架。而RN还是可以自己利用原生框架,两个各有好处。Flutter的兼容性高,RN可以利用原生已有的优秀UI
  • Flutter的第三方库还很少,RN发展的早,虽然也还不完善,Flutter github还有3000多个issues要解决,还有很长的路要走。
Flutter

Android上的输出产物是so

dart语言能被aot优化,是伪动态语言的强类型语言

[图片上传失败...(image-2bbab0-1598768489439)]

插件化

https://yq.aliyun.com/articles/361233

DexMaker方案

声明一个空的不存在的PluginActivity,若要运行某插件内的TestActivity,则在运行时通过google提供dexmaker生成一个真实存在的PluginActivtiy的dex文件;这个类是TestActivity的子类,基本不做任何修改(会修改startActivityForResult 用于分析跳转,跳转到另一个PluginActivity)

Host apk classload <- framework classload <- Plugin classload(N个,对不同的插件分别加载)

service 的机制和activity差不多,预先声明一系列PluginService

broadcast receiver 通过反射创建静态注册的receiver实例

插件内资源加载,利用apk构建一个新的assetmanager,在构建插件的Context 和override各种系统api时,将涉及获取资源的方法都指向这个新的assetManager

host app 和插件apk之间的数据传递,编写module A 专门用于host和插件之间的数据传递

热修复

https://www.cnblogs.com/baiqiantao/p/9160806.html

Instant Run方案(美团robust)

获取到补丁后,会创建DexClassLoader加载补丁,通过被修复的类信息找到该类,反射将changeQuickRedirect对象赋值为补丁对象,changeQuickRedirect将不为空,执行到方法时就会调用补丁的方法而不是原方法的逻辑,这样就起到了热修复的目的

安全

数据安全

1.关键数据不要打印日志

2.核心数据要加密存在本地(无线保镖的图片)

3.使用SQLCipher进行db加密 安装包会增大6M

4.注册在AndroidManifest里的服务要考虑是否export

apk包安全

5.反调试技术防止ida pro动态调试

6.混淆和加固,垃圾指令等 防止静态分析

行为本身

7.人机识别 a.系统维度 会识别请求是否真正来自无线客户端。 包括设备识别,模拟器识别,恶意程序识别

​ b.用户维度 会识别请求是否是用户发出的。比如是否是在正确的页面执行

动画

区别Animation和Animator的用法,概述其原理

动画的种类:前者只有透明度,旋转,平移,伸缩4种属性,而对于后者,只要是该控件的属性,且有setter该属性的方法就都可以对该属性执行一种动态变化的效果。

可操作的对象:前者只能对UI组件执行动画,但属性动画几乎可以对任何对象执行动画(不管它是否显示在屏幕上)。

动画播放顺序:在属性动画中,AnimatorSet正是通过playTogether()、playSequentially()、animSet.play().with()、before()、after()这些方法来控制多个动画协同工作,从而做到对动画播放顺序的精确控制

Mutildex

https://zhuanlan.zhihu.com/p/24305296

既然一个Dex文件不行的话,那就把这个硕大的Dex文件拆分成若干个小的Dex文件,刚好一个ClassLoader可以有多个DexFile,这就是MultiDex的基本设计思路。

主dex的优化我们已经在安装apk的时候搞定了,其余的dex就是在MultiDex#installSecondaryDexes里面优化的

优化方案

MultiDex有个比较蛋疼的问题,就是会产生明显的卡顿现象,通过上面的分析,我们知道具体的卡顿产生在解压dex文件以及优化dex两个步骤。不过好在,在Application#attachBaseContext(Context)中,UI线程的阻塞是不会引发ANR的,只不过这段长时间的卡顿(白屏)还是会影响用户体验。

preMultiDex方案

大致思路是,在安装一个新的apk的时候,先在Worker线程里做好MultiDex的解压和Optimize工作,安装apk并启动后,直接使用之前Optimize产生的odex文件,这样就可以避免第一次启动时候的Optimize工作。适用于自己的apk下载更新自己的apk包进行安装。

异步MultiDex方案

这种方案也是目前比较流行的Dex手动分包方案,启动App的时候,先显示一个简单的Splash闪屏界面,然后启动Worker线程执行MultiDex#install(Context)工作,就可以避免UI线程阻塞。不过要确保启动以及启动MultiDex#install(Context)所需要的类都在主dex里面(手动分包),而且需要处理好进程同步问题

facebook的方案 loadResActivity是启动界面 异步线程Multidex.install()

[图片上传失败...(image-a2108d-1598768489439)]

ThreadPoolExecutor

[图片上传失败...(image-b1d278-1598768489439)]

进程和线程

https://blog.csdn.net/Zheng548/article/details/54669908?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

定义方面:进程是程序在某个数据集合上的一次运行活动;线程是进程中的一个执行路径。(进程可以创建多个线程)角色方面:在支持线程机制的系统中,进程是系统资源分配的单位,线程是CPU调度的单位。资源共享方面:进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。同时线程还有自己的栈和栈指针,程序计数器等寄存器。独立性方面:进程有自己独立的地址空间,而线程没有,线程必须依赖于进程而存在。开销方面: 进程切换的开销较大。线程相对较小

线程
  • 有标识符ID
  • 有状态及状态转换,所以需要提供一些状态转换操作
  • 不运行时需要保存上下文环境,所以需要程序计数器等寄存器
  • 有自己的栈和栈指针
  • 共享所在进程的地址空间和其它资源
进程
img

Zygote进程是在系统启动时产生的,它会完成虚拟机的初始化,库的加载,预置类库的加载和初始化等等操作

进程保活

提升进程优先级

双进程保活

​ 1.2个进程通过互相监听文件锁,来感知彼此死亡

​ 2.通过fork产生子进程,fork的进程同属于一个进程组。例如p1进程和p2进程,p1forkc1 p2fork c2, p1和p2互相监听,c1和c2互相监听。四个进程三三之间形成铁三角,来保证存活

​ 3.新进程native给ams发binder节省时间(杀进程组,间隔5ms,杀40次)

http://weishu.me/2020/01/16/a-keep-alive-method-on-android/

Android P绕过api限制

http://weishu.me/2019/03/16/another-free-reflection-above-android-p/

方法1.我们写一个类暴露反射相关的接口,然后通过 JVMTI 提供的 AddToBootstrapClassLoaderSearch将此类加入 BootstrapClassLoader 就实现目的了

方法2.通过 元反射来反射调用 VMRuntime.setHiddenApiExemptions 就能将我们自己要使用的隐藏 API 全部都豁免掉了 。 元发射方法(我们通过反射 API 拿到 getDeclaredMethod 方法。getDeclaredMethod 是 public 的,不存在问题;这个通过反射拿到的方法我们称之为元反射方法。)

Native crash

无法addr2line ,对于一种情况,仍然可以进行解析,即确保当前出问题的native函数没有进行过修改,代码内部偏移量仍然有效。首先使用arm-linux-androideabi-nm -D module_video/libs/armeabi/libgameplay.so | grep _ZN8gameplay14PituCameraGame10initializeEv查找到符号起始地址,然后再加上调用栈中的偏移量(比如上面例子中的1298),然后将新的地址给addr2line进行解析。

libc崩溃

1.查调用栈我们的应用的函数地址

2.查libc的源码,libc(assert报错的时候,会打印java日志,了用于排查)

3.ida pro 反编译so 找调用位置

4.crash的原因(singal原因)

WMS和SurfaceFlinger

  1. 每一个Activity对应一个应用窗口;每一个窗口对应一个ViewRootImpl对象;
  2. 每一个App进程对应唯一的WindowManagerGlobal对象;
    • WindowManagerGlobal.sWindowManagerService用于跟WMS交互
    • WindowManagerGlobal.sWindowSession用于跟Session交互;
  1. 每一个App进程对应唯一的相同Session代理对象;
  2. App可以没有Activity/PhoneWindow/DecorView,例如带悬浮窗口的Service;
  3. Activity运行在ActivityThread所在的主线程;
  4. DecorView是Activity所要显示的视图内容;
  5. ViewRootImpl:管理view的绘制流程(performTraversals);管理DecorView跟WMS的交互;每次调用addView()添加窗口时,则都会创建一个ViewRootImpl对象;

当app来到前台的执行流程:

  1. WMS会请求SurfaceFlinger来绘制surface.
  2. SurfaceFlinger创建layer;
  3. 一个生产者的Binder对象通过WMS传递给app, 因此app可以直接向SurfaceFlinger发送帧信息.
Activity和wms

第一阶段运行在system_server进程:

  1. 将Window相应task移至顶部,并创建AppWindowToken对象,添加到WMS.mTokenMap记录该信息;
  2. 发送消息到”android.display”线程来处理ADD_STARTING启动窗口的消息。

第二阶段运行在目标进程的主线程:(当然在该阶段有些方法会Binder call到system_server进程)

  1. 创建获取AMS的binder代理;
  2. 执行performLaunchActivity过程,创建如下对象:
    • 创建LoadedApk对象;
    • 创建ComponentName对象;
    • 创建目标Activity对象;
    • 创建Application对象;
  1. 创建完Activity,执行attach操作,初始化成员变量:
    • mWindow:数据类型为PhoneWindow,继承于Window对象;
    • mWindowManager:数据类型为WindowManagerImpl,实现WindowManager接口;
    • mToken:远程ActivityRecord的appToken的代理端
  1. 执行performResumeActivity过程;
    • 回调onResume()方法;
  1. 执行addView过程:
    • 创建ViewRootImpl对象;
    • 创建WMS端的Session的代理对象;
    • 创建继承于IWindow.Stub的ViewRootImpl.W对象;
    • 执行setView()添加视图到WMS;
    • 在WMS中创建WindowState对象;
    • updateFocusedWindowLocked来更新聚焦窗口情况。

定时任务

1.sleep

Thread.sleep()使当前线程的执行暂停一段指定的时间,但并不保证这些睡眠时间的精确性,因为他们受到系统计时器和调度程序精度和准确性的影响。另外中断(interrupt)可以终止睡眠时间,所以,在任何情况下,都不能假设调用 sleep就会按照指定的时间精确的挂起线程 或开启任务 在后台服务中使用Thread.sleep()方法,特别是长时间的睡眠,系统很容就会降低该进程的优先级从而杀掉 进程

2.timer不保证精确度且在无法唤醒cpu,不适合后台任务的定时

3.AlarmManager (底层会创建alarm驱动) 精确定时

GC

  • 回收区域:只针对堆、方法区;线程私有区域数据会随线程结束销毁,不用回收

  • 回收类型:

    • 1.堆中的对象:分代收集 GC 方法会吧堆划分为新生代、老年代。新生代:新建小对象会进入新生代;通过复制算法回收对象(由于对象生存期短,每次回收都会有大量对象死去,存活的少);老年代:新建大对象及老对象会进入老年代;通过标记-清除算法回收对象。
    • 2.方法区中的类信息、常量池
  • 判断一个对象是否可被回收:

    • 1.引用计数法:有循环引用的缺点
    • 2.可达性分析法:从 GC ROOT 开始搜索,不可达的对象都是可以被回收的。其中 GC ROOT 包括活着的线程、虚拟机栈/本地方法栈(jni)中引用的对象、方法区中常量/静态变量引用的对象
    • 标记-清除法(davlik虚拟机) : 有碎片化的缺点

什么时候触发GC

(1)程序调用System.gc时可以触发

(2)系统自身来决定GC触发的时机(根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程)

GC又分为 minor GC 和 Full GC (也称为 Major GC )

Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:

a.调用System.gc时,系统建议执行Full GC,但是不必然执行

b.老年代空间不足

c.方法区空间不足

d.通过Minor GC后进入老年代的平均大小大于老年代的可用内存

e.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

img

handle泄漏

img

cpu

cpu使用率反映的是当前cpu的繁忙程度,忽高忽低的原因在于cpu处理时间的进程可以处于IO等待状态但却未释放进入wait

平均负载(load average)指某段时间内占用cpu时间的进程和等待cpu时间的进程数,这里等待cpu时间的进程是指等待被唤醒的进程,不包含处于wait状态的进程

低cpu使用率,高负载的情况。要优化IO读写速度

内部类

内部类访问外部类的私有变量

https://blog.csdn.net/qq_33330687/article/details/77915345?utm_medium=distribute.pc_relevant_bbs_down.none-task-blog-baidujs-1.nonecase&depth_1-utm_source=distribute.pc_relevant_bbs_down.none-task-blog-baidujs-1.nonecase

在虚拟机中没有外部类内部类之分都是普通的类,但是编译器会偷偷的做点修改,让内部类中多一个常量引用指向外部类,自动修改内部类构造器,初始化这个常量引用,而外部类通过扫描内部类调用了外部类的那些私有属性,为这些私有属性创造acess$xxx静态方法。这个方法是返回对应的私有属性的值

非静态内部类:

1、变量和方法不能声明为静态的。(类的编译顺序:外部类--静态方法或属性--内部类)

2、实例化的时候需要依附在外部类上面。比如:B是A的非静态内部类,实例化B,则:A.B b = new A().new B();

3、内部类可以引用外部类的静态或者非静态属性或者方法。

静态内部类:

1、属性和方法可以声明为静态的或者非静态的。

2、实例化静态内部类:比如:B是A的静态内部类,A.B b = new A.B();

3、内部类只能引用外部类的静态的属性或者方法。

4、如果属性或者方法声明为静态的,那么可以直接通过类名直接使用。比如B是A的静态内部类,b()是B中的一个静态属性,则可以:A.B.b();

范型

范型擦除

https://blog.csdn.net/briblue/article/details/76736356

泛型信息只存在于代码编译阶段(编译检查),在进入 JVM 之前,与泛型相关的信息会被擦除掉

java 异常

Checked exception: 继承自 Exception 类是 checked exception。代码需要处理 API 抛出的 checked exception,要么用 catch 语句,要么直接用 throws 语句抛出去。

Unchecked exception: 也称 RuntimeException,它也是继承自 Exception。但所有 RuntimeException 的子类都有个特点,就是代码不需要处理它们的异常也能通过编译,所以它们称作 unchecked exception。RuntimeException(运行时异常)不需要try...catch...或throws 机制去处理的异常

Webview

优化

https://tech.meituan.com/2017/06/09/webviewperf.html

  • WebView初始化慢,可以在初始化同时先请求数据,让后端和网络不要闲着。
  • 后端处理慢,可以让服务器分trunk输出,在后端计算的同时前端也加载网络静态资源。
  • 脚本执行慢,就让脚本在最后运行,不阻塞页面解析。
  • 同时,合理的预加载、预缓存可以让加载速度的瓶颈更小。
  • WebView初始化慢,就随时初始化好一个WebView待用。
  • DNS和链接慢,想办法复用客户端使用的域名和链接。
  • 脚本执行慢,可以把框架代码拆分出来,在请求页面之前就执行好
android和javascript交互

js调用android

  • 通过 WebView 的 addJavascriptInterface() 进行对象映射
  • 通过 WebViewClient 的 shouldOverrideUrlLoading() 方法回调拦截 url
  • 通过 WebChromeClient 的 onJsAlert() 、 onJsConfirm() 、 onJsPrompt()方法回调拦截 JS 对话框 alert() 、 confirm() 、 prompt() 消息

js调用java

  • jsbirdge
  • onPrompt
  • log
  • addJavascriptInterface

android(java)调用js

  • webview的loadurl() 会刷新界面 无返回值
  • webiew的evaluateJavascript() 不会刷新界面 有返回值 效率高,android4.4以后才有

相关文章

网友评论

      本文标题:Android面试复习点

      本文链接:https://www.haomeiwen.com/subject/zhkesktx.html