2024年Android最新七道Android面试题,先来简单热个身_android开发面试题,2024年最新拼多多 面试题

学习分享

①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

但是当我们往这个List里面add数据的时候,我们会发现,crash了,看到的日志是

不被支持的操作,这让首次遇到这样问题的人肯定是一脸懵,List不让添加数据了吗?之前明明可以的啊,但是之前我们创建一个List是这样创建的

它所在的包是java.util.ArrayList里面,我们看下里面的代码

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

是存在add方法的,我们再回头再去看看asList生成的List

是在java.util.Arrays包里面的,而这里面的ArrayList我们看到了,并没有去实现List接口,所以也就没有add,get等方法,另外在kotlin里面,我们会看到一个细节,当你敲完Arrays.asList的时候,编译器会提示你,可以转换成listof函数,而这个还是我们知道生成的list都是只能读取,不能往里写数据

Thread.sleep(0)到底“睡没睡”

记得在上上家公司,接手的第一个需求就是做一个动画,这个动画需要一个延迟启动的功能,我那个时候想都没想加了个Thread.sleep(3000),后来被领导批了,不可以用Thread.sleep实现延迟功能,那会还不太明白,后来知道了,Thread.sleep(3000)不一定真的暂停三秒,我们来举个例子

println("start:${System.currentTimeMillis()}")
Thread(Runnable {
    Thread.sleep(3000)
    println("end:${System.currentTimeMillis()}")
}).start()

我们在主线程先打印一条数据展示时间,然后开启一个子线程,在里面sleep三秒以后在打印一下时间,我们看下结果如何

start:1675665421590
end:1675665424591

好像对了又好像没对,为什么是过了3001毫秒才打印出来呢?有的人会说,1毫秒而已,忽略嘛,那我们把上面的代码改下再试试

println("start:${System.currentTimeMillis()}")
Thread(Runnable {
    Thread.sleep(0)
    println("end:${System.currentTimeMillis()}")
}).start()

现在sleep了0毫秒,那是不是两条打印日志应该是一样的呢,我们看看结果

start:1675666764475
end:1675666764477

这下子给整不会了,明明sleep0毫秒,那么多出来的2毫秒是怎么回事呢?其实在Android操作系统中,每个线程使用cpu资源都是有优先级的,优先级高的才有资格使用,而操作系统则是在一个线程释放cpu资源以后,重新计算所有线程的优先级来重新分配cpu资源,所以sleep真正的意义不是暂停,而是在接下去的时间内不参与cpu的竞争,等到cpu重新分配完资源以后,如果优先级没变,那么继续执行,所以sleep(0)秒的真正含义是触发cpu资源重新分配

View.post为什么可以获取控件的宽高

我们都知道在onCreate里面想要获取一个控件的宽高,如果直接获取是拿不到的

val mWith = bindingView.mainButton.width
val mHeight = bindingView.mainButton.height
println("按钮宽:$mWith,高:$mHeight")
......
按钮宽:0,高:0

而如果想要获取宽高,则必须调用View.post的方法

bindingView.mainButton.post {
    val mWith = bindingView.mainButton.width
    val mHeight = bindingView.mainButton.height
    println("按钮宽:$mWith,高:$mHeight")
}
......
按钮宽:979,高:187

很神奇,加个post就可以在同样的地方获取控件宽高了,至于为什么呢?我们来分析一下

简单的来说

Activity生命周期,onCreate方法里面视图还在绘制过程中,所以没法直接获取宽高,而在post方法中执行,就是在线程里面获取宽高,这个线程会在视图没有绘制完成的时候放在一个等待队列里面,等到视图绘制执行完毕以后再去执行队列里面的线程,所以在post里面也可以获取宽高

复杂的来说

我们首先从View.post方法里面开始看

