Android篇:2019初中级Android开发社招面试解答(中)

金三银四,冲击大厂,你值得拥有的一份2019初中级移动端社招面试总结+解答html

你当前所处:Android篇:2019初中级Android开发社招面试解答(中)前端

Android篇:2019初中级Android开发社招面试解答(下)java

Android篇:2019初中级Android开发社招面试解答(上)android

注:由于实际开发与参考答案会有所不一样,再者怕误导你们,因此这些面试题答案仍是本身去理解!面试官会针对简历中提到的知识点由浅入深提问,因此不要背答案,多理解。git

Handler

一、谈谈消息机制Handler做用 ?有哪些要素 ?流程是怎样的 ?

  • 参考回答:
    • 负责跨线程通讯,这是由于在主线程不能作耗时操做,而子线程不能更新UI,因此当子线程中进行耗时操做后须要更新UI时,经过Handler将有关UI的操做切换到主线程中执行。
    • 具体分为四大要素
      • Message(消息):须要被传递的消息,消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息。
      • MessageQueue(消息队列):负责消息的存储与管理,负责管理由 Handler发送过来的Message。读取会自动删除消息,单链表维护,插入和删除上有优点。在其next()方法中会无限循环,不断判断是否有消息,有就返回这条消息并移除。
      • Handler(消息处理器):负责Message的发送及处理。主要向消息池发送各类消息事件(Handler.sendMessage())和处理相应消息事件(Handler.handleMessage()),按照先进先出执行,内部使用的是单链表的结构。
      • Looper(消息池):负责关联线程以及消息的分发,在该线程下从 MessageQueue获取 Message,分发给Handler,Looper建立的时候会建立一个 MessageQueue,调用loop()方法的时候消息循环开始,其中会不断调用messageQueue的next()方法,当有消息就处理,不然阻塞在messageQueue的next()方法中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,而后loop()方法也就跟着退出。
    • 具体流程以下
      • 在主线程建立的时候会建立一个Looper,同时也会在在Looper内部建立一个消息队列。而在创键Handler的时候取出当前线程的Looper,并经过该Looper对象得到消息队列,而后Handler在子线程中经过MessageQueue.enqueueMessage在消息队列中添加一条Message。
      • 经过Looper.loop() 开启消息循环不断轮询调用 MessageQueue.next(),取得对应的Message而且经过Handler.dispatchMessage传递给Handler,最终调用Handler.handlerMessage处理消息。

二、一个线程可否建立多个Handler,Handler跟Looper之间的对应关系 ?

  • 参考回答:
    • 一个Thread只能有一个Looper,一个MessageQueen,能够有多个Handler
    • 以一个线程为基准,他们的数量级关系是: Thread(1) : Looper(1) : MessageQueue(1) : Handler(N)

三、软引用跟弱引用的区别

  • 参考回答:
    • 软引用(SoftReference):若是一个对象只具备软引用,则内存空间充足时,垃圾回收器就不会回收它;若是内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就能够一直被程序使用。
    • 弱引用(WeakReference):若是一个对象只具备弱引用,那么在垃圾回收器线程扫描的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。
    • 二者之间根本区别在于:只具备弱引用的对象拥有更短暂的生命周期,可能随时被回收。而只具备软引用的对象只有当内存不够的时候才被回收,在内存足够的时候,一般不被回收。
    • 推荐文章: Java中的四种引用类型:强引用、软引用、弱引用和虚引用

四、Handler 引发的内存泄露缘由以及最佳解决方案

  • 参考回答:
    • 泄露缘由:
      • Handler 容许咱们发送延时消息,若是在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是由于 Message 会持有 Handler,而又由于 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就致使 Activity 泄露。
    • 解决方案:
      • 将 Handler 定义成静态的内部类,在内部持有Activity的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除全部消息。

五、为何系统不建议在子线程访问UI?

  • 参考回答:
    • Android的UI控件不是线程安全的,若是在多线程中并发访问可能会致使UI控件处于不可预期的状态
    • 这时你可能会问为什么系统不对UI控件的访问加上锁机制呢?由于
      • 加锁机制会让UI访问逻辑变的复杂
      • 加锁机制会下降UI的访问效率,由于加锁会阻塞某些线程的执行

六、Looper死循环为何不会致使应用卡死?

  • 参考回答:
    • 主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引发主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。
    • 形成ANR的不是主线程阻塞,而是主线程的Looper消息处理过程发生了任务阻塞,没法响应手势操做,不能及时刷新UI。
    • 阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,可是只要保证有消息的时候可以马上处理,程序是不会无响应的。

