美文网首页Andorid的好东西
Android面试题(下)

Android面试题(下)

作者: kjy_112233 | 来源:发表于2018-11-02 15:48 被阅读771次

Window

Activity、View、Window三者之间的关系?
  • 在Activity启动过程其中的attach()方法中初始化了PhoneWindow,而PhoneWindow是Window的唯一实现类,然后Activity通过setContentView将View设置到了PhoneWindow上,而View通过WindowManager的addView()、removeView()、updateViewLayout()对View进行管理。
Window的内部机制
  • ViewManager接口中定义三个对Window操作方法:添加、更新和删除。
  • WindowManager也是一个接口,它继承了ViewManager接口
  • WindowManager的具体实现类是WindowManagerImpl,并没有直接实现Window的三大操作,而是交给了WindowManagerGlobal。
  • WindowManagerGlobal以单例模式向外提供自己的实例,因此通过WindowManagerGlobal的addView()、updateViewLayout()、removeView()实现WindowManager对Window的添加、删除和修改。
  • Windows的三大操作最终都会通过一个IPC过程移交给WindowManagerService。
  • Window和View通过ViewRootImpl来联系,ViewRootImpl可控制View的测量、布局和重绘。
Window有哪几种类型?
  • 应用Window:对应一个Activity。
  • 子Window:不能单独存在,需附属特定的父Window。如Dialog。
  • 系统Window: 需申明权限才能创建。如Toast。
Activity创建和Dialog创建过程的异同?
  • 相同点
  • 创建WindowDialog。和Activity类似,同样是通过PolicyManager.makeNewWindow()来实现。
  • 初始化DecorView并将Dialog的视图添加到DecorView中去。和Activity类似,同样是通过Window.setContentView() 来实现。
  • 将DecorView添加到Window中显示。和Activity一样,都是在自身要出现在前台时才会将添加Window。
  • 不同点
  • Dialog.show()方法:完成DecorView的显示。
  • WindowManager.remoteViewImmediate()方法:当Dialog被dismiss时移除DecorView。

Hander

谈谈消息机制Hander?作用?有哪些要素?流程是怎样的?
  • 作用:跨线程通信。
  • 使用场景:当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。
  • Message:需要被传递的消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,最终由Handler处理。
  • MessageQueue:用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。
  • Handler:负责发送及处理Message消息。
  • Looper:通过Looper.loop()不断地从MessageQueue中抽取Message,按分发机制将消息分发给目标处理者。
  • Handler.sendMessage()发送消息时,会通过MessageQueue.enqueueMessage()向MessageQueue中添加一条消息;通过Looper.loop()开启循环后,不断轮询调用MessageQueue.next();调用目标Handler.dispatchMessage()去传递消息,目标Handler收到消息后调用Handler.handlerMessage()处理消息。
为什么系统不建议在子线程访问UI?
  • UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态。而不对UI控件的访问加上锁机制的原因有:上锁会让UI控件变得复杂和低效;上锁后会阻塞某些进程的执行
一个Thread可以有几个Looper?几个Handler?
  • 一个Thread只能有一个Looper,可以有多个Handler;
  • Looper有一个MessageQueue,可以处理来自多个Handler的Message;
  • MessageQueue有一组待处理的Message,这些Message可来自不同的Handler;
  • Message中记录了负责发送和处理消息的Handler;
  • Handler中有Looper和MessageQueue;
如何将一个Thread线程变成Looper线程?Looper线程有哪些特点?
  • 通过Looper.prepare()可将一个Thread线程转换成Looper线程。Looper线程和普通Thread不同,它通过MessageQueue来存放消息和事件、Looper.loop()进行消息轮询。
可以在子线程直接new一个Handler吗?那该怎么做?
  • 不同于主线程直接new一个Handler,由于子线程的Looper需要手动去创建,在创建Handler时需要多一些方法: Looper.prepare();Looper.loop();
Message可以如何创建?哪种效果更好,为什么?
  • Message msg = new Message();
  • Message msg = Message.obtain();
  • Message msg = handler.obtainMessage();
  • 后两种方法都是从整个Messge池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更鼓励这种方式创建Message
这里的ThreadLocal有什么作用?

