zerofront(字节跳动面试1)

A.基本技术题Activity生命周期

第一个Activity01:onCreate01->onStart01->onResume01

显示dialog,生命周期不会变化,不会调用onPause

跳转到Activity02

a.如果Activity02正常的:

onPause01->onCreate02->onStart02->onResume02->onStop01->onSaveInstanceState01

Activity02退出返回:

onPause02->onRestart01->onStart01->onResume01->onStop02->onDestroy02

b.如果Activity02是透明的

onPause01->onCreate02->onStart02->onResume02

透明Activity02退出返回:

onPause02->onResume01->onStop02->onDestroy02

总结:跳转到另一个Activity,首先是当前Activity onPause,然后新的Activity生命周期一直到onResume,然后之前的Activity再执行onStop/onSaveInstanceState.

在adb中使用:>adb shell am kill 包名(如com.example.lcp)可模拟内存不足时进程被杀死,之后返回该应用时,每个Activity调用的是onCreate->onStart->onRestoreInstanceState->onResume

2. onNewIntent调用时机、四种启动方式

注:onNewIntent(肯定是已存在,第二次startActivity的时候调用出现的)总是在onResume之前,如果有onStart,就在onStart之后

Standard:每次创建新的

SingleTask:如果栈中已存在,就把它上面的所有实例finish,把它提到栈顶

SingleInstance:只有一个实例,并且运行在自己的独立栈中

SingleTop:如果当前栈顶是它,就复用

自己跳转自己,除了Standard都会调用onNewIntent。

其他跳转过来,SingleTask和SingleInstance会调用onNewIntent,如果当前栈顶是自己,那么SingleTop也会调用onNewIntent

singleInstance的坑借鉴这篇文章

这个模式才是重点,也是比较容易入坑的一种启动模式。字面上理解为单一实力。它具备所有singleTask的特点,唯一不同的是,它是存在于另一个任务栈中。上面的三种模式都存在于同一个任务栈中,而这种模式则是存在于另一个任务栈中。举个例子,上面的启动模式都存在于地球上,而这种模式存在于火星上。整个Android系统就是个宇宙。下面来详细介绍一下singleInstance的坑。

singleInstance之一坑

此时有三个activity,ActivityA,ActivityB,ActivityC,除了ActivityB的启动模式为singleInstance,其他的启动模式都为默认的。startActivity了一个ActivityA,在ActivityA里startActivity了一个ActivityB,在ActivityB里startActivity了一个ActivityC。

此时在当前的任务栈中的顺序是,ActivityA->ActivityB->ActivityC。照理来说在当前ActivityC页面按返回键,finish当前界面后应当回到ActivityB界面。但是事与愿违,奇迹出现了,页面直接回到了ActivityA。

这是为什么呢?其实想想就能明白了,上面已经说过,singleInstance模式是存在于另一个任务栈中的。也就是说ActivityA和ActivityC是处于同一个任务栈中的,ActivityB则是存在另个栈中。所以当关闭了ActivityC的时候,它自然就会去找当前任务栈存在的activity。当前的activity都关闭了之后,才会去找另一个任务栈中的activity。

也就是说当在ActivityC中finish之后,会回到ActivityA的界面,在ActivityA里finish之后会回到ActivityB界面。如果还想回到ActivityB的页面怎么办呢?我的做法是,在ActivityB定义一个全局变量,public static boolean returnActivityB;界面需要跳转的时候将returnActivityB=true;然后在ActivityA界面onstart方法里判断returnActivityB是否为true,是的话就跳转到ActivityB,同时将returnActivityB=false;这样就能解决跳转的问题了。不过感觉还不是很好,如果有更好的方法,欢迎大家给我留言告诉我一声。

singleInstance之二坑

此时有两个个activity,ActivityA,ActivityB,ActivityA的启动模式为默认的,ActivityB的启动模式为singleInstance。当在ActivityA里startActivity了ActivityB,当前页面为ActivityB。按下home键。应用退到后台。此时再点击桌面图标进入APP,按照天理来说,此时的界面应该是ActivityB,可是奇迹又出现了,当前显示的界面是ActivityA,并且返回不会显示ActivityB了。(如果是在最近运行的应用列表中选择,则回去的是ActivityB,并且返回不会再显示ActivityA了)