七、使用Handler的postDealy后消息队列会有什么变化?

  • 参考回答:
    • 若是队列中只有这个消息,那么消息不会被发送,而是计算到时唤醒的时间,先将Looper阻塞,到时间就唤醒它。但若是此时要加入新消息,该消息队列的对头跟delay时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大

八、能够在子线程直接new一个Handler吗?怎么作?

  • 参考回答:
    • 不能够,由于在主线程中,Activity内部包含一个Looper对象,它会自动管理Looper,处理子线程中发送过来的消息。而对于子线程而言,没有任何对象帮助咱们维护Looper对象,因此须要咱们本身手动维护。因此要在子线程开启Handler要先建立Looper,并开启Looper循环
  • 推荐文章:Android异步消息处理机制彻底解析,带你从源码的角度完全理解

九、Message能够如何建立?哪一种效果更好,为何?

  • 参考回答:能够经过三种方法建立:
    • 直接生成实例Message m = new Message
    • 经过Message m = Message.obtain
    • 经过Message m = mHandler.obtainMessage()
  • 后二者效果更好,由于Android默认的消息池中消息数量是10,然后二者是直接在消息池中取出一个Message实例,这样作就能够避免多生成Message实例。

线程

一、线程池的好处? 四种线程池的使用场景,线程池的几个参数的理解?

  • 参考回答:
    • 使用线程池的好处是减小在建立和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。若是不使用线程池,有可能形成系统建立大量同类线程而致使消耗完内存或则“过分切换”的问题,概括总结就是
      • 重用存在的线程,减小对象建立、消亡的开销,性能佳。
      • 可有效控制最大并发线程数,提升系统资源的使用率,同时避免过多资源竞争,避免堵塞。
      • 提供定时执行、按期执行、单线程、并发数控制等功能。
    • Android中的线程池都是直接或间接经过配置ThreadPoolExecutor来实现不一样特性的线程池.Android中最多见的类具备不一样特性的线程池分别为:
      • newCachedThreadPool:只有非核心线程,最大线程数很是大,全部线程都活动时会为新任务建立新线程,不然会利用空闲线程 ( 60s空闲时间,过了就会被回收,因此线程池中有0个线程的可能 )来处理任务.
        • 优势:任何任务都会被当即执行(任务队列SynchronousQuue至关于一个空集合);比较适合执行大量的耗时较少的任务.
      • newFixedThreadPool:只有核心线程,而且数量固定的,全部线程都活动时,由于队列没有限制大小,新任务会等待执行,当线程池空闲时不会释放工做线程,还会占用必定的系统资源。
        • 优势:更快的响应外界请求
      • newScheduledThreadPool:核心线程数固定,非核心线程(闲着没活干会被当即回收数)没有限制.
        • 优势:执行定时任务以及有固定周期的重复任务
      • newSingleThreadExecutor:只有一个核心线程,确保全部的任务都在同一线程中按序完成
        • 优势:不须要处理线程同步的问题
    • 经过源码能够了解到上面的四种线程池实际上仍是利用 ThreadPoolExecutor 类实现的
  • 推荐文章:java线程池解析和四种线程池的使用

二、Android中还了解哪些方便线程切换的类?

  • 参考回答:
    • AsyncTask:底层封装了线程池和Handler,便于执行后台任务以及在子线程中进行UI操做。
    • HandlerThread:一种具备消息循环的线程,其内部可以使用Handler。
    • IntentService:是一种异步、会自动中止的服务,内部采用HandlerThread。

三、讲讲AsyncTask的原理

  • 参考回答:
    • AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正地执行任务,InternalHandler用于将执行环境从线程池切换到主线程。
    • sHandler是一个静态的Handler对象,为了可以将执行环境切换到主线程,这就要求sHandler这个对象必须在主线程建立。因为静态成员会在加载类的时候进行初始化,所以这就变相要求AsyncTask的类必须在主线程中加载,不然同一个进程中的AsyncTask都将没法正常工做。

四、IntentService有什么用 ?

  • 参考回答:
    • IntentService可用于执行后台耗时的任务,当任务执行完成后会自动中止,同时因为IntentService是服务的缘由,不一样于普通Service,IntentService可自动建立子线程来执行任务,这致使它的优先级比单纯的线程要高,不容易被系统杀死,因此IntentService比较适合执行一些高优先级的后台任务。

