2019 必看 Android 高级面试题总结

说下你所知道的设计模式与使用场景

a.建造者模式:将一个复杂对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。使用场景好比最多见的AlertDialog,拿咱们开发过程当中举例,好比Camera开发过程当中,可能须要设置一个初始化的相机配置,设置摄像头方向,闪光灯开闭,成像质量等等,这种场景下就可使用建造者模式前端

装饰者模式:动态的给一个对象添加一些额外的职责,就增长功能来讲,装饰模式比生成子类更为灵活。装饰者模式能够在不改变原有类结构的状况下曾强类的功能,好比Java中的BufferedInputStream 包装FileInputStream,举个开发中的例子,好比在咱们现有网络框架上须要增长新的功能,那么再包装一层便可,装饰者模式解决了继承存在的一些问题,好比多层继承代码的臃肿,使代码逻辑更清晰观察者模式:代理模式:门面模式:单例模式:生产者消费者模式:java

java语言的特色与OOP思想

这个经过对比来描述,好比面向对象和面向过程的对比,针对这两种思想的对比,还能够举个开发中的例子,好比播放器的实现,面向过程的实现方式就是将播放视频的这个功能分解成多个过程,好比,加载视频地址,获取视频信息,初始化解码器,选择合适的解码器进行解码,读取解码后的帧进行视频格式转换和音频重采样,而后读取帧进行播放,这是一个完整的过程,这个过程当中不涉及类的概念,而面向对象最大的特色就是类,封装继承和多态是核心,一样的以播放器为例,一面向对象的方式来实现,将会针对每个功能封装出一个对象,吧如说Muxer,获取视频信息,Decoder,解码,格式转换器,视频播放器,音频播放器等,每个功能对应一个对象,由这个对象来完成对应的功能,而且遵循单一职责原则,一个对象只作它相关的事情android

说下java中的线程建立方式,线程池的工做原理

java中有三种建立线程的方式,或者说四种1.继承Thread类实现多线程2.实现Runnable接口3.实现Callable接口4.经过线程池算法

线程池的工做原理:线程池能够减小建立和销毁线程的次数,从而减小系统资源的消耗,当一个任务提交到线程池时a. 首先判断核心线程池中的线程是否已经满了,若是没满,则建立一个核心线程执行任务,不然进入下一步b. 判断工做队列是否已满,没有满则加入工做队列,不然执行下一步c. 判断线程数是否达到了最大值,若是不是,则建立非核心线程执行任务,不然执行饱和策略,默认抛出异常sql

说下 handler 原理

Handler,Message,looper 和 MessageQueue 构成了安卓的消息机制,handler建立后能够经过 sendMessage 将消息加入消息队列,而后 looper不断的将消息从 MessageQueue 中取出来,回调到 Hander 的 handleMessage方法,从而实现线程的通讯。数据库

从两种状况来讲,第一在UI线程建立Handler,此时咱们不须要手动开启looper,由于在应用启动时,在ActivityThread的main方法中就建立了一个当前主线程的looper,并开启了消息队列,消息队列是一个无限循环,为何无限循环不会ANR?由于能够说,应用的整个生命周期就是运行在这个消息循环中的,安卓是由事件驱动的,Looper.loop不断的接收处理事件,每个点击触摸或者Activity每个生命周期都是在Looper.loop的控制之下的,looper.loop一旦结束,应用程序的生命周期也就结束了。咱们能够想一想什么状况下会发生ANR,第一,事件没有获得处理,第二,事件正在处理,可是没有及时完成,而对事件进行处理的就是looper,因此只能说事件的处理若是阻塞会致使ANR,而不能说looper的无限循环会ANR编程

另外一种状况就是在子线程建立Handler,此时因为这个线程中没有默认开启的消息队列,因此咱们须要手动调用looper.prepare(),并经过looper.loop开启消息设计模式

主线程Looper从消息队列读取消息,当读完全部消息时,主线程阻塞。子线程往消息队列发送消息,而且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。所以loop的循环并不会对CPU性能有过多的消耗。数组

内存泄漏的场景和解决办法

1.非静态内部类的静态实例非静态内部类会持有外部类的引用,若是非静态内部类的实例是静态的,就会长期的维持着外部类的引用,组织被系统回收,解决办法是使用静态内部类缓存

