写点什么

三年经验 Android 开发面经总结

用户头像
Android架构
关注
发布于: 14 小时前

Intent.FLAG_ACTIVITY_NEW_TASK 相当于 singleTaskIntent. 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 方法,来自定义高度:


@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec, expandSpec);}


主要考察对 MeasureSpec 的三种模式的理解,相关文章.

自定义 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 法则,直接设置它们的大小。


线程篇

Handler、Message、Looper、MessageQueue

一、相关概念的解释


  • 主线程(UI 线程)


定义:当程序第一次启动时,Android 会同时启动一条主线程(Main Thread) 作用:主线程主要负责处理与 UI 相关的事件


  • Message(消息)


定义:Handler 接收和处理的消息对象(Bean 对象)作用:通信时相关信息的存放和传递


  • ThreadLocal


定义:ThreadLocal 是线程内部的存储类,通过它可以实现在每个线程中存储自己的私有数据。即数据存储以后,只能在指定的线程中获取这个存储的对象,而其它线程则不能获取到当前线程存储的这个对象。作用:负责存储和获取本线程的 Looper


  • MessageQueue(消息队列)


定义:采用单链表的数据结构来存储消息列表作用:用来存放通过 Handler 发过来的 Message,按照先进先出执行


  • Handler(处理者)


定义:Message 的主要处理者作用:负责发送 Message 到消息队列 &处理 Looper 分派过来的 Mes


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


sage


  • Looper(循环器)


定义:扮演 Message Queue 和 Handler 之间桥梁的角色作用: 消息循环:循环取出 Message Queue 的 Message 消息派发:将取出的 Message 交付给相应的 Handler


2、自己画下图解



3、Handler 发送消息有哪几种方式?


一、sendMessage(Message msg)二、post(Ruunable r)


4、Handler 处理消息有哪几种方式?


这个直接看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.Message、Handler、MessageQueue、Looper 的之间的关系?


首先,是这个 MessagQueue,MessageQueue 是一个消息队列,它可以存储 Handler 发送过来的消息,其内部提供了进队和出队的方法来管理这个消息队列,其出队和进队的原理是采用单链表的数据结构进行插入和删除的,即 enqueueMessage()方法和 next()方法。这里提到的 Message,其实就是一个 Bean 对象,里面的属性用来记录 Message 的各种信息。


然后,是这个 Looper,Looper 是一个循环器,它可以循环的取出 MessageQueue 中的 Message,其内部提供了 Looper 的初始化和循环出去 Message 的方法,即 prepare()方法和 loop()方法。在 prepare()方法中,Looper 会关联一个 MessageQueue,而且将 Looper 存进一个 ThreadLocal 中,在 loop()方法中,通过 ThreadLocal 取出 Looper,使用 MessageQueue 的 next()方法取出 Message 后,判断 Message 是否为空,如果是则 Looper 阻塞,如果不是,则通过 dispatchMessage()方法分发该 Message 到 Handler 中,而 Handler 执行 handlerMessage()方法,由于 handlerMessage()方法是个空方法,这也是为什么需要在 Handler 中重写 handlerMessage()方法的原因。这里要注意的是 Looper 只能在一个线程中只能存在一个。这里提到的 ThreadLocal,其实就是一个对象,用来在不同线程中存放对应线程的 Looper。


最后,是这个 Handler,Handler 是 Looper 和 MessageQueue 的桥梁,Handler 内部提供了发送 Message 的一系列方法,最终会通过 MessageQueue 的 enqueueMessage()方法将 Message 存进 MessageQueue 中。我们平时可以直接在主线程中使用 Handler,那是因为在应用程序启动时,在入口的 main 方法中已经默认为我们创建好了 Looper。


6.为什么在子线程中创建 Handler 会抛异常?


Handler 的工作是依赖于 Looper 的,而 Looper(与消息队列)又是属于某一个线程(ThreadLocal 是线程内部的数据存储类,通过它可以在指定线程中存储数据,其他线程则无法获取到),其他线程不能访问。因此 Handler 就是间接跟线程是绑定在一起了。因此要使用 Handler 必须要保证 Handler 所创建的线程中有 Looper 对象并且启动循环。因为子线程中默认是没有 Looper 的,所以会报错。 正确的使用方法是:


