Android-App 的设计架构经验谈,附学习笔记 + 面试整理 + 进阶书籍
阿里面试题
说下你所知道的设计模式与使用场景
a.建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。使用场景比如最常见的 AlertDialog,拿我们开发过程中举例,比如 Camera 开发过程中,可能需要设置一个初始化的相机配置,设置摄像头方向,闪光灯开闭,成像质量等等,这种场景下就可以使用建造者模式
装饰者模式:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。装饰者模式可以在不改变原有类结构的情况下曾强类的功能,比如 Java 中的 BufferedInputStream 包装 FileInputStream,举个开发中的例子,比如在我们现有网络框架上需要增加新的功能,那么再包装一层即可,装饰者模式解决了继承存在的一些问题,比如多层继承代码的臃肿,使代码逻辑更清晰观察者模式:代理模式:门面模式:单例模式:生产者消费者模式:
java 语言的特点与 OOP 思想
这个通过对比来描述,比如面向对象和面向过程的对比,针对这两种思想的对比,还可以举个开发中的例子,比如播放器的实现,面向过程的实现方式就是将播放视频的这个功能分解成多个过程,比如,加载视频地址,获取视频信息,初始化解码器,选择合适的解码器进行解码,读取解码后的帧进行视频格式转换和音频重采样,然后读取帧进行播放,这是一个完整的过程,这个过程中不涉及类的概念,而面向对象最大的特点就是类,封装继承和多态是核心,同样的以播放器为例,一面向对象的方式来实现,将会针对每一个功能封装出一个对象,吧如说 Muxer,获取视频信息,Decoder,解码,格式转换器,视频播放器,音频播放器等,每一个功能对应一个对象,由这个对象来完成对应的功能,并且遵循单一职责原则,一个对象只做它相关的事情
说下 java 中的线程创建方式,线程池的工作原理。
java 中有三种创建线程的方式,或者说四种 1.继承 Thread 类实现多线程 2.实现 Runnable 接口 3.实现 Callable 接口 4.通过线程池
线程池的工作原理:线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗,当一个任务提交到线程池时 a. 首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务,否则进入下一步 b. 判断工作队列是否已满,没有满则加入工作队列,否则执行下一步 c. 判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否则执行饱和策略,默认抛出异常
说下 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 的方法
面试时要注意,准备什么?
注意
第一个问题就是“不求甚解”。
何为不求甚解?其大多数的问题就是在于大量的使用第三方的 jar 包,问他个原理一点也答不上来,甚至是最基础的。举个例子说明,比如我问他 json 数据怎么解析,都知道哪些解析器,直接不知道,因为现在的开发者都直接去使用 GJSON,从来不去了解解析的整个过程和原理,更不会写。虽然用 google 提供的工具可以轻松实现,但是原理和过程还是得要学习的。学了总会有用,现在第三方的 jar 包多的是,封装的也都比较好,不是不能用,但是一定要学习原理和机制,这样才能提高自己,举一反三,如果只会用第三方,只会粘贴复制,你还仅仅处在码畜的级别,甚至有可能还不如。这个问题你犯了吗?记得要对号入座哦!
第二个问题就是不思进取,或者说叫技术落后。
怎么说呢,技术是不断在进步的,不断的在革新,尤其是我们程序员这个行业。很多程序猿一旦学习完了,工作了,虽然用旧的知识和技术也能实现这种效果,但是从不会考虑效率,不去学习新的技术,明明有更好的控件提供了,也不知道,或者知道并不去学习,还停留在以前的知识,面试时要的工资还挺高。再举个例子,面试时,问他们 ListView 的复用,大家答的都挺好的,都非常明白,我再问一句:Android5.0 提供了新的控件替代了它,你们知道吗?用过吗?就全都哑火了。
我都不知道该说什么好?程序猿之所以累,之所以叫猿就是因为他要时时刻刻保持一颗活到老学到老的心,要利用最新的技术知识解决新的难题。
讲到这里,两个问题已经讲完了,不知道你自己是否也有这样的毛病呢?请记得对号入座哦。现在我们再说一说面试者的另一方面的事,那就是简历的书写。
我相信大家写面试简历肯定都有夸大的成分,都会吹牛,这不要紧,牛吹好了,工资自然高,但是别吹过头,面试的时候我看到面试者的简历时,给我的感觉是:我靠,好屌,好厉害啊!这时一个情不自禁的想法就来了,我要问问他这方面的知识。
比如:一个面试应用层开发的,非把自己底层开发也写的很牛逼,你真的像你简历上写的那样“精通”吗?面试者把简历写的每个技术知识点都是精通与熟练,有时候,反而是搬起石头砸了自己的脚,吹牛可以,夸大也可以,都是为了生存吗?但是要适可而止,要在自己掌握的一个度里,这样既能自己面试的漂亮和顺利,给面试官也有好的印象,这样的情况不招你,招谁啊?
总结
学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!
最后如何才能让我们在面试中对答如流呢?
答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?有没有免费资料可以借鉴?为此我整理了一份 Android 学习资料路线:
这里是一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套 BAT 大厂面试资料专题包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家。
好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。
最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!
这些只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试 Android 岗位的门槛,让更多的 Android 工程师理解 Android 系统,掌握 Android 系统。喜欢的话麻烦点击一个喜欢再关注一下~
评论