Android面试题汇总及答案
汇总一下Android面试题,并逐渐补充答案。
最近比较忙,没有进一步整理,不过没有答案的题目也可以作为复习引导来用,后续有时间在补充吧。
Android:
1. 说一下四大组件。
四大组件指Activity、Service、BroadcastReceiver、ContentProvider,这四大组件都需要在AndroidManifest文件中注册才可以使用。
2. Activity生命周期和四种启动模式。
生命周期:
onCreate: Activity第一次创建时必然执行的方法,我们在这个方法中做一下初始化的操作,例如加载布局,初始化控件,注册点击事件等。
onStart: 当Activity变为可见时调用该方法。
onRestart: 该方法在Activity由停止状态转变为运行状态时调用,也就是说一个处于后台的Activity被重新启用了。(注意:后续还会调用onStart,不要以为onRestart之后就直接调用onResume了。)
onResume: 当Activity准备好和用户交互的时候调用,此时Activity必然处于栈顶,处于运行状态。
onPause: 当Activity不可交互时调用。例如被销毁时、按Home键返回桌面时,启动或者恢复某个Activity时。(注意:展示Dialog时不会调用onPasuse,有疑问可以自己测试。)
onStop: 该方法在Activity完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新Activity是一个对话框式的activity,那么,onPause()方法会得到执行,而onStop()方法并不会执行。
onDestroy: 这个方法在Activity被销毁之前调用,之后Activity的状态将变为销毁状态。
启动模式:
Standard模式:
标准模式,Standard模式是Android的默认启动模式,无需再Manifest文件中配置,这种模式下,Activity可以有多个实例,每次启动Activity,无论任务栈中是否已经有这个Activity的实例,系统都会创建一个新的Activity实例。
SingleTop模式:
栈顶复用模式,该模式需要在Manifest文件中配置。
SingleTop模式和standard模式非常相似,主要区别就是当一个singleTop模式的Activity已经位于任务栈的栈顶,再去启动它时,不会再创建新的实例,如果不位于栈顶,就会创建新的实例。
SingleTask模式:
栈内复用模式,该模式需要在Manifest文件中配置。
SingleTask模式的Activity在同一个Task内只有一个实例,如果Activity已经位于栈顶,系统不会创建新的Activity实例,和singleTop模式一样。但Activity已经存在但不位于栈顶时,系统就会把该Activity移到栈顶,并把它上面的activity出栈。
SingleInstance模式:
单例模式,该模式需要在Manifest文件中配置。
SingleInstance模式也是栈内单例的,但和SingleTask不同,SingleTask只是任务栈内单例,其栈内是可以有多个其他Activity实例的,且若有别的任务栈,其中也可能有该Activity;而SingleInstance模式的 Activity在整个系统里只有一个实例,切启动一SingleInstance模式的Activity时,系统会创建一个新的任务栈,并且这个任务栈只有他一个Activity。
3. Activity的启动过程(不要回答生命周期)。
Activity的启动过程,我们可以从Context的startActivity说起,其实现是ContextImpl的startActivity,然后内部会通过Instrumentation来尝试启动Activity,这是一个跨进程过程,它会调用ams的startActivity方法,当ams校验完activity的合法性后,会通过ApplicationThread回调到我们的进程,这也是一次跨进程过程,而applicationThread就是一个binder,回调逻辑是在binder线程池中完成的,所以需要通过Handler H将其切换到ui线程,第一个消息是LAUNCH_ACTIVITY,它对应handleLaunchActivity,在这个方法里完成了Activity的创建和启动,接着,在activity的onResume中,activity的内容将开始渲染到window上,然后开始绘制直到我们看见。
转自:https://blog.csdn.net/u010648159/article/details/81103092
4. Service的启动方式及生命周期(参考下方示例代码)。
Service的启动方式分为startService和bindService两种:
startService方式:生命周期为onCreate -> onStartCommand -> onDestroy。
bindService方式:生命周期为onCreate -> onBind -> onUnbind -> onDestroy。
注意:多次startService或者bindService,onCreate方法也只会调用一次。
也可以混合启动:
先bind后start:生命周期为onCreate -> onBind -> onStartCommand。
先start后bind:生命周期为onCreate -> onStartCommand -> onBind。
至于混合启动后的unbind和stop操作,结果都是一样的:
如果先unbind,后stop:则会依次执行onUnbind和onDestroy方法。
但是如果先stop,后unbind:则会有所不同:试图stop时,并没有任何反应,但是接下来unbind到时候,会在调用onUnbind后,直接调用onDestroy方法。
示例代码:
/*=============MainActivity=============*/
//开启服务
fun startService(){
val intent = Intent(this, MyService::class.java)
startService(intent)
}
//停止服务
fun stopService(){
val intent = Intent(this, MyService::class.java)
stopService(intent)
}
//绑定服务
fun bindService() {
if (conn == null) {
val intent = Intent(this, MyService::class.java)
conn = object : ServiceConnection{
override fun onServiceDisconnected(name: ComponentName?) {
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
myIBinder = service as MyService.MyIBinder
}
}
bindService(intent, conn!!, Context.BIND_AUTO_CREATE)
}
}
//解绑服务
fun unbindService(){
if (conn != null) {
unbindService(conn!!)
conn = null
}
}
/*=============MainActivity=============*/
/*=============MyService=============*/
override fun onCreate() {
super.onCreate()
loge("onCreate")
}
override fun onBind(intent: Intent?): IBinder? {
loge("onBind")
return MyIBinder()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
loge("onStartCommand")
return super.onStartCommand(intent, flags, startId)
}
override fun onUnbind(intent: Intent?): Boolean {
loge("onUnbind")
return super.onUnbind(intent)
}
override fun onDestroy() {
super.onDestroy()
loge("onDestroy")
}
/*=============MyService=============*/
5. BroatcastReceiver的注册方式。
BroatcastReceiver的注册方式分为静态注册和动态注册两种。
动态注册广播不是常驻型广播,也就是说广播跟随Activity的生命周期。注意在Activity结束前,移除广播接收器。 静态注册是常驻型,也就是说当应用程序关闭后,如果有合适的广播,程序也会被系统调用自动运行。
当广播为有序广播时:优先级高的先接收(不分静态和动态)。同优先级的广播接收器,动态优先于静态
同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。
当广播为默认广播时:无视优先级,动态广播接收器优先于静态广播接收器。同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后册的。
6. ContentProvider:
相关文章:https://www.jianshu.com/p/380231307070
8. 子线程能更新UI吗?
Android的单线程模型模型要求必须要在UI线程更新UI,否则认为是不安全的,原则上子线程是不能更新UI的。在其他线程访问UI,Android给提供了如下一些方法:
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable, long)
- Handler
说道低都是调度到主线程来完成更新UI的工作。
其实如果非要直接在子线程强行更新UI,则会得到一个异常:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
实际上,通过CalledFromWrongThreadException的堆栈信息可以追踪ViewRootImpl这个类,实际上到这个是通过其中的一个checkThread方法,来检查当前访问的线程,如果不是UI线程,就会抛出该异常。
回到问题本身,子线程真的不能更新UI吗?
实际上在如下情况中子线程是可以更新的UI的:
在Activity执行onCreate时,ViewRootImpl还没有实例化,加入此时立即new一个Thread更新UI,则不会抛出异常。
另外,如果我们能够通过某种方法,在子线程中拥有ViewRootImpl,那么也可以更新UI,具体方法就不深究了。
7. Java虚拟机和Dalvik虚拟机和Art的区别。
相关文章:
https://www.jianshu.com/p/713d24fa9982
https://blog.csdn.net/evan_man/article/details/52414390
8. 进程和线程。
进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,一个进程可以包含多个线程。
进程和线程的关系和区别如下:
- 进程是cpu资源分配的最小单位, 线程是cpu调度的最小单位。
- 线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。
- 不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。
- 进程之间不能共享资源,而线程共享所在进程的内存空间和其它资源。
- 一个进程内可拥有多个线程,可开启新的进程,也可开启线程。
- 一个线程只能属于一个进程,线程可直接使用同进程的资源,线程依赖于进程而存在。
9. 进程保活(不死进程)。
首先明确一点,“不死进程”本身其实是一个伪命题,尽量优化APP自身才是正道。
首先说一下进程的优先级:
根据进程的重要性,划分5级,优先级依次递减:
前台进程 (Foreground process)
可见进程 (Visible process)
服务进程 (Service process)
后台进程 (Background process)
空进程 (Empty process)
保活手段:
当前业界的Android进程保活手段主要分为** 黑、白、灰 **三种,其大致的实现思路如下:
- 黑色保活:不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)
所谓黑色保活,就是利用不同的app进程使用广播来进行相互唤醒。举个3个比较常见的场景:
场景1:开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app
场景2:接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景3
场景3:假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差不多)
- 白色保活:启动前台Service
白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。如音乐APP的可操作的通知。
- 灰色保活:利用系统的漏洞启动前台Service
灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:
思路一:API < 18,启动前台Service时直接传入new Notification(); 思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理
下面补充一下Android的杀进程机制:
熟悉Android系统的童鞋都知道,系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于Linux内核的 OOM Killer(Out-Of-Memory killer)机制诞生。
了解完 Low Memory Killer,再科普一下oom_adj。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于oom_adj的作用,你只需要记住以下几点即可:
进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收
普通app进程的oom_adj>=0,系统进程的oom_adj才可能<0
有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。
所以,进程保活的根本方案终究还是回到了性能优化上,进程永生不死终究是个彻头彻尾的伪命题!
10. 讲解一下Context。
先来看一下Context家族的继承树结构:
image.png
可以看出,Context的直接子类有两个,分别为ContextWrapper,一个是ContextImpl。顾名思义,ContextWrapper是Context的包装(封装)类,ContextImpl是Context的实现类。
可以看到ContextWrapper有三个直接的子类,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity。
所以在Android工程中,Context只有3中来源或者说3中类型,分别是:Application,Activity,Service。那么也就不难统计真正的Context的数量了:
Context数量 = Activity数量 + Service数量 + 1(Application)。
那么ContextImpl是怎么回事呢,前面说了,这是一个Context的实现类,由它来具体实现了Context中定义的各种方法,而ContextWrapper是Context的包装类,我们看一看ContextWrapper的源码就会发现,其中有一个Context成员变量mBase,ContextWrapper将Context方法的实现全部委托给mBase负责,其中有一个方法attachBaseContext——用来给mBase赋值,且只能赋值一次,否则会抛异常,另有一个方法getBaseContext,可以用来获取mBase。
//=========ContextWrapper部分代码=========
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
/**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
/**
* @return the base context as set by the constructor or setBaseContext
*/
public Context getBaseContext() {
return mBase;
}
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources() {
return mBase.getResources();
}
@Override
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
//=========ContextWrapper部分代码=========
各种Context的作用:
使用场景 | Application | Activity | Service | ContentProvider | BroadcastReceiver |
---|---|---|---|---|---|
Show a Dialog | N | Y | N | N | N |
Start an Activity | N(1) | Y | N(1) | N(1) | N(1) |
Layout Inflation | N(2) | Y | N(2) | N(2) | N(2) |
Start a Service | Y | Y | Y | Y | Y |
Bind a Service | Y | Y | Y | Y | N |
Send a Broadcast | Y | Y | Y | Y | Y |
Register Broadcast Receiver | Y | Y | Y | Y | N(3) |
Load Resource Value | Y | Y | Y | Y | Y |
注:其中ContentProvider,BroadcastReceiver指的内部方法中做为参数传递进来的Context。
大家注意看到有一些NO后面的括号添加了一些数字,其实这些从能力上来说是YES,但是为什么说是NO呢?下面一个一个解释:
N(1):启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。
N(2):在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
N(3):在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)
可以看出:和UI相关的方法基本都不建议或者不可使用Application,并且,前三个操作基本不可能在Application中出现。实际上,只要把握住一点,凡是跟UI相关的,都应该使用Activity做为Context来处理
11. 理解Activity,View,Window三者关系。
12. View的绘制流程。
13. View,ViewGroup事件分发。
14. 简述如何自定义控件。
14. 保存Activity状态。
15. Android中的几种动画。
16. Android中跨进程通讯的几种方式。
17. AIDL理解(此处延伸:简述Binder)。
18. Handler的工作原理。
19. Android内存泄露及管理。
20. Fragment与Fragment、Activity通信的方式。
21. Android UI适配。
字体使用sp,使用dp,多使用match_parent,wrap_content,weight
图片资源,不同图片的的分辨率,放在相应的文件夹下。可使用百分比代替。
相关文章:https://www.jianshu.com/p/a4b8e4c5d9b0?tdsourcetag=s_pcqq_aiomsg