2.多线程相关的匿名内部类和非静态内部类匿名内部类一样会持有外部类的引用,若是在线程中执行耗时操做就有可能发生内存泄漏,致使外部类没法被回收,直到耗时任务结束,解决办法是在页面退出时结束线程中的任务

3.Handler内存泄漏Handler致使的内存泄漏也能够被概括为非静态内部类致使的,Handler内部message是被存储在MessageQueue中的,有些message不能立刻被处理,存在的时间会很长,致使handler没法被回收,若是handler是非静态的,就会致使它的外部类没法被回收,解决办法是1.使用静态handler,外部类引用使用弱引用处理2.在退出页面时移除消息队列中的消息

4.Context致使内存泄漏根据场景肯定使用Activity的Context仍是Application的Context,由于两者生命周期不一样,对于没必要须使用Activity的Context的场景(Dialog),一概采用Application的Context,单例模式是最多见的发生此泄漏的场景,好比传入一个Activity的Context被静态类引用,致使没法回收

5.静态View致使泄漏使用静态View能够避免每次启动Activity都去读取并渲染View,可是静态View会持有Activity的引用,致使没法回收,解决办法是在Activity销毁的时候将静态View设置为null(View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是咱们的Activity,声明一个静态变量引用这个View,也就引用了activity)

6.WebView致使的内存泄漏WebView只要使用一次,内存就不会被释放,因此WebView都存在内存泄漏的问题,一般的解决办法是为WebView单开一个进程,使用AIDL进行通讯,根据业务需求在合适的时机释放掉

7.资源对象未关闭致使如Cursor,File等,内部每每都使用了缓冲,会形成内存泄漏,必定要确保关闭它并将引用置为null

8.集合中的对象未清理集合用于保存对象,若是集合愈来愈大,不进行合理的清理,尤为是入股集合是静态的

9.Bitmap致使内存泄漏bitmap是比较占内存的,因此必定要在不使用的时候及时进行清理,避免静态变量持有大的bitmap对象

10.监听器未关闭不少须要register和unregister的系统服务要在合适的时候进行unregister,手动添加的listener也须要及时移除

如何避免OOM?

1.使用更加轻量的数据结构:如使用ArrayMap/SparseArray替代HashMap,HashMap更耗内存,由于它须要额外的实例对象来记录Mapping操做,SparseArray更加高效,由于它避免了Key Value的自动装箱,和装箱后的解箱操做

2.便面枚举的使用,能够用静态常量或者注解@IntDef替代

3.Bitmap优化:a.尺寸压缩:经过InSampleSize设置合适的缩放b.颜色质量:设置合适的format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差别c.inBitmap:使用inBitmap属性能够告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用以前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存从新申请一块区域来存放Bitmap。利用这种特性,即便是上千张的图片,也只会仅仅只须要占用屏幕所可以显示的图片数量的内存大小,但复用存在一些限制,具体体如今:在Android 4.4以前只能重用相同大小的Bitmap的内存,而Android 4.4及之后版本则只要后来的Bitmap比以前的小便可。使用inBitmap参数前,每建立一个Bitmap对象都会分配一块内存供其使用,而使用了inBitmap参数后,多个Bitmap能够复用一块内存,这样能够提升性能

4.StringBuilder替代String: 在有些时候,代码中会须要使用到大量的字符串拼接的操做,这种时候有必要考虑使用StringBuilder来替代频繁的“+”

5.避免在相似onDraw这样的方法中建立对象,由于它会迅速占用大量内存,引发频繁的GC甚至内存抖动

6.减小内存泄漏也是一种避免OOM的方法

说下 Activity 的启动模式,生命周期,两个 Activity 跳转的生命周期,若是一个 Activity 跳转另外一个 Activity 再按下 Home 键在回到 Activity 的生命周期是什么样的

启动模式

Standard 模式:Activity 能够有多个实例,每次启动 Activity,不管任务栈中是否已经有这个Activity的实例,系统都会建立一个新的Activity实例

SingleTop模式:当一个singleTop模式的Activity已经位于任务栈的栈顶,再去启动它时,不会再建立新的实例,若是不位于栈顶,就会建立新的实例

SingleTask模式:若是Activity已经位于栈顶,系统不会建立新的Activity实例,和singleTop模式同样。但Activity已经存在但不位于栈顶时,系统就会把该Activity移到栈顶,并把它上面的activity出栈