ThreadLocal类可实现线程本地存储的功能,把共享数据的可见范围限制在同一个线程之内,无须同步就能保证线程之间不出现数据争用的问题,这里可理解为ThreadLocal帮助Handler找到本线程的Looper。

主线程中Looper的轮询死循环为何没有阻塞主线程?
  • Android是依靠事件驱动的,通过Loop.loop()不断进行消息循环,可以说Activity的生命周期都是运行在 Looper.loop()的控制之下,一旦退出消息循环,应用也就退出了。而所谓的导致ANR多是因为某个事件在主线程中处理时间太耗时,因此只能说是对某个消息的处理阻塞了Looper.loop()。
使用Hanlder的postDealy()后消息队列会发生什么变化?

post delay的Message并不是先等待一定时间再放入到MessageQueue中,而是直接进入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,如果触发时间更近则放入队头,保证队头的时间最小、队尾的时间最大。此时,如果队头的Message正是被delay的,则将当前线程堵塞一段时间,直到等待足够时间再唤醒执行该Message,否则唤醒后直接执行。

线程

Android中还了解哪些方便线程切换的类?
  • AsyncTask:一种轻量级的异步任务类。底层封装了线程池和Handler,便于执行后台任务以及在子线程中进行UI操作。
  • HandlerThread:一种具有消息循环的线程,其内部可使用Handler。
  • IntentService:是一种异步、会自动停止的服务,内部采用HandlerThread。
AsyncTask相比Handler有什么优点?不足呢?

Handler的缺点:代码相对臃肿;多任务同时执行时不易精确控制线程。
AsyncTask的优点:创建异步任务更简单,直接继承AsyncTask便可实现后台异步任务的执行和进度的回调更新UI,而无需编写任务线程和Handler实例就能完成相同的任务。

使用AsyncTask需要注意什么?
  • 不要直接调用onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()和onCancelled()方法
  • 开始和结束异步任务的方法必须在主线程调用,一个异步对象只能调用一次execute()方法,
AsyncTask中的五个核心方法
  • onPreExecute():运行在主线程,在异步任务执行之前被调用。可用于进行一些界面上的初始化操作。
  • doInBackground():运行在子线程,用于处理所有耗时任务,更新UI需调用 publishProgress()方法。任务一旦完成就通过return语句将任务的执行结果返回。
  • onProgressUpdate():运行在主线程,在后台任务中调用publishProgress()之后该方法会被调用,可利用方法中携带的参数如Progress来对UI进行相应地更新。
  • onPostExecute():运行在主线程,在异步任务执行完毕并通过return语句返回时被调用,可利用方法中返回的数据来进行一些UI操作。
  • onCancelled():运行在主线程,当异步任务被取消时调用,用于界面取消的更新。
AsyncTask中使用的线程池大小?
  • SerialExecutor:用于任务的排队,默认是串行的线程池,在3.0以前核心线程数为5、线程池大小为128,而3.0以后变为同一时间只能处理一个任务
  • THREAD_POOL_EXECUTOR:用于真正执行任务。
HandlerThread有什么特点?
  • HandlerThread是一个线程类,它继承自Thread。与普通Thread不同,HandlerThread具有消息循环的效果,这是因为它内部HandlerThread.run()方法中有Looper,能通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环。
快速实现子线程使用Handler
  • 实例化一个HandlerThread对象,参数是该线程名称;通过handlerThread.start()启动线程;实例化一个Handler并传入handlerThread中looper对象,使得与HandlerThread绑定;利用handler即可执行异步任务;当不需要HandlerThread时,通过quit()或quitSafely()方法来终止线程的执行。
IntentService的工作原理?
  • 在IntentService.onCreate()方法中实例化一个HandlerThread,利用其内部的Looper会实例化一个ServiceHandler对象;
  • 任务请求的Intent会被封装到Message并通过ServiceHandler发送给Looper的MessageQueue,最终在HandlerThread中执行;
  • 在ServiceHandler.handleMessage()中会调用IntentService.onHandleIntent()和stopSelf(),可在onHandleIntent方法中处理后台任务的逻辑,stopSelf方法终止服务。
