史上最全的2022年最新版Android安卓面试题集锦(含答案+源码)

在这里插入图片描述
金三银四,又到了一年一度的黄金跳槽季,想必也许有不少Android程序员开始摩拳擦掌,蠢蠢欲动了。而网络上的面试题大部分已经过时甚至是错误成堆,漏洞百出,今天结合自己这段时间的面试经验和几个Android技术群里面大厂大牛的交流总结出了这篇2022年最新的Android面试题合集。那么,废话不多说,让我们马上进入正题。

前言

首先要声明的是:面试题的目的不是为了让大家背题,而是从不同维度帮助大家复习,取长补短。让我们正式进入正题:

面试题汇总

1.Android四大组件:Activity、Service、BroadcastReceiver、ContentProvider。它们的作用分别是:

Activity—>配合View展示界面
Service—>长时间在后台运行不与用户直接交互
BroadcastReceiver—>接收广播
ContentProvider—>提供数据给其他模块使用

2.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 都会放到其他的任务栈中。

3.Service 有几种类型?各有什么应用场景?

方法 启动方式 停止方式 与启动的组件通信方法 生命周期
startService 在其他组件中调用startService()方法后,服务即处于启动状态 service中调用stopSelf()方法,或者其他组件调用stopService()方法后,service将停止运行 没有提供默认的通信方式,启动service后该service就处于独立运行状态 一旦启动,service即可在后台无限期运行,即使启动service的组件已被销毁也不受其影响,直到其被停止
bindService 在其他组件中调用bindService()方法后,服务即处于启动状态 所有与service绑定的组件都被销毁,或者它们都调用了unbindService()方法后,service将停止运行 可以通过 ServiceConnection进行通信,组件可以与service进行交互、发送请求、获取结果,甚至是利用IPC跨进程执行这些操作 当所有与其绑定的组件都取消绑定(可能是组件被销毁也有可能是其调用了unbindService()方法)后,service将停止

4.LaunchMode 的应用场景?

LaunchMode 有四种,分别为 Standard,SingleTop,SingleTask 和 SingleInstance,每种模式的实现原理一楼都做了较详细说明,下面说一下具体使用场景:

  • Standard:
    Standard 模式是系统默认的启动模式,一般我们 app中大部分页面都是由该模式的页面构成的,比较常见的场景是:社交应用中,点击查看用户A信息->查看用户A粉丝->在粉丝中挑选查看用户B信息->查看用户A粉丝…这种情况下一般我们需要保留用户操作 Activity 栈的页面所有执行顺序。
  • SingleTop:
    SingleTop 模式一般常见于社交应用中的通知栏行为功能,例如:App 用户收到几条好友请求的推送消息,需要用户点击推送通知进入到请求者个人信息页,将信息页设置为 SingleTop 模式就可以增强复用性。
  • SingleTask:
    SingleTask 模式一般用作应用的首页,例如浏览器主页,用户可能从多个应用启动浏览器,但主界面仅仅启动一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。
  • SingleInstance:
    SingleInstance 模式常应用于独立栈操作的应用,如闹钟的提醒页面,当你在A应用中看视频时,闹钟响了,你点击闹钟提醒通知后进入提醒详情页面,然后点击返回就再次回到A的视频页面,这样就不会过多干扰到用户先前的操作了。

5.后台和前台Service

这就涉及到Service的分类了。
如果从是否无感知来分类,Service可以分为前台和后台。前台Service会通过通知的方式让用户感知到,后台有这么一个玩意在运行。
比如音乐类APP,在后台播放音乐的同时,可以发现始终有一个通知显示在前台,让用户知道,后台有一个这么音乐相关的服务。
在Android8.0,Google要求如果程序在后台,那么就不能创建后台服务,已经开启的后台服务会在一定时间后被停止。
所以,建议使用前台Service,它拥有更高的优先级,不易被销毁。使用方法如下:

扫描二维码关注公众号,回复: 15297498 查看本文章
startForegroundService(intent);
    public void onCreate() {
        super.onCreate();
        Notification notification = new Notification.Builder(this)
                .setChannelId(CHANNEL_ID)
                .setContentTitle("主服务")//标题
                .setContentText("运行中...")//内容
                .setSmallIcon(R.mipmap.ic_launcher)
                .build();
        startForeground(1,notification);
    }  

    <!--android 9.0上使用前台服务,需要添加权限-->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

那后台任务该怎么办呢?官方建议使用 JobScheduler 。
6.说说JobScheduler
任务调度JobScheduler,Android5.0被推出。(可能有的朋友感觉比较陌生,其实他也是通过Service实现的,这个待会再说)
它能做的工作就是可以在你所规定的要求下进行自动任务执行。比如规定时间、网络为WIFI情况、设备空闲、充电时等各种情况下后台自动运行。
所以Google让它来替代后台Service的一部分功能,使用案例:

  • 首先,创建一个JobService:
public class MyJobService extends JobService {