这是因为当重新启动的时候,系统会先去找主栈(我是这么叫的)里的activity,也就是APP中LAUNCHER的activity所处在的栈。查看是否有存在的activity。没有的话则会重新启动LAUNCHER。要解决这个方法则是和一坑的解决办法一样,在ActivityB定义一个全局变量,public static boolean returnActivityB;在oncreat方法将returnActivityB=true;然后在ActivityA界面onstart方法里判断returnActivityB是否为true,是的话就跳转到ActivityB,同时将returnActivityB=false;这样就能解决跳转的问题了。

注意:Intent设置的Flags优先级高于manifest中设置的启动模式,即运行时的实际要求优先级高于配置

3. HashMap结构、扩容时机、ConcurrentHashMap实现线程安全机制

A.HashMap:数组+链表,当链表数量大于8时,链表变成红黑树

B.HahMap扩容时机:初始数组也就是桶的容量capacity是16,必须是2的幂,当map数量等于数组数量capcity*factor(默认值是0.75)时,比如16*3/4=12时,数组扩容,减小哈希碰撞

C.ConcurrentHashMap使用Synchronized同步块而不是同步方法(1.8,在1.7中使用ReentrantLock重入锁)实现安全机制,同时使用CAS(CompareAndSwap:比较并替换,是UnSafe类,适用于原子类,就是所有操作都是原子的,要么成功,要么失败)操作

什么是unsafe呢?Java语言不像C,C++那样可以直接访问底层操作系统,但是JVM为我们提供了一个后门,这个后门就是unsafe。unsafe为我们提供了硬件级别的原子操作。至于valueOffset对象,是通过unsafe.objectFiledOffset方法得到,所代表的是AtomicInteger对象value成员变量在内存中的偏移量。我们可以简单的把valueOffset理解为value变量的内存地址。我们上面说过,CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。而unsafe的compareAndSwapInt方法的参数包括了这三个基本元素:valueOffset参数代表了V,expect参数代表了A,update参数代表了B。正是unsafe的compareAndSwapInt方法保证了Compare和Swap操作之间的原子性操作。

4. Okhttp和retrofit比volley好在哪里,retrofit实现原理,断点下载

添加配置,retrofit实现restApi,怎么实现的?

断点下载,请求头中有Range数据的信息,只要客户端告诉服务端从range字节开始返回数据就可以了。多线程下载是说每个线程下载不同的range就可以实现了。

5. Binder底层实现机制

6. 消息机制、进程间通信

B.Kotlin问题:

inline/noinline/crossinline三个关键字区别,好处是什么

参考 StefanJi关于inline/noinline/crossinline的解释:

a. inline:内联函数,就是在编译期将函数代码直接复制到调用的地方

noinline和crossinline都用在inline函数中

b. noinline就是不要直接复制代码

c. crossinline不允许lambda中有显示的return语句,正常的function可以有

C.算法题:

1. 链表反转

方法1:对我来说最好的理解方式是:将第一个元素作为轴心current,每次循环都把current的后一个元素放到第一位

Class Node(var number:Int?,var next:Node)fun reverse(head:Node) { //把第一个元素作为当前元素current,每次循环都将current的下一个元素current.next移到第一位,直到current.next=null为止 //时间复杂度为O(n),空间复杂度是1 var current = head.next while (current?.next != null) { val first = head.next val currentNext = current.next //current的下一个元素移动到第一位 head.next = currentNext current.next = currentNext?.next currentNext?.next = first }}

方法2:

fun reverse2(head:Node) { //新建一个节点,每次通过将p节点连接到newFirst,然后newFirst=p,p=p.next就可以进行链表反转了 var newFirst: Node?=null var p:Node? = head.next while (p != null) { val tmp = p.next p.next=newFirst newFirst=p p = tmp } head.next=newHead}

2. 一个数组,将所有0前移,非0保持前后位置

fun arrayMoveZeroToFront(array: IntArray) { //最后一个0的位置 var lastZeroIndex = -1 //循环时与最后一个0的位置对比,如果非0并且后面还有0,就交换位置 for (i in array.indices.reversed()) { if (array[i] == 0) { if (lastZeroIndex < i) lastZeroIndex = i } else { if (i < lastZeroIndex) { array[lastZeroIndex] = array[i] array[i] = 0 lastZeroIndex-- } } } println(array.contentToString())}