Activity 有几种 launch mode?每一种有什么特点?
1.standard(默认的启动模式)
当我们发送一个 intent 启动该 Activity 后,该 Activity 总是被创建一个新的实例。
2.singleTop(栈顶复用)
如果要启动 Activity 存在于任务栈中,并且位于任务栈的顶部,就不会创建新的 Activity
实例,而是会调用 该 Activity 的 onNewIntent() 方法, 避免栈顶的 Activity 被重复的创建。
如果被调用的Activity的实例已经存在但不位于栈顶,那这个被调用的activity依然会被创建。
3.singleTask (栈内复用)
Activity 只会在任务栈里面存在一个实例。如果要启动的 Activity 在任务栈 Task 中已
经存在,就不会创建新的 Activity 实例,而是复用这个已经存在的 Activity,调用
onNewIntent() 方法,并且清空这个 Activity 任务栈之上所有的其他 Activity
4.singleInstance (单一实例模式)
设置了该模式的 Activity 只能位于一个单独的任务栈中,不能在有其他 Activity , 其他
任何从该 Activity 启动的其他 Activity 都会放到其他的任务栈中。
Service 有几种类型?各有什么应用场景?
广播有几种注册方式?有什么区别?
1.代码注册
非常驻型注册,跟随生命周期变化,及 Activity 不可见时取消注册。不过当 BroadcastReceiver
(receiver)需要更新 UI 的时候,一般会采用该注册广播的方法
2.通过 androidmainfest.xml 中注册
常驻行注册,简单的说就是即使关闭了应用程序,后台接收到消息通知,照样可以唤醒
应用当中的响应程序Activity 有哪些生命周期回调?Kotlin 中的扩展函数是什么?
kotlin 中的扩展函数,实际上是在自己的类中添加了一个 static final 方法,将需要扩展
的类,在使用的时候作为第一个参数传入方法中,然后使用:
fun String.hello(str: String): String{
return "hello" + str + this.length
}
fun String.hello1(str: String): String{
return "hello$str"
}
经过反编译之后生成字节码如下:
public final class ExtensionTestKt {
@NotNull
public static final String hello(@NotNull String $receiver, @NotNull String str) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
Intrinsics.checkParameterIsNotNull(str, "str");
return "hello" + str + $receiver.length();
}
@NotNull
public static final String hello1(@NotNull String $receiver, @NotNull String str) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
Intrinsics.checkParameterIsNotNull(str, "str");
return "hello" + str;
}
}
kotlin 中的扩展函数,实际上就是通过给类添加 public static final 函数的做法来实现,这
样做可以减少 utils 类的使用。
扩展函数是静态解析的,是采用静态分派的过程来处理。这意味着调用的扩展函数是由
函数调用所在的表达式的类型来决定的,而不是由表达式运行时求值结果决定的。这意思其
实就是在使用该扩展函数的时候,如果类本身和其子类都进行了同一个函数的扩展,这函数
是不会有重写关系的,在使用的时候,只会根据需要使用该方法的对象的实际类型来决定是
调用了哪个,就是相当于调用静态方法。而不是动态分派。JVM 内存模型是怎么样的?
其中方法区和堆是所有线程共享的,栈,本地方法栈和程序虚拟机则为线程私有的。
程序计数器
程序计数器是一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。
Java 栈(虚拟机栈)
同计数器也为线程私有,生命周期与相同,就是我们平时说的栈,栈描述的是 Java 方法执
行的内存模型。 每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,
动态链接, 方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入
栈到出栈的过程
本地方法栈
本地方法栈是与虚拟机栈发挥的作用十分相似,区别是虚拟机栈执行的是Java方法(也就是字
节码) 服务,而本地方法栈则为虚拟机使用到的 native 方法服务,可能底层调用的 c 或者 c++, 我们打开 jdk 安装目录可以看到也有很多用 c 编写的文件,可能就是 native 方法所调用的 c
代码
堆
堆是 java 虚拟机管理内存最大的一块内存区域,因为堆存放的对象是线程共享的, 所以多
线程的时候也需要同步机制
方法区
方法区同堆一样,是所有线程共享的内存区域,为了区分堆,又被称为非堆。 用于存储已
被虚拟机加载的类信息、常量、静态变量,如 static 修饰的变量加载类的时候 就被加载到方法
区中GC 回收算法?
1.标记-清除算法
该算法先标记,后清除,将所有需要回收的算法进行标记,然后清除;这种算法的缺点是:
效率比较低;标记清除后会出现大量不连续的内存碎片,这些碎片太多可能会使存储大对象
会触发 GC 回收,造成内存浪费以及时间的消耗。
2.复制算法
复制算法将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另
一块内存中,然后把使用的清除。这种算法运行简单,解决了标记-清除算法的碎片问题,
但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工
作,效率比较低。
3.标记-整理算法
标记整理算法是针对复制算法在对象存活率较高时持续复制导致效率较低的缺点进行改进
的,该算法是在标记-清除算法基础上,不直接清理,而是使存活对象往一端游走,然后清
除一端边界以外的内存,这样既可以避免不连续空间出现,还可以避免对象存活率较高时的
持续复制。这种算法适合老生代。
4.分代收集算法
分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,
将内存分为各个年代,在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率
低,可以使用复制算法。而老年代对象存活率高,没有额外空间对它进行分配担保,所以使
用标记整理算法
内存分配与回收策略
如图所示是堆中内存分配示意图,创建一个对象,首先会在 eden 区域分配区域,如果
内存不够,就会将年龄大的转移到 Survivor 区,当 survivor 区域存储不下,则会转移年老代
的。对于一些静态变量不需要使用对象,直接调用的,则会被放入永生代。一般来说长期存
活的对象最终会被存放到年老代,还有一种特殊情况也会被存放到年老代,就是创建大对象
时,比如数据这种需要申请连续空间的,如果空间比较大的,则会直接进入年老代。
在回收过程中,有一个参数比较重要,就是对象的年龄,如果在一次垃圾回收过程中有
使用该对象的,则将对象年龄加 1,否则减 1,当计数为 0,则进行回收,如果年龄达到一
定数字则进入老生代。总的来说内存分配机制主要体现在对象创建之后是否仍在使用,已经
不使用的则回收,继续使用的则对其年龄进行更新,达到一定程度,转移到年老代。Java 中有几种引用类型?
1.强引用
这种引用是平时开发中最常用的,例如 String strong = new String("Strong Reference"),
当一个实例对象具有强引用时,垃圾回收器不会回收该对象,当内存不足时,宁愿抛出
OutOfMemeryError 异常也不会通过回收强引用的对象,因为 JVM 认为强引用的对象是用户
正在使用的对象,它无法分辨出到底该回收哪个,强行回收有可能导致系统严重错误
2.软引用
如果一个对象只有软引用,那么只有当内存不足时,JVM 才会去回收该对象,其他情
况不会回收。软引用可以结合 ReferenceQueue 来使用,当由于系统内存不足,导致软引用
的对象被回收了,JVM 会把这个软引用加入到与之相关联的 ReferenceQueue 中。
3.弱引用
只有弱引用的对象,当 JVM 触发 gc 时,就会回收该对象。与软引用不同的是,不管是
否内存不足,弱引用都会被回收。弱引用可以结合 ReferenceQueue 来使用,当由于系统触
发 gc, 导致 软引 用的 对象 被回 收了 , JVM 会 把这 个弱 引用 加入 到与 之相 关 联的
ReferenceQueue 中,不过由于垃圾收集器线程的优先级很低,所以弱引用不一定会被很快
回收。
4.虚引用
如果一个对象只有虚引用在引用它,垃圾回收器是可以在任意时候对其进行回收的,虚引
用主要用来跟踪对象被垃圾回收器回收的活动,当被回收时,JVM 会把这个虚引用加入到与之
相关联的 ReferenceQueue 中。与软引用和弱引用不同的是,虚引用必须有一个与之关联的
ReferenceQueue,通过 phantomReference.get()得到的值为 null
Android 消息机制是怎么实现的?
1.Handler 通过 sendMessage()发送 Message 到 MessageQueue 队列;
2.Looper 通过 loop(),不断提取出达到触发条件的 Message,并将 Message 交给 target 来处
理;
3.经过 dispatchMessage()后,交回给 Handler 的 handleMessage()来进行相应地处理。4.将 Message 加入 MessageQueue 时,处往管道写入字符,可以会唤醒 loop 线程;如果
MessageQueue 中没有 Message,并处于 Idle 状态,则会执行 IdelHandler 接口中的方法,往往用
于做一些清理性地工作。
Android 触摸事件如何传递?
onInterceptTouchEvent 返回值 true 表示事件拦截, onTouch/onTouchEvent 返回值 true 表示
事件消费。
触摸事件先交由 Activity.dispatchTouchEvent。再一层层往下分发,当中间的 ViewGroup 都不
拦截时,进入最底层的 View 后,开始由最底层的 OnTouchEvent 来处理,如果一直不消费,则
最后返回到 Activity.OnTouchEvent。
ViewGroup 才有 onInterceptTouchEvent 拦截方法。在分发过程中,中间任何一层 ViewGroup
都可以直接拦截,则不再往下分发,而是交由发生拦截操作的 ViewGroup 的 OnTouchEvent 来处
理。
子 View 可调用 requestDisallowInterceptTouchEvent 方法,来设置 disallowIntercept=true,从
而阻止父 ViewGroup 的 onInterceptTouchEvent 拦截操作。OnTouchEvent 由下往上冒泡时,当中间任何一层的 OnTouchEvent 消费该事件,则不再往上
传递,表示事件已处理。
如果 View 没有消费 ACTION_DOWN 事件,则之后的 ACTION_MOVE 等事件都不会再接收。
只要 View.onTouchEvent 是可点击或可长按,则消费该事件. onTouch 优先于 onTouchEvent 执行,上面流程图中省略,onTouch 的位置在 onTouchEvent
前面。当 onTouch 返回 true,则不执行 onTouchEvent,否则会执行 onTouchEvent。onTouch 只有 View
设置了 OnTouchListener,且是 enable 的才执行该方法。
Android 如何在不同组件间通信?(跨进程,跨线程)
同一进程内:
使用 intent 进行传值
使用 binder 传值(Activity 和 Service 之间)
使用 broadcast 广播进行传值
使用 Application,SharePreference,文件存储,数据库,ContentProvider 等等
使用接口使用事件总线的 Eventbus,rxbus,LiveDataBus 等。
跨进程:
Activity 可以跨进程调用其他应用程序的 Activity
Content Provider 可以跨进程访问其他应用程序中的数据(以 Cursor 对象形式返回)
Broadcast 可以向 android 系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可
以监听这些广播
Service 和 Content Provider 类似,也可以访问其他应用程序中的数据,但不同的是,Content
Provider 返回的是 Cursor 对象,而 Service 返回的是 Java 对象,这种可以跨进程通讯的服务叫
AIDL 服务。
跨线程: 通过消息机制