IntentService的特点?
  • 相比于线程:由于是服务,优先级比线程高,更不容易被系统杀死。因此较适合执行一些高优先级的后台任务。
  • 相比于普通Service:可自动创建子线程来执行任务,且任务执行完毕后自动退出。
为何不用bindService方式创建IntentService?
  • 当有Intent任务请求时会把Intent封装到Message,然后ServiceHandler会把消息发送出,而发送消息是在onStartCommand()完成的,只能通过startService()才可走该生命周期方法,因此不能通过bindService创建IntentService。
线程池的好处、原理、类型?

线程池的优点:

  • 重用线程池中的线程,避免线程的创建和销毁带来的性能消耗;
  • 有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致阻塞现象;
  • 进行线程管理,提供定时/循环间隔执行等功能

线程池的原理

  • 通过ThreadPoolExecutor并通过一系列参数来配置各种各样的线程池,具体参数:
    • corePoolSize核心线程数:一般会在线程中一直存活
    • maximumPoolSize最大线程数:当活动线程数达到这个数值后,后续的任务将会被阻塞
    • keepAliveTime非核心线程超时时间:超过这个时长,闲置的非核心线程就会被回收
    • unit:用于指定keepAliveTime参数的时间单位
    • workQueue任务队列:通过线程池的execute()方法提交的Runnable对象会存储在这个参数中。
    • threadFactory:线程工厂,可创建新线程
    • handler:在线程池无法执行新任务时进行调度

线程池的类型

  • FixThreadPool:线程数量固定的线程池,所有线程都是核心线程,当线程空闲时不会被回收,能快速响应外界请求。
  • CachedThreadPool:线程数量不定的线程池,只有非核心线程,空闲线程有超时机制,超时回收,适合于执行大量的耗时较少的任务
  • ScheduledThreadPool:核心线程数量固定,非核心线程数量不定,适合于执行定时任务和固定周期的任务。
  • SingleThreadExecutor:只有一个核心线程,可确保所有的任务都在同一个线程中按顺序执行,无需处理线程同步问题。
ThreadPoolExecutor的工作策略?
  • 若程池中的线程数量未达到核心线程数,则会直接启动一个核心线程执行任务。
  • 若线程池中的线程数量已达到或者超过核心线程数量,则任务会被插入到任务列表等待执行。 若任务无法插入到任务列表中,往往由于任务列表已满,此时如果线程数量未达到线程池最大线程数,则会启动一个非核心线程执行任务;线程数量已达到线程池规定的最大值,则拒绝执行此任务。
什么是ANR?什么情况会出现ANR?如何避免?在不看代码的情况下如何快速定位出现ANR问题所在?
  • ANR(Application Not Responding,应用无响应):当操作在一段时间内系统无法处理时,会在系统层面弹出ANR对话框。
  • 产生ANR可能是因为5s内无响应用户输入事件、10s内未结束BroadcastReceiver、20s内未结束Service
  • 想要避免ANR就不要在主线程做耗时操作,而是通过开子线程操作。

IPC(跨进程通信)

Android中进程和线程的关系?
  • 进程是操作系统分配和管理资源的单位,线程是CPU调度和管理的单位,是CPU调度的最小单元
  • 进程拥有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程间共享地址空间,线程有自己的堆栈和局部变量,一个线程崩溃会导致整个进程崩溃掉。
  • 一个进程可包含多个线程,即一个应用程序上可以同时执行多个任务。
为何需要进行IPC?多进程通信可能会出现什么问题?
  • 所有运行在不同进程的四大组件,只要它们之间需要通过内存共享数据,都会共享失败。由于Android为每个应用分配了独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间
  • 静态变量和单例模式失效:由独立的虚拟机造成。
  • 线程同步机制失效:由独立的虚拟机造成
  • SharedPreference的不可靠下降: SharedPreferences不支持两个进程同时进行读写操作,即不支持并发读写,有一定几率导致数据丢失。
  • Application多次创建:Android系统会为新的进程分配独立虚拟机,相当于系统又把这个应用重新启动了一次