    @Override
    public boolean onStartJob(JobParameters params) {
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}
  • 然后,注册这个服务(因为JobService也是Service)
<service android:name=".MyJobService"
    android:permission="android.permission.BIND_JOB_SERVICE" />
  • 最后,创建一个JobInfo并执行
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);  
 ComponentName jobService = new ComponentName(this, MyJobService.class);

 JobInfo jobInfo = new JobInfo.Builder(ID, jobService) 
         .setMinimumLatency(5000)// 任务最少延迟时间 
         .setOverrideDeadline(60000)// 任务deadline,当到期没达到指定条件也会开始执行 
         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)// 网络条件,默认值NETWORK_TYPE_NONE
         .setRequiresCharging(true)// 是否充电 
         .setRequiresDeviceIdle(false)// 设备是否空闲
         .setPersisted(true) //设备重启后是否继续执行
         .setBackoffCriteria(3000,JobInfo.BACKOFF_POLICY_LINEAR) //设置退避/重试策略
         .build();  
 scheduler.schedule(jobInfo);

简单说下原理:

JobSchedulerService是在SystemServer中启动的服务,然后会遍历没有完成的任务,通过Binder找到对应的JobService,执行onStartJob方法,完成任务。具体可以看看参考链接的分析。
所以也就知道了,在5.0之后,如果有需要后台任务执行,特别是需要满足一定条件触发的任务,比如网络电量等等情况,就可以使用JobScheduler。
有的人可能要问了,5.0之前怎么办呢?
可以使用GcmNetworkManager或者BroadcastReceiver等处理部分情况下的任务需求。
Google也是考虑到了这一点,所以将5.0之后的JobScheduler和5.0之前的GcmNetworkManager、GcmNetworkManager、AlarmManager等和任务相关的API相结合,设计出了WorkManager。
7.说说WorkManager

WorkManager 是一个 API,可供您轻松调度那些即使在退出应用或重启设备后仍应运行的可延期异步任务。

作为Jetpack的一员,并不算很新的内容,它的本质就是结合已有的任务调度相关的API,然后根据版本需求等来执行这些任务,官网有一张图:

所以WorkManager到底能做什么呢?

1、对于一些任务约束能很好的执行,比如网络、设备空闲状态、足够存储空间等条件下需要执行的任务。
2、可以重复、一次性、稳定的执行任务。包括在设备重启之后都能继续任务。
3、可以定义不同工作任务的衔接关系。比如设定一个任务接着一个任务。
总之,它是后台执行任务的一大利器。

8.谈一谈startService和bindService的区别,生命周期以及使用场景?

  1. 生命周期上的区别

执行startService时,Service会经历onCreate->onStartCommand。当执行stopService时,直接调用onDestroy方法。调用者如果没有stopService,Service会一直在后台运行,下次调用者再起来仍然可以stopService。

执行bindService时,Service会经历onCreate->onBind。这个时候调用者和Service绑定在一起。调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。

多次调用startService,该Service只能被创建一次,即该Service的onCreate方法只会被调用一次。但是每次调用startService,onStartCommand方法都会被调用。Service的onStart方法在API 5时被废弃,替代它的是onStartCommand方法。

第一次执行bindService时,onCreate和onBind方法会被调用,但是多次执行bindService时,onCreate和onBind方法并不会被多次调用,即并不会多次创建服务和绑定服务。

  1. 调用者如何获取绑定后的Service的方法

onBind回调方法将返回给客户端一个IBinder接口实例,IBinder允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。我们需要IBinder对象返回具体的Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象。

  1. 既使用startService又使用bindService的情况呢?
    如果一个Service又被启动又被绑定,则该Service会一直在后台运行。首先不管如何调用,onCreate始终只会调用一次。对应startService调用多少次,Service的onStart方法便会调用多少次。Service的终止,需要unbindService和stopService同时调用才行。不管startService与bindService的调用顺序,如果先调用unbindService,此时服务不会自动终止,再调用stopService之后,服务才会终止;如果先调用stopService,此时服务也不会终止,而再调用unbindService或者之前调用bindService的Context不存在了(如Activity被finish的时候)之后,服务才会自动停止。

那么,什么情况下既使用startService,又使用bindService呢?

如果你只是想要启动一个后台服务长期进行某项任务,那么使用startService便可以了。如果你还想要与正在运行的Service取得联系,那么有两种方法:一种是使用broadcast,另一种是使用bindService。前者的缺点是如果交流较为频繁,容易造成性能上的问题,而后者则没有这些问题。因此,这种情况就需要startService和bindService一起使用了。

另外,如果你的服务只是公开一个远程接口,供连接上的客户端(Android的Service是C/S架构)远程调用执行方法,这个时候你可以不让服务一开始就运行,而只是bindService,这样在第一次bindService的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是远程服务,那么效果会越明显(当然在Servcie创建的是偶会花去一定时间,这点需要注意)。

  1. 本地服务与远程服务

本地服务依附在主进程上,在一定程度上节约了资源。本地服务因为是在同一进程,因此不需要IPC,也不需要AIDL。相应bindService会方便很多。缺点是主进程被kill后,服务变会终止。

远程服务是独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被kill的是偶,该服务依然在运行。缺点是该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。