SingleInstance模式:singleInstance 模式也是单例的,但和singleTask不一样,singleTask 只是任务栈内单例,系统里是能够有多个singleTask Activity实例的,而 singleInstance Activity 在整个系统里只有一个实例,启动一singleInstanceActivity 时,系统会建立一个新的任务栈,而且这个任务栈只有他一个Activity

生命周期

onCreate onStart onResume onPause onStop onDestroy

两个 Activity 跳转的生命周期1.启动AonCreate - onStart - onResume

2.在A中启动BActivityA onPauseActivityB onCreateActivityB onStartActivityB onResumeActivityA onStop

3.从B中返回A(按物理硬件返回键)ActivityB onPauseActivityA onRestartActivityA onStartActivityA onResumeActivityB onStopActivityB onDestroy

4.继续返回ActivityA onPauseActivityA onStopActivityA onDestroy

onRestart 的调用场景

(1)按下home键以后,而后切换回来,会调用onRestart()。(2)从本Activity跳转到另外一个Activity以后,按back键返回原来Activity,会调用onRestart();(3)从本Activity切换到其余的应用,而后再从其余应用切换回来,会调用onRestart();

说下 Activity 的横竖屏的切换的生命周期,用那个方法来保存数据,二者的区别。触发在何时在那个方法里能够获取数据等。

是否了 SurfaceView,它是什么?他的继承方式是什么?他与View的区别(从源码角度,如加载,绘制等)。

SurfaceView中采用了双缓冲机制,保证了UI界面的流畅性,同时 SurfaceView 不在主线程中绘制,而是另开辟一个线程去绘制,因此它不妨碍UI线程;

SurfaceView 继承于View,他和View主要有如下三点区别:(1)View底层没有双缓冲机制,SurfaceView有;(2)view主要适用于主动更新,而SurfaceView适用与被动的更新,如频繁的刷新(3)view会在主线程中去更新UI,而SurfaceView则在子线程中刷新;SurfaceView的内容不在应用窗口上,因此不能使用变换(平移、缩放、旋转等)。也难以放在ListView或者ScrollView中,不能使用UI控件的一些特性好比View.setAlpha()

View:显示视图,内置画布,提供图形绘制函数、触屏事件、按键事件函数等;必须在UI主线程内更新画面,速度较慢。SurfaceView:基于view视图进行拓展的视图类,更适合2D游戏的开发;是view的子类,相似使用双缓机制,在新的线程中更新画面因此刷新界面速度比view快,Camera预览界面使用SurfaceView。GLSurfaceView:基于SurfaceView视图再次进行拓展的视图类,专用于3D游戏开发的视图;是SurfaceView的子类,openGL专用。

如何实现进程保活

a: Service 设置成 START_STICKY kill 后会被重启(等待5秒左右),重传Intent,保持与重启前同样b: 经过 startForeground将进程设置为前台进程, 作前台服务,优先级和前台应用一个级别,除非在系统内存很是缺,不然此进程不会被 killc: 双进程Service: 让2个进程互相保护对方,其中一个Service被清理后,另外没被清理的进程能够当即重启进程d: 用C编写守护进程(即子进程) : Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不一样的进程。当父进程被杀死的时候,子进程仍然能够存活,并不受影响(Android5.0以上的版本不可行)联系厂商,加入白名单e.锁屏状态下,开启一个一像素Activity

说下冷启动与热启动是什么,区别,如何优化,使用场景等。
app冷启动: 当应用启动时,后台没有该应用的进程,这时系统会从新建立一个新的进程分配给该应用, 这个启动方式就叫作冷启动(后台不存在该应用进程)。冷启动由于系统会从新建立一个新的进程分配给它,因此会先建立和初始化Application类,再建立和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。

app热启动: 当应用已经被打开, 可是被按下返回键、Home键等按键时回到桌面或者是其余程序的时候,再从新打开该app时, 这个方式叫作热启动(后台已经存在该应用进程)。热启动由于会从已有的进程中来启动,因此热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),因此热启动的过程只须要建立和初始化一个MainActivity就好了,而没必要建立和初始化Application

