Fork me on GitHub

安卓面试清单

一、Activity相关:

1. Activity的加载模式。

http://blog.csdn.net/mynameishuangshuai/article/details/51491074

任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。

注:某些适用于清单文件的启动模式不可用作 Intent 标志,同样,某些可用作 Intent 标志的启动模式无法在清单文件中定义。

standard-默认模式

这个模式是默认的启动模式,即标准模式,在不指定启动模式的前提下,系统默认使用该模式启动Activity,每次启动一个Activity都会重写创建一个新的实例,不管这个实例存不存在,这种模式下,谁启动了该模式的Activity,该Activity就属于启动它的Activity的任务栈中。这个Activity它的onCreate(),onStart(),onResume()方法都会被调用。

singleTop模式分3种情况

  • 当前栈中已有该Activity的实例并且该实例位于栈顶时,不会新建实例,而是复用栈顶的实例,并且会将Intent对象传入,回调onNewIntent方法
  • 当前栈中已有该Activity的实例但是该实例不在栈顶时,其行为和standard启动模式一样,依然会创建一个新的实例
  • 当前栈中不存在该Activity的实例时,其行为同standard启动模式

standard和singleTop启动模式都是在原任务栈中新建Activity实例,不会启动新的Task,即使你指定了taskAffinity属性。

那么什么是taskAffinity属性呢,可以简单的理解为任务相关性。

  • 这个参数标识了一个Activity所需任务栈的名字,默认情况下,所有Activity所需的任务栈的名字为应用的包名。

  • 我们可以单独指定每一个Activity的taskAffinity属性覆盖默认值。

  • 一个任务的affinity决定于这个任务的根activity(root activity)的taskAffinity。

  • 在概念上,具有相同的affinity的activity(即设置了相同taskAffinity属性的activity)属于同一个任务。

  • 为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task。

  • 很重要的一点taskAffinity属性不对standard和singleTop模式有任何影响,即时你指定了该属性为其他不同的值,这两种启动模式下不会创建新的task(如果不指定即默认值,即包名)

singleTask-栈内复用模式 (MainActivity时会用到)

这个模式十分复杂,有各式各样的组合。在这个模式下,如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,会将它上面的Activity全部出栈,并且会回调该实例的onNewIntent方法。其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,会在自己需要的任务栈中寻找实例,这个任务栈就是通过taskAffinity属性指定。如果这个任务栈不存在,则会创建这个任务栈。

  • singleTask启动模式启动Activity时,首先会根据taskAffinity去寻找当前是否存在一个对应名字的任务栈。

  • 如果不存在,则会创建一个新的Task,并创建新的Activity实例入栈到新创建的Task中去。

  • 如果存在,则得到该任务栈,查找该任务栈中是否存在该Activity实例。

    • 如果存在实例,则将它上面的Activity实例都出栈,然后回调启动的Activity实例的onNewIntent方法。

    • 如果不存在该实例,则新建Activity,并入栈。

此外,我们可以将两个不同App中的Activity设置为相同的taskAffinity,这样虽然在不同的应用中,但是Activity会被分配到同一个Task中去。

singleInstance-全局唯一模式

该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。

整个系统即为Android系统,允许不同的APP应用同用一个任务栈。


2. Application、Activity、Fragment、broadcastReceiver、service生命周期,横竖屏切换时候Activity的生命周期

###Application:

Application和Activity,Service一样,是android框架的一个系统组件,当android程序启动时系统会创建一个application对象,用来存储系统的一些信息。application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。因为它是全局的单例的,所以在不同的Activity,Service中获得的对象都是同一个对象。所以通过Application来进行一些,数据传递,数据共享 等,数据缓存等操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class BaseApplication extends Application {
@Override
public void onCreate() {
// 程序创建的时候执行
super.onCreate();
}
@Override
public void onTerminate() {
/**
* onTerminate()会在app关闭的时候调用,但是就像onDestroy()一样,
* 不能保证一定会被调用。
* 系统内存不足可能强制杀掉你的进程,这时候onterminate就不调了。
* 所以最好不要依赖这个方法做重要的处理,
* 这个方法最多可以用来销毁一写对象,清除一下缓存,
* 但是也并不能保证一定会清除掉,其他操作,例如想在程序结束保存
* 数据,用这个方法明显是错误的。
*/
super.onTerminate();
}
@Override
public void onLowMemory() {
/**onLowMemory()在内存比较紧张时,根据优先级把后台程序杀死
时,系统回调他,它用在14之前,14之后就出现了onTrimMemory()
*/
super.onLowMemory();
}
@Override
public void onTrimMemory(int level) {
/**
* 程序在内存清理的时候执行(回收内存)
* HOME键退出应用程序、长按MENU键,打开Recent TASK都会执行。
*/
super.onTrimMemory(level);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
}

###Activity:

这里写图片描述
这里写图片描述
这里写图片描述

程序启动运行并结束上述生命周期的方法执行顺序是这样的:

1
onCreate –> onContentChanged –> onStart –> onPostCreate –> onResume –> onPostResume –> onPause –> onStop –> onDestroy

- onContentChanged

onContentChanged()是Activity中的一个回调方法 当Activity的布局改动时,即setContentView()或者addContentView()方法执行完毕时就会调用该方法, 例如,Activity中各种View的findViewById()方法都可以放到该方法中。

- onPostCreate、onPostResume

onPostCreate方法是指onCreate方法彻底执行完毕的回调,onPostResume类似,这两个方法官方说法是一般不会重写,现在知道的做法也就只有在使用ActionBarDrawerToggle的使用在onPostCreate需要在屏幕旋转时候等同步下状态,Google官方提供的一些实例就是如下做法:

1
2
3
4
5
6
7
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}