这个代码里面的两个框子,说明了post方法做了两件事情,当mAttachInfo不为空的时候,直接让mHandler去执行线程action,当mAttachInfo为空的时候,将线程放在了一个队列里面,从注释里面的第一个单词Postpone就可以知道,这个action是要推迟进行,什么时候进行呢,我们在慢慢看,既然是判断当mAttachInfo不为空才去执行线程,那我们找找什么时候对mAttachInfo赋值,整个View的源码里面只有一处是对mAttachInfo赋值的,那就是在dispatchAttachedToWindow 这个方法里面,我们看下

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;
    ...省略部分源码...
  
    // Transfer all pending runnables.
    if (mRunQueue != null) {
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null;
    }

}

当走到dispatchAttachedToWindow这个方法的时候,mAttachInfo才不为空,也就是从这里开始,我们就可以获取控件的宽高等信息了,另外我们顺着这个方法往下看,可以发现,之前的那个队列在这里开始执行了,现在就关键在于,什么时候执行dispatchAttachedToWindow这个方法,这个时候就要去ViewRootIml类里面查看,发现只有一处调用了这个方法,那就是在performTraversals这个方法里面

private void performTraversals() {
        ...省略部分源码...
    host.dispatchAttachedToWindow(mAttachInfo, 0);
        ...省略部分源码...
    // Ask host how big it wants to be
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...省略部分源码...
    performLayout(lp, mWidth, mHeight);
        ...省略部分源码...
    performDraw();
}

performTraversals这个方法我们就很熟悉了,整个View的绘制流程都在里面,所以只有当mAttachInfo在这个环节赋值了,才可以得到视图的信息

IdleHandler到底有啥用

Handler是面试的时候必问的环节,除了问一下那四大组件之外,有的面试官还会问一下IdleHandler,那IdleHandler到底是什么呢,它是干什么用的呢,我们来看看

Message next() {
...省略部分代码...
    synchronized (this) {
        // If first time idle, then get the number of idlers to run.
        // Idle handles only run if the queue is empty or if the first message
        // in the queue (possibly a barrier) is due to be handled in the future.
        if (pendingIdleHandlerCount < 0
                && (mMessages == null || now < mMessages.when)) {
            pendingIdleHandlerCount = mIdleHandlers.size();
        }
        if (pendingIdleHandlerCount <= 0) {
            // No idle handlers to run.  Loop and wait some more.
            mBlocked = true;
            continue;
        }

        if (mPendingIdleHandlers == null) {
            mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
        }
        mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
    }

    // Run the idle handlers.
    // We only ever reach this code block during the first iteration.
    for (int i = 0; i < pendingIdleHandlerCount; i++) {
        final IdleHandler idler = mPendingIdleHandlers[i];
        mPendingIdleHandlers[i] = null; // release the reference to the handler

        boolean keep = false;
        try {
            keep = idler.queueIdle();
        } catch (Throwable t) {
            Log.wtf(TAG, "IdleHandler threw exception", t);
        }

        if (!keep) {
            synchronized (this) {
                mIdleHandlers.remove(idler);
            }
        }
    }

}

只有在MessageQueue中的next方法里面出现了IdleHandler,作用也很明显,当消息队列在遍历队列中的消息的时候,当消息已经处理完了,或者只存在延迟消息的时候,就会去处理mPendingIdleHandlers里面每一个idleHandler的事件,而这些事件都是通过方法addIdleHandler注册进去的

Looper.myQueue().addIdleHandler {
    false
}

addIdlehandler接受的参数是一个返回值为布尔类型的函数类型参数,至于这个返回值是true还是false,我们从next()方法中就能了解到,当为false的时候,事件处理完以后,这个IdleHandler就会从数组中删除,下次再去遍历执行这个idleHandler数组的时候,该事件就没有了,如果为true的话,该事件不会被删除,下次依然会被执行,所以我们按需设置。现在我们可以利用idlehandler去解决上面讲到的在onCreate里面获取控件宽高的问题

Looper.myQueue().addIdleHandler {
    val mWith = bindingView.mainButton.width
    val mHeight = bindingView.mainButton.height
    println("按钮宽:$mWith,高:$mHeight")
    false
}