五、直接在Activity中建立一个thread跟在service中建立一个thread之间的区别?

  • 参考回答:
    • 在Activity中被建立:该Thread的就是为这个Activity服务的,完成这个特定的Activity交代的任务,主动通知该Activity一些消息和事件,Activity销毁后,该Thread也没有存活的意义了。
    • 在Service中被建立:这是保证最长生命周期的Thread的惟一方式,只要整个Service不退出,Thread就能够一直在后台执行,通常在Service的onCreate()中建立,在onDestroy()中销毁。因此,在Service中建立的Thread,适合长期执行一些独立于APP的后台任务,比较常见的就是:在Service中保持与服务器端的长链接。

六、ThreadPoolExecutor的工做策略 ?

  • 参考回答:ThreadPoolExecutor执行任务时会遵循以下规则
    • 若是线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
    • 若是线程池中的线程数量已经达到或则超过核心线程的数量,那么任务会被插入任务队列中排队等待执行。
    • 若是在第2点没法将任务插入到任务队列中,这每每是因为任务队列已满,这个时候若是在线程数量未达到线程池规定的最大值,那么会马上启动一个非核心线程来执行任务。
    • 若是第3点中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。

七、Handler、Thread和HandlerThread的差异?

  • 参考回答:
    • Handler:在android中负责发送和处理消息,经过它能够实现其余支线线程与主线程之间的消息通信。
    • Thread:Java进程中执行运算的最小单位,亦即执行处理机调度的基本单位。某一进程中一路单独运行的程序。
    • HandlerThread:一个继承自Thread的类HandlerThread,Android中没有对Java中的Thread进行任何封装,而是提供了一个继承自Thread的类HandlerThread类,这个类对Java的Thread作了不少便利的封装。HandlerThread继承于Thread,因此它本质就是个Thread。与普通Thread的差异就在于,它在内部直接实现了Looper的实现,这是Handler消息机制必不可少的。有了本身的looper,可让咱们在本身的线程中分发和处理消息。若是不用HandlerThread的话,须要手动去调用Looper.prepare()和Looper.loop()这些方法。

八、ThreadLocal的原理

  • 参考回答:
    • ThreadLocal是一个关于建立线程局部变量的类。使用场景以下所示:
      • 实现单个线程单例以及单个线程上下文信息存储,好比交易id等。
      • 实现线程安全,非线程安全的对象使用ThreadLocal以后就会变得线程安全,由于每一个线程都会有一个对应的实例。 承载一些线程相关的数据,避免在方法中来回传递参数。
    • 当须要使用多线程时,有个变量恰巧不须要共享,此时就没必要使用synchronized这么麻烦的关键字来锁住,每一个线程都至关于在堆内存中开辟一个空间,线程中带有对共享变量的缓冲区,经过缓冲区将堆内存中的共享变量进行读取和操做,ThreadLocal至关于线程内的内存,一个局部变量。每次能够对线程自身的数据读取和操做,并不须要经过缓冲区与 主内存中的变量进行交互。并不会像synchronized那样修改主内存的数据,再将主内存的数据复制到线程内的工做内存。ThreadLocal可让线程独占资源,存储于线程内部,避免线程堵塞形成CPU吞吐降低。
    • 在每一个Thread中包含一个ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的对象,value是独享数据。

九、多线程是否必定会高效(优缺点)

  • 参考回答:
    • 多线程的优势:
      • 方便高效的内存共享 - 多进程下内存共享比较不便,且会抵消掉多进程编程的好处
      • 较轻的上下文切换开销 - 不用切换地址空间,不用更改CR3寄存器,不用清空TLB
      • 线程上的任务执行完后自动销毁
    • 多线程的缺点:
      • 开启线程须要占用必定的内存空间(默认状况下,每个线程都占512KB)
      • 若是开启大量的线程,会占用大量的内存空间,下降程序的性能
      • 线程越多,cpu在调用线程上的开销就越大
      • 程序设计更加复杂,好比线程间的通讯、多线程的数据共享
    • 综上得出,多线程不必定能提升效率,在内存空间紧张的状况下反而是一种负担,所以在平常开发中,应尽可能
      • 不要频繁建立,销毁线程,使用线程池
      • 减小线程间同步和通讯(最为关键)
      • 避免须要频繁共享写的数据
      • 合理安排共享数据结构,避免伪共享(false sharing)
      • 使用非阻塞数据结构/算法
      • 避免可能产生可伸缩性问题的系统调用(好比mmap)
      • 避免产生大量缺页异常,尽可能使用Huge Page
      • 能够的话使用用户态轻量级线程代替内核线程