- onPause、 onStop

这里顺便再提一下onPause、 onStop的区别, onPause是在整个窗口被半遮盖或者半透明的时候会执行,而onStop则是在整个窗口被完全遮盖才会触发, 触发onStop的方法之前必定会触发onPause方法。

- onCreate、 onStart

onCreate方法会在第一次创建的时候执行,紧接着便会执行onStart方法,之后页面被完全遮挡会执行onStop方法,再返回的时候一般便会执行onRestart –> onStart方法, 但是如果如果这时候App内存不够需要更多的内存的时候,App便会杀死该进程,结束掉该Activity,所以这时候再返回的时候便会重新执行onCreate –> onStart –> onResume方法。

- onSaveInstanceState、 onRestoreInstanceState

  • onSaveInstanceState

onSaveInstanceState字面理解就是保存实例的状态,当某个activity变得“容易”被系统销毁时,该activity的onSaveInstanceState就会被执行,除非该activity是被用户主动销毁的,例如当用户按BACK键的时候。

注意上面的双引号,何为“容易”?言下之意就是该activity还没有被销毁,而仅仅是一种可能性。这种可能性有这么几种情况:

  1. 当用户按下HOME键时

这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则

  1. 长按HOME键,选择运行其他的程序时。
  2. 按下电源按键(关闭屏幕显示)时。
  3. 从activity A中启动一个新的activity时。
  4. 屏幕方向切换时,例如从竖屏切换到横屏时。

在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState一定会被执行

总而言之,onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。

  • onRestoreInstanceState

onSaveInstanceState字面理解就是恢复实例的状态, 需要注意的是,onSaveInstanceState方法和onRestoreInstanceState方法“不一定”是成对的被调用的,onRestoreInstanceState被调用的前提是,activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行。

不过大多数情况下也是很少使用onRestoreInstanceState方法的,经常我们还是在onCreate方法里直接恢复状态的,onCreate方法里本身会有一个Bundle参数的,很多时候我们是这样使用的。(onCreate在onStart之前调用,而onRestoreInstanceState是在onStart之后调用)

1
2
3
4
5
6
7
8
9
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(icicle);
savedInstanceState.putLong("param", value);
}
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null){
value = savedInstanceState.getLong("param");
}
}

来结合一些特定的使用场景来分析下Activity的生命周期。

首次启动

1
onCreate –> onStart –> onResume

按下返回按键

1
onPause –> onStop –> onDestroy

按Home键

1
onPause –> onSaveInstanceState –> onStop

再次打开

1
onRestart –> onStart –> onResume

屏幕旋转

如果你不做任何配置,启动Activity会执行如下方法:

1
onCreate –> onStart –> onResume

之后旋转屏幕,则Activity会被销毁并重新创建,之后便会执行如下方法:

1
onPause –> onSaveInstanceState –> onStop –> onDestroy –> onCreate –> onStart –> onRestoreInstanceState –> onResume

在AndroidManifest配置文件里声明android:configChanges属性
默认屏幕旋转会重新创建,当然可以通过在配置文件里加上如下代码:

1
android:configChanges="keyboardHidden|orientation|screenSize"(sdk>13时需加上screenSize)

这个时候再旋转屏幕便不会销毁Activity,这时候再旋转屏幕可以看到只会执行onConfigurationChanged方法,有什么在屏幕旋转的逻辑可以重写这个方法:

1
2
3
4
5
6
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig.orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
// TODO:
}
super.onConfigurationChanged(newConfig);
}

FirstActivity打开SecondActivity

FirstActivity打开SecondActivity,这时候FirstActivity生命周期的方法是这样的: onPause –> onSaveInstanceState –> onStop, 这个时候在SecondActivity按返回键,FirstActivity会有以下集中情况:

正常情况下会执行:

1
onRestart -> onStart -> onResume

当系统由于要回收内存而把 activity 销毁时,