冷启动的流程当点击app的启动图标时,安卓系统会从Zygote进程中fork建立出一个新的进程分配给该应用,以后会依次建立和初始化Application类、建立MainActivity类、加载主题样式Theme中的windowBackground等属性设置给MainActivity以及配置Activity层级上的一些属性、再inflate布局、当onCreate/onStart/onResume方法都走完了后最后才进行contentView的measure/layout/draw显示在界面上

冷启动的生命周期简要流程:Application构造方法 –> attachBaseContext()–>onCreate –>Activity构造方法 –> onCreate() –> 配置主体中的背景等操做 –>onStart() –> onResume() –> 测量、布局、绘制显示

冷启动的优化主要是视觉上的优化,解决白屏问题,提升用户体验,因此经过上面冷启动的过程。能作的优化以下:

减小 onCreate()方法的工做量

不要让 Application 参与业务的操做

不要在 Application 进行耗时操做

不要以静态变量的方式在 Application 保存数据

减小布局的复杂度和层级

减小主线程耗时

为何冷启动会有白屏黑屏问题?缘由在于加载主题样式Theme中的windowBackground等属性设置给MainActivity发生在inflate布局当onCreate/onStart/onResume方法以前,而windowBackground背景被设置成了白色或者黑色,因此咱们进入app的第一个界面的时候会形成先白屏或黑屏一下再进入界面。解决思路以下

1.给他设置 windowBackground 背景跟启动页的背景相同,若是你的启动页是张图片那么能够直接给 windowBackground 这个属性设置该图片那么就不会有一闪的效果了

<style name=``"Splash_Theme"` `parent=``"@android:style/Theme.NoTitleBar"``>`
    <item name=``"android:windowBackground"``>@drawable/splash_bg</item>`
    <item name=``"android:windowNoTitle"``>``true``</item>`
</style>`

2.采用世面的处理方法,设置背景是透明的,给人一种延迟启动的感受。,将背景颜色设置为透明色,这样当用户点击桌面APP图片的时候,并不会"当即"进入APP,并且在桌面上停留一会,其实这时候APP已是启动的了,只是咱们心机的把Theme里的windowBackground 的颜色设置成透明的,强行把锅甩给了手机应用厂商(手机反应太慢了啦)

<style name=``"Splash_Theme"` `parent=``"@android:style/Theme.NoTitleBar"``>`
    <item name=``"android:windowIsTranslucent"``>``true``</item>`
    <item name=``"android:windowNoTitle"``>``true``</item>`
</style>`

3.以上两种方法是在视觉上显得更快,但其实只是一种表象,让应用启动的更快,有一种思路,将 Application 中的没必要要的初始化动做实现懒加载,好比,在SpashActivity 显示后再发送消息到 Application,去初始化,这样能够将初始化的动做放在后边,缩短应用启动到用户看到界面的时间

Android 中的线程有那些,原理与各自特色

AsyncTask,HandlerThread,IntentService

AsyncTask原理:内部是Handler和两个线程池实现的,Handler用于将线程切换到主线程,两个线程池一个用于任务的排队,一个用于执行任务,当AsyncTask执行execute方法时会封装出一个FutureTask对象,将这个对象加入队列中,若是此时没有正在执行的任务,就执行它,执行完成以后继续执行队列中下一个任务,执行完成经过Handler将事件发送到主线程。AsyncTask必须在主线程初始化,由于内部的Handler是一个静态对象,在AsyncTask类加载的时候他就已经被初始化了。在Android3.0开始,execute方法串行执行任务的,一个一个来,3.0以前是并行执行的。若是要在3.0上执行并行任务,能够调用executeOnExecutor方法

HandlerThread原理:继承自 Thread,start开启线程后,会在其run方法中会经过Looper 建立消息队列并开启消息循环,这个消息队列运行在子线程中,因此能够将HandlerThread 中的 Looper 实例传递给一个 Handler,从而保证这个 Handler 的 handleMessage 方法运行在子线程中,Android 中使用 HandlerThread的一个场景就是 IntentService

IntentService原理:继承自Service,它的内部封装了 HandlerThread 和Handler,能够执行耗时任务,同时由于它是一个服务,优先级比普通线程高不少,因此更适合执行一些高优先级的后台任务,HandlerThread底层经过Looper消息队列实现的,因此它是顺序的执行每个任务。能够经过Intent的方式开启IntentService,IntentService经过handler将每个intent加入HandlerThread子线程中的消息队列,经过looper按顺序一个个的取出并执行,执行完成后自动结束本身,不须要开发者手动关闭