什么是序列化?Serializable接口和Parcelable接口的区别?为何推荐使用后者?
  • 序列化表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。
  • Serializable:Java序列化接口,将一个对象转化成可存储或可传输的状态,操作简单、效率低、开销大,ObjectOutputStream和ObjectInputStream过程都需要大量的I/O操作。适合将对象序列化到存储设备或将对象序列化后通过网络设备传输。
  • Parcelable:Android序列化接口,将一个对象进行分解,且分解后的每一个部分都是传递可支持的类型。操作比较麻烦、效率高。主要用在内存的序列化。
Android中为何新增Binder来作为主要的IPC方式?
  • 传输效率高、可操作性强:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。
  • 实现C/S架构方便:Linux的众IPC方式除了Socket以外都不是基于C/S架构,而Socket主要用于网络间的通信且传输效率较低。Binder基于C/S 架构 ,Server端与Client端相对独立,稳定性较好。
  • 安全性高:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID且在Binder通信时会根据UID/PID进行有效性检测。
Binder框架中ServiceManager的作用?
  • 在Binder框架定义了四个角色:Server,Client,ServiceManager和Binder驱动。其中Server、Client、ServiceManager运行于用户空间,Binder驱动运行于内核空间。
  • ServiceManager:服务的管理者,将Binder名字转换为Client中对该Binder的引用,使得Client可以通过Binder名字获得Server中Binder实体的引用。
  • Server&Client:服务器&客户端。在Binder驱动和ServiceManager提供的基础设施上,进行Client-Server之间的通信。
Android中有哪些基于Binder的IPC方式?简单对比下?
  • Bundle:Bundle实现了Parcelable接口,方便在不同的进程中传输数据。支持在activity、service、receiver之间通过intent.putExtra()传递Bundle数据。Bundle内部是通过ArrayMap来存取数据。Bundle不支持的数据类型无法在进程中被传递
  • Messager:底层实现是AIDL,即对AIDL进行了封装,更便于进行进程间通信。支持一对多串行通信,支持实时通信。
  • 文件共享:两个进程通过读/写同一个文件来交换数据。比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。
  • ContentProvider:专门用来进行不同应用间数据共享的方式。
  • AIDL:支持一对多并发通信,支持实时通信。
  • Socket:不仅可跨进程,还可以跨设备通信。网络数据交换
是否了解AIDL?原理是什么?如何优化多模块都使用AIDL的情况?
  • 如果在一个进程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数,AIDL会生成一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法。
  • 当有多个业务模块都需要AIDL来进行IPC,此时需要为每个模块创建特定的aidl文件,那么相应的Service就会很多。必然会出现系统资源耗费严重、应用过度重量级的问题。解决办法是建立Binder连接池,即将每个业务模块的Binder请求统一转发到一个远程Service中去执行,从而避免重复创建Service。
  • 每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的Binder对象。服务端只需要一个Service,服务器提供一个queryBinder接口,它会根据业务模块的特征来返回相应的Binder对像,不同的业务模块拿到所需的Binder对象后就可进行远程方法的调用了

Bitmap

加载图片的时候需要注意什么?
  • 直接加载大容量的高清Bitmap很容易出现显示不完整、内存溢出OOM的问题,所以最好按一定的采样率将图片缩小后再加载进来
  • 为减少流量消耗,可对图片采用内存缓存策略,又为了避免图片占用过多内存导致内存溢出,最好以软引用方式持有图片
  • 如果还需要网上下载图片,注意要开子线程去做下载的耗时操作
LRU算法的原理?
  • LruCache(内存缓存):LruCache类是一个线程安全的泛型类:内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,并提供get和put方法来完成缓存的获取和添加操作,当缓存满时会移除较早使用的缓存对象,再添加新的缓存对象。
  • DiskLruCache(磁盘缓存): 通过将缓存对象写入文件系统从而实现缓存效果。