十、多线程中,让你作一个单例,你会怎么作

  • 参考回答:
    • 多线程中创建单例模式考虑的因素有不少,好比线程安全 -延迟加载-代码安全:如防止序列化攻击,防止反射攻击(防止反射进行私有方法调用) -性能因素
    • 实现方法有多种,饿汉,懒汉(线程安全,线程非安全),双重检查(DCL),内部类,以及枚举
    • 推荐文章:单例模式的总结

十一、除了notify还有什么方式能够唤醒线程

  • 参考回答:
    • 当一个拥有Object锁的线程调用 wait()方法时,就会使当前线程加入object.wait 等待队列中,而且释放当前占用的Object锁,这样其余线程就有机会获取这个Object锁,得到Object锁的线程调用notify()方法,就能在Object.wait 等待队列中随机唤醒一个线程(该唤醒是随机的与加入的顺序无关,优先级高的被唤醒几率会高)
    • 若是调用notifyAll()方法就唤醒所有的线程。注意:调用notify()方法后并不会当即释放object锁,会等待该线程执行完毕后释放Object锁。

十二、什么是ANR ? 什么状况会出现ANR ?如何避免 ? 在不看代码的状况下如何快速定位出现ANR问题所在 ?

  • 参考回答:
    • ANR(Application Not Responding,应用无响应):当操做在一段时间内系统没法处理时,会在系统层面会弹出ANR对话框
    • 产生ANR多是由于5s内无响应用户输入事件、10s内未结束BroadcastReceiver、20s内未结束Service
    • 想要避免ANR就不要在主线程作耗时操做,而是经过开子线程,方法好比继承Thread或实现Runnable接口、使用AsyncTask、IntentService、HandlerThread等
    • 推荐文章:如何快速分析定位ANR

Bitmap

一、Bitmap使用须要注意哪些问题 ?

  • 参考回答:
    • 要选择合适的图片规格(bitmap类型):一般咱们优化Bitmap时,当须要作性能优化或者防止OOM,咱们一般会使用RGB_565,由于ALPHA_8只有透明度,显示通常图片没有意义,Bitmap.Config.ARGB_4444显示图片不清楚,Bitmap.Config.ARGB_8888占用内存最多。:
      • ALPHA_8 每一个像素占用1byte内存
      • ARGB_4444 每一个像素占用2byte内存
      • ARGB_8888 每一个像素占用4byte内存(默认)
      • RGB_565 每一个像素占用2byte内存
    • 下降采样率:BitmapFactory.Options 参数inSampleSize的使用,先把options.inJustDecodeBounds设为true,只是去读取图片的大小,在拿到图片的大小以后和要显示的大小作比较经过calculateInSampleSize()函数计算inSampleSize的具体值,获得值以后。options.inJustDecodeBounds设为false读图片资源。
    • 复用内存:即经过软引用(内存不够的时候才会回收掉),复用内存块,不须要再从新给这个bitmap申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。
    • 使用recycle()方法及时回收内存
    • 压缩图片

二、Bitmap.recycle()会当即回收么?何时会回收?若是没有地方使用这个Bitmap,为何垃圾回收不会直接回收?

  • 参考回答:
    • 经过源码能够了解到,加载Bitmap到内存里之后,是包含两部份内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了
    • 可是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。因此须要调用recycle()方法来释放C部分的内存
    • bitmap.recycle()方法用于回收该Bitmap所占用的内存,接着将bitmap置空,最后使用System.gc()调用一下系统的垃圾回收器进行回收,调用System.gc()并不能保证当即开始进行回收过程,而只是为了加快回收的到来。

三、一张Bitmap所占内存以及内存占用的计算

  • 参考回答:
    • Bitamp 所占内存大小 = 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存字节大小
      • 注:这里inDensity表示目标图片的dpi(放在哪一个资源文件夹下),inTargetDensity表示目标屏幕的dpi,因此你能够发现inDensity和inTargetDensity会对Bitmap的宽高进行拉伸,进而改变Bitmap占用内存的大小。
    • 在Bitmap里有两个获取内存占用大小的方法。
      • getByteCount():API12 加入,表明存储 Bitmap 的像素须要的最少内存。
      • getAllocationByteCount():API19 加入,表明在内存中为 Bitmap 分配的内存大小,代替了 getByteCount() 方法。
      • 不复用 Bitmap 时,getByteCount() 和 getAllocationByteCount 返回的结果是同样的。在经过复用 Bitmap 来解码图片时,那么 getByteCount() 表示新解码图片占用内存的大 小,getAllocationByteCount() 表示被复用 Bitmap 真实占用的内存大小