ANR的缘由

1.耗时的网络访问2.大量的数据读写3.数据库操做4.硬件操做(好比camera)5.调用thread的join()方法、sleep()方法、wait()方法或者等待线程锁的时候6.service binder的数量达到上限7.system server中发生WatchDog ANR8.service忙致使超时无响应9.其余线程持有锁,致使主线程等待超时10.其它线程终止或崩溃致使主线程一直等待

三级缓存原理

当 Android 端须要得到数据时好比获取网络中的图片,首先从内存中查找(按键查找),内存中没有的再从磁盘文件或sqlite中去查找,若磁盘中也没有才经过网络获取

LruCache 底层实现原理:

LruCache 中 Lru 算法的实现就是经过 LinkedHashMap 来实现的。LinkedHashMap 继承于 HashMap,它使用了一个双向链表来存储 Map中的Entry顺序关系,对于get、put、remove等操做,LinkedHashMap除了要作HashMap作的事情,还作些调整Entry顺序链表的工做。LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。

说下你对 Collection 这个类的理解。

Collection是集合框架的顶层接口,是存储对象的容器,Colloction定义了接口的公用方法如add remove clear等等,它的子接口有两个,List和Set,List的特色有元素有序,元素能够重复,元素都有索引(角标),典型的有Vector:内部是数组数据结构,是同步的(线程安全的)。增删查询都很慢。ArrayList:内部是数组数据结构,是不一样步的(线程不安全的)。替代了Vector。查询速度快,增删比较慢。LinkedList:内部是链表数据结构,是不一样步的(线程不安全的)。增删元素速度快。

而Set的是特色元素无序,元素不能够重复HashSet:内部数据结构是哈希表,是不一样步的。Set集合中元素都必须是惟一的,HashSet做为其子类也需保证元素的惟一性。判断元素惟一性的方式:经过存储对象(元素)的hashCode和equals方法来完成对象惟一性的。若是对象的hashCode值不一样,那么不用调用equals方法就会将对象直接存储到集合中;若是对象的hashCode值相同,那么需调用equals方法判断返回值是否为true,若为false, 则视为不一样元素,就会直接存储;若为true, 则视为相同元素,不会存储。若是要使用HashSet集合存储元素,该元素的类必须覆盖hashCode方法和equals方法。通常状况下,若是定义的类会产生不少对象,一般都须要覆盖equals,hashCode方法。创建对象判断是否相同的依据。

TreeSet:保证元素惟一性的同时能够对内部元素进行排序,是不一样步的。判断元素惟一性的方式:根据比较方法的返回结果是否为0,若是为0视为相同元素,不存;若是非0视为不一样元素,则存。TreeSet对元素的排序有两种方式:方式一:使元素(对象)对应的类实现Comparable接口,覆盖compareTo方法。这样元素自身具备比较功能。方式二:使TreeSet集合自身具备比较功能,定义一个比较器Comparator,将该类对象做为参数传递给TreeSet集合的构造函数

说下AIDL的使用与原理

aidl是安卓中的一种进程间通讯方式

说下你对广播的理解说下你对服务的理解,如何杀死一个服务。服务的生命周期(start与bind)。是否接触过蓝牙等开发设计一个ListView左右分页排版的功能自定义View,说出主要的方法。-说下binder序列化与反序列化的过程,与使用过程是否接触过JNI/NDK,java如何调用C语言的方法-如何查看模拟器中的SP与SQList文件。如何可视化查看布局嵌套层数与加载时间。你说用的代码管理工具什么,为何会产生代码冲突,该如何解决说下你对后台的编程有那些认识,聊些前端那些方面的知识。说下你对线程池的理解,如何建立一个线程池与使用。说下你用过那些注解框架,他们的原理是什么。本身实现过,或是理解他的工做过程吗?说下java虚拟机的理解,回收机制,JVM是如何回收对象的,有哪些方法等一些java与Android源码相关知识等

大学成绩大学那些专业,你哪方面学得好单片机,嵌入式,电子线路。毕业设计什么,几我的实现的,主要功能是什么还有些其余硬件相关知识本身的职业规划与发展方向


领取资料:【Android技术开发交流②】:979045005 点击连接加入群聊:https://jq.qq.com/?_wv=1027&k...

相关文章
相关标签/搜索