项目中如何做性能优化的?
  • 布局优化:布局嵌套过深;使用合适的布局三种常见的ViewGroup的绘制速度:FrameLayout > LinerLayout > RelativeLayout;使用include标签可以指定插入一段布局文件到当前布局。这样的话既提高了布局复用,也减少了我们的代码书写,<merge>标签可以和<include>的标签一起使用从而减少布局层级;ViewStub延时加载;移除Activity默认背景,在不需要Activity的默认背景减少Activity启动时的渲染时间,提升启动效率。
  • 线程优化:使用AsyncTask或者线程池对线程进行管理,可以提升APP的性能。推荐使用Rxjava来实现异步操作,既方便又优雅。
  • 内存泄漏优化:内存泄露会导致APP占用内存过高,影响效率,严重的话会导致OOM。因此如果项目存在内存泄露的话要优先解决。查找内存泄露可以用LeakCanary等工具。
  • 绘制优化:View过度绘制;onDraw中不要创建新的局部对象;onDraw方法中不要做耗时的任务,也不能执行成千上万次的循环操作,大量的循环仍然十分抢占CPU的时间片,这会造成View的绘制过程不流畅。
  • 响应速度优化:避免在主线程中做耗时操作
  • 列表优化:列表控件优化convertView的复用,ViewHolder的使用避免重复遍历节点
  • Bitmap优化: 对加载图片进行压缩,避免加载图片多大导致OOM出现
内存泄漏和内存溢出的区别
  • 内存泄漏(Memory Leak)是指程序在申请内存后,无法释放已申请的内存空间。是造成应用程序OOM的主要原因之一。
  • 内存溢出(out of memory)是指程序在申请内存时,没有足够的内存空间供其使用。
内存泄漏是什么?为什么会发生?常见哪些内存泄漏的例子?都是怎么解决的?
  • 内存泄漏(Memory Leak)是指程序在申请内存后,无法释放已申请的内存空间。
  • 发生内存泄漏是由于长周期对象持有对短周期对象的引用,使得短周期对象不能被及时回收。
    动画:无限循环动画,导致动画一直播放。
    解决方法:当播放的Activity退出,在onDestroy中停止动画
    静态成员View:静态成员生命周期与应用的生命周期一样,当前View持有Activity的引用,当前Activity销毁,而他的引用一直存在,导致当前Activity无法被回收。
  • Handler导致的内存泄漏:Message持有对Handler的引用,而非静态内部类的Handler又隐式持有对外部类Activity的引用,使得引用关系会保持至消息得到处理,从而阻止了Activity的回收。
    解决办法:使用静态内部类+WeakReference弱引用;当外部类结束生命周期时清空消息队列。
  • 单例模式导致的内存泄漏
  • 匿名内部类导致的内存泄漏:匿名内部类隐式持有对所在Activity的引用。
    解决办法:静态内部类或独立出来;内部采用弱引用
  • 资源未关闭导致的内存泄漏:未及时注销资源导致内存泄漏,如BraodcastReceiver、File、Cursor、Stream、Bitmap等。
    解决办法:在Activity销毁的时候要及时关闭或者注销。
什么情况会导致内存溢出?

内存泄漏是导致内存溢出的主要原因;直接加载大图片也易造成内存溢出

相关文章

  • Android面试题

    Android面试必备: (一)、Java面试题 (二)、Android面试题 (三)、Android高级面试题

  • 面试题

    关注的面试题文集 Android面试题整理 Android工程师面试题大全 Android 面试题总结之Andro...

  • Android面试总结二

    参考文章 40个Android面试题Java面试题集Android名企面试题及知识点整理Android面试题收集较...

  • Android 面试题(重点2)

    掘金官网Android面试题 Android 动画 Android 动画Android面试题 算法和数据结构 设计...

  • Android主流框架面试准备

    MVP Android MVP架构搭建史上最全的Android MVP模式架构面试题集锦Android面试题-架构...

  • Android面试题汇总

    面试题汇总 Android复习资料——Android知识点汇总(一) 史上最全的Android面试题集锦 ForA...

  • Android 面试题汇总

    一、Android面试题 Android面试题包括Android基础,还有一些源码级别的、原理这些等。所以想去大公...

  • Android面试

    [toc] Android面试题 Android面试题除了Android基础之外,更多的问的是一些源码级别的、原理...

  • Android面试题

    二、Android面试题 Android面试题包括Android基础,还有一些源码级别的、原理这些等。所以想去大公...

  • 2017 Android 面试题分享整理

    第174期:2017 Android 面试题分享整理 深度讨论 2017 Android 面试题分享整理 (欢迎评...

网友评论

    本文标题:Android面试题(下)

    本文链接:https://www.haomeiwen.com/subject/qwebtqtx.html