Activity在onPause或者onStop状态下都有可能遇到由于突发事件系统需要回收内存,之后的onDestroy方法便不会再执行,这时候会执行:

1
onCreate –> onStart –> onRestoreInstanceState –> onResume

二、Handler相关:

1. Handler机制。

1、 (鸿洋Android 异步消息处理机制 让你深入理解Looper、Handler、Message三者关系)

2、子线程更新UI问题?(不严谨):

  • 情况1:若在子线程(需要标记)创建ViewRoot对象,调用windowmanager的addview()方法,添加对应的视图,则可以更新UI,参考:忘记地址了……

  • 情况2:在onresume()之前,比如oncreate(),开启子线程并且更新UI,不会报错,因为此时还没有执行requestlayout(),也未执行checkthread()方法。参考自己的博客(子线程真的不能更新UI吗?

UI控件是线程不安全的。若在子线程中更新UI,会产生竞争、死锁等问题,多个子线程更新UI会使UI控件混乱,且不可控制。

2. Looper。

1、 如何保证一个线程只有一个Looper?

Looper.prepare()方法已经保证了一个线程只能拥有一个Looper实例。但是其中是使用ThreadLocal进行保存这个looper对象的。那么ThreadLocal怎么保证这个looper唯一呢,而不是作为一个同步共享变量呢?

Java并发编程:深入剖析ThreadLocal

阅读上文(注意阅读评论,作者原文内容部分有歧义)我们可以知道:

一个线程内部可以创建多个ThreadLocal,每个ThreadLocal维护一个ThreadLocalMap,每个ThreadLocalMap的key(键)为ThreadLocal对象,我们需要保存的任何对象作为value(值);

在looper.prepare()方法里面只创建了一个sThreadLocal对象,通过

1
sThreadLocal.set(new Looper(true));

方法可以保存此looper的引用(某些地方称为副本,并不准确),可见此looper 在每个线程中是唯一的,因为:

  1. 每个sThreadLocal只能存放一个键值对。
  2. 下面这段代码保证了looper不能被new两次。
    1
    2
    3
    if (sThreadLocal.get() != null) {
    throw new RuntimeException("Only one Looper may be created per thread");
    }

三、多线程:

1. 安卓多线程的方式有哪些。

###安卓多线程方式:

Handler+Thread(sendmessage() 和 handler.post())

AsynTask

ThreadPoolExecutor

IntentService

###安卓异步更新UI方式:

sendmessage() 和 handler.post()

AsynTask

RunOnUiThread

view.post()(需要确保View已经attach到window)详见View#post与Handler#post的区别,以及导致的内存泄漏分析

###详解几种线程池:

Android开发之线程池使用总结

###多线程面试基础知识点-java教程:

  1. 什么是进程?什么是线程?进程与线程的关系是怎样的?
    【答】进程:进程是一个可并发的具有独立功能的程序关于某个数据集合的一次执行过程,也是操作系统进行资源分配和保护的基本单位。
    线程:线程是操作系统进程中能够独立执行的实体,是处理器调度和分派的基本单位。
    线程是进程的组成部分,每个进程内允许包含多个并发执行的线程。
  1. 操作系统为什么要支持多线程技术?
    【答】操作系统采用进程机制能够减少程序并发时所付出的时空开销,使得并发粒度更细,并发性更好,提高了资源使用率和系统效率。
    1. Java为什么要支持线程?什么场合需要使用多线程程序设计?
      【答】支持多线程能充分发挥硬件的并发性,消除处理器和I/O设备的互等现象,提高系统效率。 一般一下场合需要使用多线程:1、程序包好复杂的计算任务时,主要是利用多线程获取更所得CPU时间。2、处理速度较慢的外围设备。3、程序设计自身的需要。
    2. Java提供了哪些接口和类实现多线程机制?
      【答】Java主要提供了java.lang.Runnable接口和Thread线程类来实现多线程机制。
    3. 一个线程对象的生命周期有哪几种状态构成?各状态之间是如何变化的?
      【答】
      线程对象的生命周期主要包括:新建态、就绪态和运行态、阻塞态和等待态、终止态。新建态通过start()方法是线程成为运行态,当运行态遇到sleep()或wait()方法时就进入等待态,而当sleep()时间到或通过notify()方法线程就又进入了运行态;当运行态遇到synchronized()方法时就进入阻塞态,当线程获得互斥锁使用权时就又到了运行态;当线程的run()方法结束时整个线程就进入了终止态,整个线程结束。
    4. Java提供了哪些方法能够改变线程状态?程序中能够调度线程立即执行吗?
      【答】Java中提供了start()方法来启动已创建的线程对象;sleep()方法使当前线程睡眠若干毫秒,线程有运行态进入等待态,但是不交出临界区的锁;yield() 方法暂停当前线程的执行,允许其他线程竞争CPU;stop()方法来结束一个线程;wait()方法将本线程设为等待态;notify() 方法来唤醒当前处于等待态的线程;interrupt()方法改变当前线程的中断状态,但是当前线程还可以继续执行。
    5. 什么是线程的优先级?设置线程优先级有什么作用?
      【答】每个线程被执行的优先顺序即为线程的优先级,默认优先级为5。当创建了多个线程并要执行这些线程时,操作系统不知到底该执行哪个线程,当设置好优先级后,程序首先会执行优先级最高的线程,然后依次执行下去。这样处理器将会合理而且充实的额被利用,不会造成资源的浪费或者运行的混乱。
    6. 线程按什么规则排队等待?
      【答】线程按优先级排队,线程调度语句优先级基础上的“先到先服务”原则
    7. 多线程间共享数据时会发生怎样的并发执行错误?
      【答】当几个线程共享数据时,一个线程的执行可能影响其他线程的执行结果,并导致错误的程序运行结果。
    8. 在什么情况下需要采用线程同步机制?
      【答】当并发执行的多个线程间需要共享资源或交换数据时,各个线程执行时相互之间会干扰或影响其他线程的执行结果,这时就需要采用线程同步机制。
    9. 关键字synchronized是什么含义?为什么wait()和notify()方法要与synchronized同时使用?
      【答】synchronized用于声明一段程序为临界区,使线程对临界资源采用互斥使用方式。
      wait()和notify()不是属于线程类,而是每一个对象都具有的方法,而且这两个方法都和对象锁有关,有锁的地方必有synchronized()方法,故wait()和notify()方法要与synchronized同时使用。
    10. 明sleep()和wait()方法的异同。
      【答】wait是Object类的方法,sleep与yield都是Thread类的方法。wait调用的时候需要注意的是该方法是释放锁标志的,而sleep在调用的时候是紧紧抱着锁标志的,也就是等他完全执行完成了才可以让其他线程来访问的
      sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。
    11. 一个对象为什么需要调用wait()方法使当前线程等待?
      【答】wait()使当前线程进入停滞状态时,还会释放当前线程所占有的“锁标志”, 从而使线程所在对象中的其它synchronized数据可被别的线程使用。当调用sleep()方法时,该线程不会释放当前线程所占有的“锁标志”。
    12. 什么是死锁?什么情况下会死锁?
      【答】由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
      当多个线程同时被阻塞,他们中的一个或者全部都在等待某个资源被释放,由于线程被无限期地阻塞,因此程序处于非正常终止,才会产生死锁。

四、ANR异常发生条件:

###发生条件:

ANR 一般有三种类型:

  • KeyDispatchTimeout(5 seconds) –主要类型

    按键或触摸事件在特定时间内无响应

  • BroadcastTimeout(10 seconds)

    BroadcastReceiver在特定时间内无法处理完成

  • ServiceTimeout(20 seconds) –小概率类型

超时的原因一般有两种:

  1. 当前的事件没有机会得到处理(UI线程正在处理前一个事件没有及时完成或者looper被某种原因阻塞住)。
  2. 当前的事件正在处理,但没有及时完成。
    UI线程尽量只做跟UI相关的工作,耗时的工作(数据库操作,I/O,连接网络或者其他可能阻碍 UI线程的操作)放入单独的线程处理,尽量用Handler来处理 UI thread 和 thread 之间的交互。
    UI线程主要包括如下:
    Activity:onCreate(), onResume(), onDestroy(), onKeyDown(), onClick()
    AsyncTask: onPreExecute(), onProgressUpdate(), onPostExecute(), onCancel()
    Mainthread handler: handleMessage(), post(runnable r)

###如何分析ANR:

查找 ANR 的方式:

  • 导出/data/data/anr/traces.txt,找出函数和调用过程,分析代码
  • 通过 LOG 查找

五、自定义View和ViewGroup:

###自定义View:

1、教你步步为营掌握自定义View(修改原文中广播为 Timer更好)
2、个人项目 H-Express 自定义时间轴(待完善)

###自定义ViewGroup:

教你步步为营掌握自定义ViewGroup

六、安卓事件分发处理:

图解 Android 事件分发机制

可能是讲解Android事件分发最好的文章

七、GC算法:

JVM学习之GC常用算法

你不得不了解的JVM(一、二)

八、四大引用:

Android性能提升之强引用、软引用、弱引用、虚引用使用

九、安卓动画:

Android动画学习笔记-Android Animation

Android 属性动画(Property Animation) 完全解析 (上)

十、Dalvik和Art区别?:

Dalvik和ART运行时环境的区别


csdn地址:http://blog.csdn.net/u012534831
github地址:https://github.com/qht1003077897
个人博客地址:https://qht1003077897.github.io
QQ:1003077897

安卓面试清单