四、Android中缓存更新策略 ?

  • 参考回答:
    • Android的缓存更新策略没有统一的标准,通常来讲,缓存策略主要包含缓存的添加、获取和删除这三类操做,但无论是内存缓存仍是存储设备缓存,它们的缓存容量是有限制的,所以删除一些旧缓存并添加新缓存,如何定义缓存的新旧这就是一种策略,不一样的策略就对应着不一样的缓存算法
    • 好比能够简单地根据文件的最后修改时间来定义缓存的新旧,当缓存满时就将最后修改时间较早的缓存移除,这就是一种缓存算法,但不算很完美

五、LRU的原理 ?

  • 参考回答:
    • 为减小流量消耗,可采用缓存策略。经常使用的缓存算法是LRU(Least Recently Used):当缓存满时, 会优先淘汰那些近期最少使用的缓存对象。主要是两种方式:
    • LruCache(内存缓存):LruCache类是一个线程安全的泛型类:内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,并提供get和put方法来完成缓存的获取和添加操做,当缓存满时会移除较早使用的缓存对象,再添加新的缓存对象。
    • DiskLruCache(磁盘缓存): 经过将缓存对象写入文件系统从而实现缓存效果

性能优化

一、图片的三级缓存中,图片加载到内存中,若是内存快爆了,会发生什么?怎么处理?

  • 参考回答:
    • 首先咱们要清楚图片的三级缓存是如何的
      若是内存足够时不回收。内存不够时就回收软引用对象

二、内存中若是加载一张500*500的png高清图片.应该是占用多少的内存?

  • 参考回答:
    • 不考虑屏幕比的话:占用内存=500 * 500 * 4 = 1000000B ≈ 0.95MB
    • 考虑屏幕比的的话:占用内存= 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存字节大小
    • inDensity表示目标图片的dpi(放在哪一个资源文件夹下),inTargetDensity表示目标屏幕的dpi

三、WebView的性能优化 ?

  • 参考回答:
    • 一个加载网页的过程当中,native、网络、后端处理、CPU都会参与,各自都有必要的工做和依赖关系;让他们相互并行处理而不是相互阻塞才可让网页加载更快:
      • WebView初始化慢,能够在初始化同时先请求数据,让后端和网络不要闲着。
      • 经常使用 JS 本地化及延迟加载,使用第三方浏览内核
      • 后端处理慢,可让服务器分trunk输出,在后端计算的同时前端也加载网络静态资源。
      • 脚本执行慢,就让脚本在最后运行,不阻塞页面解析。
      • 同时,合理的预加载、预缓存可让加载速度的瓶颈更小。
      • WebView初始化慢,就随时初始化好一个WebView待用。
      • DNS和连接慢,想办法复用客户端使用的域名和连接。
        WebView启动时间
    • 推荐文章:WebView性能、体验分析与优化