当MessageQueue中的消息处理完的时候,我们的视图绘制也完成了,所以这个时候肯定也能获取控件的宽高,我们在IdleHandler里面执行了同样的代码之后,运行后的结果如下

按钮宽:979,高:187

除此之外,我们还可以做点别的事情,比如我们常说的不要在主线程里面做一些耗时的工作,这样会降低页面启动速度,严重的还会出现ANR,这样的场景除了开辟子线程去处理耗时操作之外,我们现在还可以用IdleHandler,这里举个例子,我们在主线程中给sp塞入一些数据,然后在把这些数据读取出来,看看耗时多久

println(System.currentTimeMillis())
val testData = "aabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhas" +
        "jkhdaabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhasjkhd" +
        "aabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhasjkhd" +
        "aabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhasjkhd" +
        "aabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhasjkhd" +
        "aabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhasjkhd"
sharePreference = getSharedPreferences(packageName, MODE_PRIVATE)
for (i in 1..5000) {
    sharePreference.edit().putString("test$i", testData).commit()
}
for (i in 1..5000){
    sharePreference.getString("test$i","")
}
println(System.currentTimeMillis())

......运行结果
1676260921617
1676260942770

我们看到在塞入5000次数据,再读取5000次数据之后,一共耗时大概20秒,同时也阻塞了主线程,导致的现象是页面一片空白,只有等读写操作结束了,页面才展示出来,我们接着把读写操作的代码用IdleHandler执行一下看看

Looper.myQueue().addIdleHandler {
    sharePreference = getSharedPreferences(packageName, MODE_PRIVATE)
    val editor = sharePreference.edit()
    for (i in 1..5000) {
        editor.putString("test$i", testData).commit()
    }
    for (i in 1..5000){
        sharePreference.getString("test$i","")
    }
    println(System.currentTimeMillis())
    false
}
......运行结果
1676264286760
1676264308294

运行结果依然耗时二十秒左右,但区别在于这个时候页面不会受到读写操作的阻塞,很快就展示出来了,说明读写操作的确是等到页面渲染完才开始工作,上面过程没有放效果图主要是因为时间太长了,会影响gif的体验,有兴趣的可以自己试一下

如何让指定视图不被软键盘遮挡

我们通常使用android:windowSoftInputMode属性来控制软键盘弹出之后移动界面,让输入框不被遮挡,但是有些场景下,键盘永远都会挡住一些我们使用频次比较高的控件,比如现在我们有个登录页面,大概的样子长这样

它的布局文件是这样

<RelativeLayout
    android:id="@+id/mainroot"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:src="@mipmap/ic_launcher_round" />

    <androidx.appcompat.widget.LinearLayoutCompat
        android:id="@+id/ll_view1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="120dp"
        android:gravity="center"
        android:orientation="vertical">

        <EditText
            android:id="@+id/main_edit"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:hint="请输入用户名"
            android:textColor="@color/black"
            android:textSize="15sp" />

        <EditText
            android:id="@+id/main_edit2"
            android:layout_width="match_parent"


### 总结

开发是面向对象。我们找工作应该更多是面向面试。哪怕进大厂真的只是去宁螺丝,但你要进去得先学会面试的时候造飞机不是么?

> 作者13年java转Android开发,在小厂待过,也去过华为,OPPO等,去年四月份进了阿里一直到现在。等大厂待过也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

这里附上上述的技术体系图相关的几十套**腾讯、头条、阿里、美团等公司的面试题**,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含**知识脉络 + 诸多细节**,由于篇幅有限,这里以图片的形式给大家展示一部分。

**相信它会给大家带来很多收获:**

![](https://img-blog.csdnimg.cn/img_convert/9822014a36abfcbf848afab344f5e36f.webp?x-oss-process=image/format,png)

![960页全网最全Android开发笔记](https://img-blog.csdnimg.cn/img_convert/af354810b69210f889d8da536ddecfbd.webp?x-oss-process=image/format,png)

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

> 当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

g-VRupIlzo-1715671595201)]

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

> 当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值