这是我近段时间收集的面试题, 献给打算年后找工作的同学们文中涉及的知识比较广也可能比较零散, 并且一些较为基础的知识我都略去了(比如 Android 四大组件是什么这类问题), 有些我附上了自己的理解, 有些附上了详细的相关文章链接大家挑自己感兴趣的内容查看即可, 后期我也会继续不断补充
基础组件篇
横竖屏切换时 Activity 的生命周期变化?
不设置 Activity 的 android:configChanges 时, 切屏会重新回调各个生命周期, 切横屏时会执行一次, 切竖屏时会执行两次
设置 Activity 的 android:configChanges=orientation 时, 切屏还是会调用各个生命周期, 切换横竖屏只会执行一次
设置 Activity 的 android:configChanges=orientation |keyboardHidden 时, 切屏不会重新调用各个生命周期, 只会执行 onConfigurationChanged 方法
onSaveInstanceState() 与 onRestoreIntanceState()
资源相关的系统配置发生改变或者资源不足: 例如屏幕旋转, 当前 Activity 会销毁, 并且在 onStop 之前回调 onSaveInstanceState 保存数据, 在重新创建 Activity 的时候在 onStart 之后回调 onRestoreInstanceState 其中 Bundle 数据会传到 onCreate(不一定有数据)和 onRestoreInstanceState(一定有数据) 用户或者程序员主动去销毁一个 Activity 的时候不会回调, 其他情况都会调用, 来保存界面信息如代码中 finish()或用户按下 back, 不会回调
如何保证 Service 不被杀死?
提供进程优先级, 降低进程被杀死的概率 方法一: 监控手机锁屏解锁事件, 在屏幕锁屏时启动 1 个像素的 Activity, 在用户解锁时将 Activity 销毁掉 方法二: 启动前台 service 方法三: 提升 service 优先级: 在 AndroidManifest.xml 文件中对于 intent-filter 可以通过 android:priority = "1000" 这个属性设置最高优先级, 1000 是最高值, 如果数字越小则优先级越低, 同时适用于广播
在进程被杀死后, 进行拉活 方法一: 注册高频率广播接收器, 唤起进程如网络变化, 解锁屏幕, 开机等 方法二: 双进程相互唤起 方法三: 依靠系统唤起 方法四: onDestroy 方法里重启 service:service +broadcast 方式, 就是当 service 走 ondestory 的时候, 发送一个自定义的广播, 当收到广播的时候, 重新启动 service;
依靠第三方 根据终端不同, 在小米手机 (包括 MIUI) 接入小米推送华为手机接入华为推送; 其他手机可以考虑接入腾讯信鸽或极光推送与小米推送做 A/B Test
简述下 Acitivty 任务栈和使用方法
任务栈是一种后进先出的结构位于栈顶的 Activity 处于焦点状态, 当按下 back 按钮的时候, 栈内的 Activity 会一个一个的出栈, 并且调用其 onDestory()方法如果栈内没有 Activity, 那么系统就会回收这个栈, 每个 APP 默认只有一个栈, 以 APP 的包名来命名. 1standard: 默认模式: 每次启动都会创建一个新的 activity 对象, 放到目标任务栈中
2singleTop: 判断当前的任务栈顶是否存在相同的 activity 对象, 如果存在, 则直接使用, 如果不存在, 那么创建新的 activity 对象放入栈中
3singleTask: 在任务栈中会判断是否存在相同的 activity, 如果存在, 那么会清除该 activity 之上的其他 activity 对象显示, 如果不存在, 则会创建一个新的 activity 放入栈顶
4singleIntance: 会在一个新的任务栈中创建 activity, 并且该任务栈种只允许存在一个 activity 实例, 其他调用该 activity 的组件会直接使用该任务栈种的 activity 对象
方法一: 使用 android:launchMode="standard|singleInstance|single Task|singleTop" 来控制 Acivity 任务栈
方法二: Intent Flags:
- Intent intent=new Intent();
- intent.setClass(MainActivity.this, MainActivity2.class);
- intent.addFlags(Intent. FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
Flags 有很多, 比如:
Intent.FLAG_ACTIVITY_NEW_TASK 相当于 singleTask
Intent. FLAG_ACTIVITY_CLEAR_TOP 相当于 singleTop
Context 相关问题
Activity 和 Service 以及 Application 的 Context 是不一样的, Activity 继承自 ContextThemeWraper. 其他的继承自 ContextWrapper.
每一个 Activity 和 Service 以及 Application 的 Context 都是一个新的 ContextImpl 对象 getApplication()用来获取 Application 实例的, 但是这个方法只有在 Activity 和 Service 中才能调用的到那么也许在绝大多数情况下我们都是在 Activity 或者 Service 中使用 Application 的, 但是如果在一些其它的场景, 比如 BroadcastReceiver 中也想获得 Application 的实例, 这时就可以借助 getApplicationContext()方法.
getApplicationContext()比 getApplication()方法的作用域会更广一些, 任何一个 Context 的实例, 只要调用 getApplicationContext()方法都可以拿到我们的 Application 对象
Context 的数量等于 Activity 的个数 + Service 的个数 + 1, 这个 1 为 Application.
那 Broadcast Receiver,Content Provider 呢? Broadcast Receiver,Content Provider 并不是 Context 的子类, 他们所持有的 Context 都是其他地方传过去的, 所以并不计入 Context 总数
怎么在 Service 中创建 Dialog 对话框
1. 在我们取得 Dialog 对象后, 需给它设置类型, 即:
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
2. 在 Manifest 中加上权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
View 篇
非 UI 线程可以更新 UI 吗?
可以
当访问 UI 时, ViewRootImpl 会调用 checkThread 方法去检查当前访问 UI 的线程是哪个, 如果不是 UI 线程则会抛出异常 执行 onCreate 方法的那个时候 ViewRootImpl 还没创建, 无法去检查当前线程. ViewRootImpl 的创建在 onResume 方法回调之后.
- void checkThread() {
- if (mThread != Thread.currentThread()) {
- throw new CalledFromWrongThreadException(
- "Only the original thread that created a view hierarchy can touch its views.");
- }
- }
非 UI 线程是可以刷新 UI 的, 前提是它要拥有自己的 ViewRoot, 即更新 UI 的线程和创建 ViewRoot 是同一个, 或者在执行 checkThread()前更新 UI.
解决 ScrollView 嵌套 ListView 和 GridView 冲突的方法
重写 ListView 的 onMeasure 方法, 来自定义高度:
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
- super.onMeasure(widthMeasureSpec, expandSpec);
- }
自定义 View 优化策略
为了加速你的 view, 对于频繁调用的方法, 需要尽量减少不必要的代码先从 onDraw 开始, 需要特别注意不应该在这里做内存分配的事情, 因为它会导致 GC, 从而导致卡顿在初始化或者动画间隙期间做分配内存的动作不要在动画正在执行的时候做内存分配的事情
你还需要尽可能的减少 onDraw 被调用的次数, 大多数时候导致 onDraw 都是因为调用了 invalidate(). 因此请尽量减少调用 invaildate()的次数如果可能的话, 尽量调用含有 4 个参数的 invalidate()方法而不是没有参数的 invalidate()没有参数的 invalidate 会强制重绘整个 view
另外一个非常耗时的操作是请求 layout 任何时候执行 requestLayout(), 会使得 Android UI 系统去遍历整个 View 的层级来计算出每一个 view 的大小如果找到有冲突的值, 它会需要重新计算好几次另外需要尽量保持 View 的层级是扁平化的, 这样对提高效率很有帮助
如果你有一个复杂的 UI, 你应该考虑写一个自定义的 ViewGroup 来执行他的 layout 操作与内置的 view 不同, 自定义的 view 可以使得程序仅仅测量这一部分, 这避免了遍历整个 view 的层级结构来计算大小这个 PieChart 例子展示了如何继承 ViewGroup 作为自定义 view 的一部分 PieChart 有子 views, 但是它从来不测量它们而是根据他自身的 layout 法则, 直接设置它们的大小
线程篇
HandlerMessageLooperMessageQueue
一相关概念的解释
主线程(UI 线程)
定义: 当程序第一次启动时, Android 会同时启动一条主线程(Main Thread) 作用: 主线程主要负责处理与 UI 相关的事件
Message(消息)
定义: Handler 接收和处理的消息对象(Bean 对象)
作用: 通信时相关信息的存放和传递
ThreadLocal
定义: 线程内部的数据存储类
作用: 负责存储和获取本线程的 Looper
MessageQueue(消息队列)
定义: 采用单链表的数据结构来存储消息列表
作用: 用来存放通过 Handler 发过来的 Message, 按照先进先出执行
Handler(处理者)
定义: Message 的主要处理者
作用: 负责发送 Message 到消息队列 & 处理 Looper 分派过来的 Message
Looper(循环器)
定义: 扮演 Message Queue 和 Handler 之间桥梁的角色
作用: 消息循环: 循环取出 Message Queue 的 Message 消息派发: 将取出的 Message 交付给相应的 Handler
2 自己画下图解
3Handler 发送消息有哪几种方式?
一 sendMessage(Message msg) 二 post(Ruunable r)
4Handler 处理消息有哪几种方式?
这个直接看 dispatchMessage()源码:
- public void dispatchMessage(Message msg) {
- if (msg.callback != null) {
- //1. post()方法的处理方法
- handleCallback(msg);
- } else {
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- //2. sendMessage()方法的处理方法
- handleMessage(msg);
- }
- }
- //1. post()方法的最终处理方法
- private static void handleCallback(Message message) {
- message.callback.run();
- }
- //2. sendMessage()方法的最终处理方法
- public void handleMessage(Message msg) {
- }
5.MessageHandlerMessageQueenLooper 的之间的关系?
首先, 是这个 MessageQueen,MessageQueen 是一个消息队列, 它可以存储 Handler 发送过来的消息, 其内部提供了进队和出队的方法来管理这个消息队列, 其出队和进队的原理是采用单链表的数据结构进行插入和删除的, 即 enqueueMessage()方法和 next()方法这里提到的 Message, 其实就是一个 Bean 对象, 里面的属性用来记录 Message 的各种信息
然后, 是这个 Looper,Looper 是一个循环器, 它可以循环的取出 MessageQueen 中的 Message, 其内部提供了 Looper 的初始化和循环出去 Message 的方法, 即 prepare()方法和 loop()方法在 prepare()方法中, Looper 会关联一个 MessageQueen, 而且将 Looper 存进一个 ThreadLocal 中, 在 loop()方法中, 通过 ThreadLocal 取出 Looper, 使用 MessageQueen 的 next()方法取出 Message 后, 判断 Message 是否为空, 如果是则 Looper 阻塞, 如果不是, 则通过 dispatchMessage()方法分发该 Message 到 Handler 中, 而 Handler 执行 handlerMessage()方法, 由于 handlerMessage()方法是个空方法, 这也是为什么需要在 Handler 中重写 handlerMessage()方法的原因这里要注意的是 Looper 只能在一个线程中只能存在一个这里提到的 ThreadLocal, 其实就是一个对象, 用来在不同线程中存放对应线程的 Looper
最后, 是这个 Handler,Handler 是 Looper 和 MessageQueen 的桥梁, Handler 内部提供了发送 Message 的一系列方法, 最终会通过 MessageQueen 的 enqueueMessage()方法将 Message 存进 MessageQueen 中我们平时可以直接在主线程中使用 Handler, 那是因为在应用程序启动时, 在入口的 main 方法中已经默认为我们创建好了 Looper
- handler = null;
- new Thread(new Runnable() {
- private Looper mLooper;
- @Override
- public void run() {
- // 必须调用 Looper 的 prepare 方法为当前线程创建一个 Looper 对象, 然后启动循环
- //prepare 方法中实质是给 ThreadLocal 对象创建了一个 Looper 对象
- // 如果当前线程已经创建过 Looper 对象了, 那么会报错
- Looper.prepare();
- handler = new Handler();
- // 获取 Looper 对象
- mLooper = Looper.myLooper();
- // 启动消息循环
- Looper.loop();
- // 在适当的时候退出 Looper 的消息循环, 防止内存泄漏
- mLooper.quit();
- }
- }).start();
- HandlerThread
- www.jackywang.tech/AndroidInte
- github.com/AweiLoveAnd
- github.com/hadyang/int
- jingbin.me/2017/02/20/
来源: https://juejin.im/post/5a82a07df265da4e7071c78f