对于startService来说,不管是本地服务还是远程服务,我们需要做的工作都一样简单。

9.Service如何进行保活?

利用系统广播拉活
利用系统service拉活
利用Native进程拉活<Android5.0以后失效> fork进行监控主进程,利用native拉活
利用JobScheduler机制拉活<Android5.0以后>
利用账号同步机制拉活

10.线程更新UI导致崩溃的原因?
在触发绘制方法requestLayout中,有个checkThread方法:

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

其中对mThread和当前线程进行了比较。而mThread是在ViewRootImpl实例化的时候赋值的。

所以崩溃的原因就是 view被绘制到界面时候的线程(也就是ViewRootImpl被创建时候的线程)和进行UI更新时候的线程不是同一个线程。

11.Activity、Dialog、PopupWindow、Toast 与Window的关系
这是扩展的一题,简单的从创建方式的角度来说一说:

Activity。在Activity创建过程中所创建的PhoneWindow,是层级最小的Window,叫做应用Window,层级范围1-99。(层级范围大的Window可以覆盖层级小的Window)
Dialog。Dialog的显示过程和Activity基本相同,也是创建了PhoneWindow,初始化DecorView,并将Dialog的视图添加到DecorView中,最终通过addView显示出来。
但是有一点不同的是,Dialog的Window并不是应用窗口,而是子窗口,层级范围1000-1999,子Window的显示必须依附于应用窗口,也会覆盖应用级Window。这也就是为什么Dialog传入的上下文必须为Activity的Context了。

PopupWindow。PopupWindow的显示就有所不同了,它没有创建PhoneWindow,而是直接创建了一个View(PopupDecorView),然后通过WindowManager的addView方法显示出来了。
没有创建PhoneWindow,是不是就跟Window没关系了呢?

并不是,其实只要是调用了WindowManager的addView方法,那就是创建了Window,跟你有没有创建PhoneWindow无关。View就是Window的表现形式,只不过PhoneWindow的存在让Window形象更立体了一些。

所以PopupWindow也是通过Window展示出来的,而它的Window层级属于子Window,必须依附与应用窗口。

Toast。Toast和PopupWindow比较像,没有新建PhoneWindow,直接通过addView方法显示View即可。不同的是它属于系统级Window,层级范围2000-2999,所以无须依附于Activity。
四个比较下来,可以发现,只要想显示View,就会涉及到WindowManager的addView方法,也就用到了Window这个概念,然后会根据不同的分层依次显示覆盖到界面上。

不同的是,Activity和Dialog涉及到了布局比较复杂,还会有布局主题等元素,所以用到了PhoneWindow进行一个解耦,帮助他们管理View。而PopupWindow和Toast结构比较简单,所以直接新建一个类似DecorView的View,通过addView显示到界面。

12.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类的使用。

扩展函数是静态解析的,是采用静态分派的过程来处理。这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的,而不是由表达式运行时求值结果决定的。这意思其实就是在使用该扩展函数的时候,如果类本身和其子类都进行了同一个函数的扩展,这函数是不会有重写关系的,在使用的时候,只会根据需要使用该方法的对象的实际类型来决定是调用了哪个,就是相当于调用静态方法。而不是动态分派。

总结

这次整理的这些每日一题都是一些编程题,其中有些还是我们日常开发中可能会遇到的问题,通过做这些题也可以检验一下自己对这些实用编程技巧的掌握程度。每日一题,每天成长一点点。

如果你觉得自己学习效率低,缺乏正确的指导,可以参考下下面分享我多年工作以来收集整理的学习路线,给大家做个参考:

  1. 确定好方向,梳理成长路线图

思维导图

知识梳理完之后,就需要进行查漏补缺。

  1. 看视频进行系统学习

因为我是转行进的移动开发行业,前几年的学习盲目啃书,结果面试时面试官一问,瞬间脑子空白,至此我才发现自己技术过于零散,对Android也不够深入、不够系统。当时就下定决心:重新进行一个系统的学习。我差的是系统知识,差的是结构框架和思路,于是我决定跟着一个老师的视频来进行系统的学习,效果更好,也更全面。关于视频学习,个人推荐可以去B站进行学习,B站上有很多学习视频,唯一的缺点就是由于是免费的,部分技术容易过时,而且部分涉及版权会被下架。

  1. 通过源码来系统性地学习

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。当然必不可少的还有自己手敲一遍,纸上得来终觉浅,绝知此事要躬行。

  1. 简历准备充分

可以去知乎上面搜搜别人写的有关简历的帖子,怎么准备,吸引hr, 突出自己的优点和能力,或者让朋友帮你看看简历有没有问题,比如过于简单或者浮夸,没有重点等。

尽量把你的亮点总结成一句简练的话,再加上数字来说明它的影响和意义。

其次在简历里中加入了可交互、可展示的内容,更能显出你的能力与众不同。

  1. 刷题备战,直通大厂

面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。

最后需要的朋友可以添加下方的二维码回复JJ免费领取,我们承诺100%免费
]

猜你喜欢

转载自blog.csdn.net/B1ueSocks/article/details/123556452