四、Bitmap如何处理大图,如一张30M的大图,如何预防OOM?

  • 参考回答:避免OOM的问题就须要对大图片的加载进行管理,主要经过缩放来减少图片的内存占用。
    • BitmapFactory提供的加载图片的四类方法(decodeFile、decodeResource、decodeStream、decodeByteArray)都支持BitmapFactory.Options参数,经过inSampleSize参数就能够很方便地对一个图片进行采样缩放
    • 好比一张10241024的高清图片来讲。那么它占有的内存为102410244,即4MB,若是inSampleSize为2,那么采样后的图片占用内存只有512512*4,即1MB(注意:根据最新的官方文档指出,inSampleSize的取值应该老是为2的指数,即一、二、四、8等等,若是外界输入不足为2的指数,系统也会默认选择最接近2的指数代替,好比2
    • 综合考虑。经过采样率便可有效加载图片,流程以下
      • 将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片
      • 从BitmapFactory.Options中取出图片的原始宽高信息,它们对应outWidth和outHeight参数
      • 根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize
      • 将BitmapFactory.Options的inJustDecodeBounds参数设为false,从新加载图片
    • 推荐文章:Android高效加载大图、多图解决方案,有效避免程序OOM

五、内存回收机制与GC算法(各类算法的优缺点以及应用场景);GC原理时机以及GC对象

  • 参考回答:
    • 内存断定对象可回收有两种机制:
      • 引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任什么时候刻计数器为0的对象就是不可能再被使用的。然而在主流的Java虚拟机里未选用引用计数算法来管理内存,主要缘由是它难以解决对象之间相互循环引用的问题,因此出现了另外一种对象存活断定算法。
      • 可达性分析法:经过一系列被称为『GCRoots』的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证实此对象是不可用的。其中可做为GC Roots的对象:虚拟机栈中引用的对象,主要是指栈帧中的本地变量*、本地方法栈中Native方法引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象
    • GC回收算法有如下四种:
      • 分代收集算法:是当前商业虚拟机都采用的一种算法,根据对象存活周期的不一样,将Java堆划分为新生代和老年代,并根据各个年代的特色采用最适当的收集算法。
      • 新生代:大批对象死去,只有少许存活。使用『复制算法』,只需复制少许存活对象便可。
        • 复制算法:把可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用尽后,把还存活着的对象『复制』到另一块上面,再将这一块内存空间一次清理掉。实现简单,运行高效。在对象存活率较高时就要进行较多的复制操做,效率将会变低
      • 老年代:对象存活率高。使用『标记—清理算法』或者『标记—整理算法』,只需标记较少的回收对象便可。
        • 标记-清除算法:首先『标记』出全部须要回收的对象,而后统一『清除』全部被标记的对象。标记和清除两个过程的效率都不高,清除以后会产生大量不连续的内存碎片,空间碎片太多可能会致使之后在程序运行过程当中须要分配较大对象时,没法找到足够的连续内存而不得不提早触发另外一次垃圾收集动做。
        • 标记-整理算法:首先『标记』出全部须要回收的对象,而后进行『整理』,使得存活的对象都向一端移动,最后直接清理掉端边界之外的内存。标记整理算法会将全部的存活对象移动到一端,并对不存活对象进行处理,所以其不会产生内存碎片
    • 推荐文章:图解Java 垃圾回收机制

六、内存泄露和内存溢出的区别 ?AS有什么工具能够检测内存泄露

  • 参考回答:
    • 内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;好比申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
    • 内存泄露(memory leak):是指程序在申请内存后,没法释放已申请的内存空间,一次内存泄露危害能够忽略,但内存泄露堆积后果很严重,不管多少内存,早晚会被占光。memory leak会最终会致使out of memory!
    • 查找内存泄漏能够使用Android Studio 自带的AndroidProfiler工具或MAT

七、性能优化,怎么保证应用启动不卡顿? 黑白屏怎么处理?

  • 参考回答:
    • 应用启动速度,取决于你在application里面时候作了什么事情,好比你集成了不少sdk,而且sdk的init操做都须要在主线程里实现因此会有卡顿的感受。在非必要的状况下能够把加载延后或则开启子线程处理
    • 另外,影响界面卡顿的两大因素,分别是界面绘制和数据处理。
      • 布局优化(使用include,merge标签,复杂布局推荐使用ConstraintLayout等)
      • onCreate() 中不执行耗时操做 把页面显示的 View 细分一下,放在 AsyncTask 里逐步显示,用 Handler 更好。这样用户的看到的就是有层次有步骤的一个个的 View 的展现,不会是先看到一个黑屏,而后一下显示全部 View。最好作成动画,效果更天然。
      • 利用多线程的目的就是尽量的减小 onCreate() 和 onReume() 的时间,使得用户能尽快看到页面,操做页面。
      • 减小主线程阻塞时间。
      • 提升 Adapter 和 AdapterView 的效率。
    • 推荐文章:Android 性能优化以内存检测、卡顿优化、耗电优化、APK瘦身
    • 黑白屏产生缘由:当咱们在启动一个应用时,系统会去检查是否已经存在这样一个进程,若是不存在,系统的服务会先检查startActivity中的intent的信息,而后在去建立进程,最后启动Acitivy,即冷启动。而启动出现白黑屏的问题,就是在这段时间内产生的。系统在绘制页面加载布局以前,首先会初始化窗口(Window),而在进行这一步操做时,系统会根据咱们设置的Theme来指定它的Theme 主题颜色,咱们在Style中的设置就决定了显示的是白屏仍是黑屏。
      • windowIsTranslucent和windowNoTitle,将这两个属性都设置成true (会有明显的卡顿体验,不推荐)
      • 若是启动页只是是一张图片,那么为启动页专注设置一个新的主题,设置主题的android:windowBackground属性为启动页背景图便可
      • 使用layer-list制做一张图片launcher_layer.xml,将其设置为启动页专注主题的背景,并将其设置为启动页布局的背景。
    • 推荐文章:Android启动页解决攻略

八、强引用置为null,会不会被回收?

  • 参考回答:
    • 不会当即释放对象占用的内存。 若是对象的引用被置为null,只是断开了当前线程栈帧中对该对象的引用关系,而 垃圾收集器是运行在后台的线程,只有当用户线程运行到安全点(safe point)或者安全区域才会扫描对象引用关系,扫描到对象没有被引用则会标记对象,这时候仍然不会当即释放该对象内存,由于有些对象是可恢复的(在 finalize方法中恢复引用 )。只有肯定了对象没法恢复引用的时候才会清除对象内存。

九、ListView跟RecyclerView的区别

  • 参考回答:
    • 动画区别:
      • RecyclerView中,内置有许多动画API,例如:notifyItemChanged(), notifyDataInserted(), notifyItemMoved()等等;若是须要自定义动画效果,能够经过实现(RecyclerView.ItemAnimator类)完成自定义动画效果,而后调用RecyclerView.setItemAnimator();
      • 可是ListView并无实现动画效果,但咱们能够在Adapter本身实现item的动画效果;
    • 刷新区别:
      • ListView中一般刷新数据是用全局刷新notifyDataSetChanged(),这样一来就会很是消耗资源;自己没法实现局部刷新,可是若是要在ListView实现局部刷新,依然是能够实现的,当一个item数据刷新时,咱们能够在Adapter中,实现一个onItemChanged()方法,在方法里面获取到这个item的position(能够经过getFirstVisiblePosition()),而后调用getView()方法来刷新这个item的数据;
      • RecyclerView中能够实现局部刷新,例如:notifyItemChanged();
    • 缓存区别:
      • RecyclerView比ListView多两级缓存,支持多个离ItemView缓存,支持开发者自定义缓存处理逻辑,支持全部RecyclerView共用同一个RecyclerViewPool(缓存池)。
      • ListView和RecyclerView缓存机制基本一致,但缓存使用不一样
    • 推荐文章:

十、ListView的adapter是什么adapter

  • 参考回答:
    • BaseAdapter:抽象类,实际开发中咱们会继承这个类而且重写相关方法,用得最多的一个适配器!
    • ArrayAdapter:支持泛型操做,最简单的一个适配器,只能展示一行文字〜
    • SimpleAdapter:一样具备良好扩展性的一个适配器,能够自定义多种效果!
    • SimpleCursorAdapter:用于显示简单文本类型的listView,通常在数据库那里会用到,不过有点过期,不推荐使用!

十一、LinearLayout、FrameLayout、RelativeLayout性能对比,为何?

  • 参考回答:
    • RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子 View 2次onMeasure
    • RelativeLayout的子View若是高度和RelativeLayout不一样,则会引起效率问题,当子View很复杂时,这个问题会更加严重。若是能够,尽可能使用padding代替margin。
    • 在不影响层级深度的状况下,使用LinearLayout和FrameLayout而不是RelativeLayout。

JNI

一、对JNI是否了解

  • 参考回答:
    • Java的优势是跨平台,但也由于其跨平台的的特性致使其本地交互的能力不够强大,一些和操做系统相关的的特性Java没法完成,因而Java提供JNI专门用于和本地代码交互,经过JNI,用户能够调用C、C++编写的本地代码
    • NDK是Android所提供的一个工具集合,经过NDK能够在Android中更加方便地经过JNI访问本地代码,其优势在于
      • 提升代码的安全性。因为so库反编译困难,所以NDK提升了Android程序的安全性
      • 能够很方便地使用目前已有的C/C++开源库
      • 便于平台的移植。经过C/C++实现的动态库能够很方便地在其它平台上使用
      • 提升程序在某些特定情形下的执行效率,可是并不能明显提高Android程序的性能

二、如何加载NDK库 ?如何在JNI中注册Native函数,有几种注册方法 ?

  • 参考回答:
public class JniTest{
        //加载NDK库 
        static{
            System.loadLirary("jni-test");
        }
    }

复制代码

三、你用JNI来实现过什么功能 ? 怎么实现的 ?(加密处理、影音方面、图形图像处理)

设计模式

一、你所知道的设计模式有哪些?

  • 参考回答:
    • 建立型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
    • 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
    • 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录 模式、状态模式、访问者模式、中介者模式、解释器模式。

二、谈谈MVC、MVP和MVVM,好在哪里,很差在哪里 ?

  • 参考回答:
    • MVC:
      • 视图层(View) 对应于xml布局文件和java代码动态view部分
      • 控制层(Controller) MVC中Android的控制层是由Activity来承担的,Activity原本主要是做为初始化页面,展现数据的操做,可是由于XML视图功能太弱,因此Activity既要负责视图的显示又要加入控制逻辑,承担的功能过多。
      • 模型层(Model) 针对业务模型,创建数据结构和相关的类,它主要负责网络请求,数据库处理,I/O的操做。
    • 总结
      • 具备必定的分层,model完全解耦,controller和view并无解耦层与层之间的交互尽可能使用回调或者去使用消息机制去完成,尽可能避免直接持有 controller和view在android中没法作到完全分离,但在代码逻辑层面必定要分清业务逻辑被放置在model层,可以更好的复用和修改增长业务。
    • MVP
      • 经过引入接口BaseView,让相应的视图组件如Activity,Fragment去实现BaseView,实现了视图层的独立,经过中间层Preseter实现了Model和View的彻底解耦。MVP完全解决了MVC中View和Controller傻傻分不清楚的问题,可是随着业务逻辑的增长,一个页面可能会很是复杂,UI的改变是很是多,会有很是多的case,这样就会形成View的接口会很庞大。
    • MVVM
      • MVP中咱们说过随着业务逻辑的增长,UI的改变多的状况下,会有很是多的跟UI相关的case,这样就会形成View的接口会很庞大。而MVVM就解决了这个问题,经过双向绑定的机制,实现数据和UI内容,只要想改其中一方,另外一方都可以及时更新的一种设计理念,这样就省去了不少在View层中写不少case的状况,只须要改变数据就行。
    • 三者如何选择?
      • 若是项目简单,没什么复杂性,将来改动也不大的话,那就不要用设计模式或者架构方法,只须要将每一个模块封装好,方便调用便可,不要为了使用设计模式或架构方法而使用。
      • 对于偏向展现型的app,绝大多数业务逻辑都在后端,app主要功能就是展现数据,交互等,建议使用mvvm。
      • 对于工具类或者须要写不少业务逻辑app,使用mvp或者mvvm均可。
  • 推荐文章:MVC、MVP、MVVM,我到底该怎么选?

三、封装p层以后.若是p层数据过大,如何解决?

四、是否能从Android中举几个例子说说用到了什么设计模式 ?

  • 参考回答:
    • AlertDialog、Notification源码中使用了Builder(建造者)模式完成参数的初始化
    • Okhttp内部使用了责任链模式来完成每一个Interceptor拦截器的调用
    • RxJava的观察者模式;单例模式;GridView的适配器模式;Intent的原型模式
    • 平常开发的BaseActivity抽象工厂模式

五、装饰模式和代理模式有哪些区别 ?

  • 参考回答:
    • 装饰器模式与代理模式的区别就在于
      • 二者都是对类的方法进行扩展,但装饰器模式强调的是加强自身,在被装饰以后你可以在被加强的类上使用加强后的功能。
      • 代理模式则强调要让别人帮你去作一些自己与你业务没有太多关系的职责(记录日志、设置缓存)代理模式是为了实现对象的控制,由于被代理的对象每每难以直接得到或者是其内部不想暴露出来。

六、实现单例模式有几种方法 ?懒汉式中双层锁的目的是什么 ?两次判空的目的又是什么 ?

  • 参考回答:
    • 单例模式实现方法有多种:饿汉,懒汉(线程安全,线程非安全),双重检查(DCL),内部类,以及枚举
    • 所谓双层检验锁(在加锁先后对实例对象进行两次判空的检验):加锁是为了第一次对象实例化的线程同步,而锁内还要有第二层判空是由于可能会有多个线程进入第一层if判断内部,而在加锁代码块外排队等候,若是锁内不进行第二次检验,仍然会出现实例化多个对象的状况。
    • 推荐文章:单例模式的总结

七、用到的一些开源框架,介绍一个看过源码的,内部实现过程。

八、Fragment若是在Adapter中使用应该如何解耦?

  • 参考回答:
    • 接口回调
    • 广播

你当前所处:Android篇:2019初中级Android开发社招面试解答(中)github

Android篇:2019初中级Android开发社招面试解答(下)web

Android篇:2019初中级Android开发社招面试解答(上)面试

相关文章
相关标签/搜索