private final class WorkThread extends Thread {


private Handler mHandler;


public Handler getHandler() {return mHandler;}public void quit() {mHandler.getLooper().quit();}@Overridepublic void run() {super.run();//创建该线程对应的 Looper,// 内部实现// 1。new Looper()// 2。将 1 步中的 lopper 放在 ThreadLocal 里,ThreadLocal 是保存数据的,主要应用场景是:线程间数据互不影响的情况// 3。在 1 步中的 Looper 的构造函数中 new MessageQueue();//其实就是创建了该线程对用的 Looper,Looper 里创建 MessageQueue 来实现消息机制//对消息机制不懂得同学可以查阅资料,网上很多也讲的很不错。Looper.prepare();mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);}};//开启消息的死循环处理即:dispatchMessageLooper.loop();//注意这 3 个的顺序不能颠倒 Log.d("WorkThread", "end");}}

HandlerThread

1、HandlerThread 作用


当系统有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google 提供了 HandlerThread,HandlerThread 是在线程中创建一个 Looper 循环器,让 Looper 轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞。


2、HanlderThread 的优缺点


  • HandlerThread 本质上是一个线程类,它继承了 Thread;

  • HandlerThread 有自己的内部 Looper 对象,可以进行 looper 循环;

  • 通过获取 HandlerThread 的 looper 对象传递给 Handler 对象,可以在handleMessage()方法中执行异步任务。

  • 创建 HandlerThread 后必须先调用HandlerThread.start()方法,Thread 会先调用 run 方法,创建 Looper 对象。

  • HandlerThread 优点是异步不会堵塞,减少对性能的消耗

  • HandlerThread 缺点是不能同时继续进行多任务处理,需要等待进行处理,处理效率较低

  • HandlerThread 与线程池不同,HandlerThread 是一个串行队列,背后只有一个线程。

IntentService

  • 它本质是一种特殊的 Service,继承自 Service 并且本身就是一个抽象类

  • 它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止

  • 它拥有较高的优先级,不易被系统杀死(继承自 Service 的缘故),因此比较适合执行一些高优先级的异步任务 它内部通过 HandlerThread 和 Handler 实现异步操作

  • 创建 IntentService 时,只需实现 onHandleIntent 和构造方法,onHandleIntent 为异步方法,可以执行耗时操作

  • 即使我们多次启动 IntentService,但 IntentService 的实例只有一个,这跟传统的 Service 是一样的,最终 IntentService 会去调用 onHandleIntent 执行异步任务。

  • 当任务完成后,IntentService 会自动停止,而不需要手动调用stopSelf()。另外,可以多次启动 IntentService,每个耗时操作都会以工作队列的方式在IntentServiceonHandlerIntent()回调方法中执行,并且每次只会执行一个工作线程。

AsyncTask

1、AsyncTask 是什么


AsyncTask 是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并主线程中更新 UI,通过 AsyncTask 可以更加方便执行后台任务以及在主线程中访问 UI,但是 AsyncTask 并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。


2、AsyncTask 使用方法


三个参数


  • Params:表示后台任务执行时的参数类型,该参数会传给 AysncTask 的 doInBackground()方法

  • Progress:表示后台任务的执行进度的参数类型,该参数会作为 onProgressUpdate()方法的参数

  • Result:表示后台任务的返回结果的参数类型,该参数会作为 onPostExecute()方法的参数


五个方法


  • onPreExecute():异步任务开启之前回调,在主线程中执行

  • doInBackground():执行异步任务,在线程池中执行

  • onProgressUpdate():当 doInBackground 中调用

  • publishProgress 时回调,在主线程中执行

  • onPostExecute():在异步任务执行之后回调,在主线程中执行

  • onCancelled():在异步任务被取消时回调


3、AsyncTask 引起的内存泄漏


原因:非静态内部类持有外部类的匿名引用,导致 Activity 无法释放


解决: AsyncTask 内部持有外部 Activity 的弱引用 AsyncTask 改为静态内部类 Activity 的 onDestory()中调用 AsyncTask.cancel()


4.结果丢失


屏幕旋转或 Activity 在后台被系统杀掉等情况会导致 Activity 的重新创建,之前运行的 AsyncTask 会持有一个之前 Activity 的引用,这个引用已经无效,这时调用 onPostExecute()再去更新界面将不再生效。


5、AsyncTask 并行 or 串行


AsyncTask 在 Android 2.3 之前默认采用并行执行任务,AsyncTask 在 Android 2.3 之后默认采用串行执行任务 如果需要在 Android 2.3 之后采用并行执行任务,可以调用 AsyncTask 的 executeOnExecutor();


6.AsyncTask 内部的线程池


private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;


sDefaultExecutorSerialExecutor的一个实例,而且它是个静态变量。也就是说,一个进程里面所有 AsyncTask 对象都共享同一个SerialExecutor对象。

一些面试心经

一般情况下第一轮都是基础面试,需要扎实的基础


  • 最常用的 Android 基础知识

  • Java 基础知识

  • 了解一些 常用东西的原理,例如:handler, thread 等

  • 项目中的技术点


第二轮的时候需要了解更深层次的东西


  • Android 事件分发机制原理

  • Android 绘图机制原理

  • WindowManager 的相关知识

  • 进程间传输方式

  • Java 内存管理机制

  • 一些常用的 list,map 原理,以及子类之间的差别


能进入第三轮基本没什么问题,但是要注意以下问题


该轮一般是 老大或者部门负责人,问的问题一般都看 深度与广度 当问及薪水的时候,要说一个合适的,小公司随意,大公司一定要慎重,当心里没底的时候,可以告诉对方,让对方给一个合理的薪资。一般都是在原工资基础之上增长,听猎头说一般涨幅都在 15%-30%,超 NB 的可以要 30%及以上,如果感觉自己还不错的,挺厉害的,建议最高 20%,一般人就定在 15% 左右最靠谱。公司内部一般有一套机制,根据公司情况而定。


我们的面试原则就是拿到合理薪资,得到 offer 个人发展情况,这个问题很难回答,如果和公司方向不符合,极有可能和公司无缘。建议多试探性的问问公司缺少什么,你能否给予公司对应的东西。当然对于有自我追求的人,那可以放心大胆的提。我的方向就是架构师,哈哈哈,挺极端的,别学我哦。我感觉选择都是双向的,因此我知道自己需要的是什么。

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
三年经验Android开发面经总结