记一次爱奇艺、滴滴打车Android客户端面试经历,面试官的这些问题让我感受到了暴击!

“已经够难受了!还要我写在简书分享出来?听、快听、那是我心碎的声音”

上面写的是我与前同事兼朋友之间的对话,就在今年的金九银十,我满怀希望的抓住了这次面试招聘的好机会,也只是没想到这才开始就遇到了不测,接连面试了两个心仪的公司(滴滴和爱奇艺)都没过,面试过程可以算是最......心酸的一次面试经历了。他让我把面经分享出来能让正在面试的朋友面试时注意避坑,听了他的话,我把失败的面经整理了一下加了段优美的语言编成了这一篇文章。希望在面试的朋友能完美避开我蹚过的坑!

爱奇艺Android客户端面经分析

1、说一下Java语言的特点与 OOP 思想

这个通过对比来描述,比如面向对象和面向过程的对比,针对这两种思想的对比, 还可以举个开发中的例子,比如播放器的实现,面向过程的实现方式就是将播放 视频的这个功能分解成多个过程。

比如,加载视频地址,获取视频信息,初始化 解码器,选择合适的解码器进行解码,读取解码后的帧进行视频格式转换和音频 重采样,然后读取帧进行播放,这是一个完整的过程,这个过程中不涉及类的概念,而面向对象最大的特点就是类,封装继承和多态是核心。

同样的以播放器为例,一面向对象的方式来实现,将会针对每一个功能封装出一个对象,吧如说 Muxer,获取视频信息,Decoder,解码,格式转换器,视频播放器,音频播放器等,每一个功能对应一个对象,由这个对象来完成对应的功能,并且遵循单一职 责原则,一个对象只做它相关的事情

2、说一下Activity、 Window、 View 三者的差别, fragment 的特点?

Activity、 Window、 View 三者如何协同显示界面的。

——考点:显示的过程(view 绘 制流程)源码的熟悉度。
Activity 剪窗花的人(控制的);
Window 窗户(承载的一个模型);
View 窗花(要显示 的视图 View);
LayoutInflater 剪刀---将布局(图纸)剪成窗花。

fragment的特点:fragment 的设计主要是把 Activity 界面包括其逻辑打碎成很多个独立的模块,这样便 于模块的重用和更灵活地组装呈现多样的界面。
1) Fragment 可以作为 Activity 界面的一个部分组成;
2) 可以在一个 Activity 里面出现多个 Fragment,并且一个 fragment 可以在多个 Activity 中使用;
3) 在 Activity 运行中,可以动态地添加、删除、替换 Fragment。
4) Fragment 有自己的生命周期的,可以响应输入事件。

3、低版本 SDK 如何实现高版本 api?

两种情况:
1) 一般很多高版本的新的 API 都会在兼容包里面找到替代的实现。比如 fragment。 Notification , 在 v4 兼 容 包 里 面 有 NotificationCompat 类 。 5.0+ 出 现 的 backgroundTint,minSdk 小于 5.0 的话会包检测错误,v4 兼容包 DrawableCompat 类。
2) 没有替代实现就自己手动实现。比如:控件的水波纹效果—第三方实现。 或者直接在低版本去除这个效果。
3) 补充:如果设置了 minSDK 但是代码里面使用了高版本的 API,会出现检测错误。需 要在代码里面使用声明编译检测策略,比如:@SuppressLint 和@TargetApi 注解提 示编译器编译的规则。@SuppressLint 是忽略检测;@TargetApi=23,会根据你函数 里面使用的 API,严格地匹配 SDK 版本,给出相应的编译错误提示。
4) 为了避免位置的错误,最好不要使用废弃 api。(一般情况下不会有兼容性问题,后 面可能会随时删除这个 API 方法;性能方面的问题。)

4、view 绘制流程?

View 绘制中主要流程分为measure,layout, draw 三个阶段。

measure :根据父 view 传递的 MeasureSpec 进行计算大小。
layout :根据 measure 子 View 所得到的布局大小和布局参数,将子View放在合适的位置上。
draw :把 View 对象绘制到屏幕上。

5、 从Handler的源码角度说说Handler是如何执行的?

这些东西还是挺多的。那么我们先看一个栗子吧

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_http).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                text();
            }
        });

    }

    private void text() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler1 = new Handler() {//为了说明问题的写法
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.e(TAG, "handle1: " + msg.what + "-thread1-" + Thread.currentThread().getName());
                    }
                };
                Message msg = new Message();
                msg.what = 2;
                handler1.sendMessage(msg);
                handler2.obtainMessage(1).sendToTarget();
                handler2.obtainMessage(4).sendToTarget();
                Looper.loop();
                Log.e(TAG, "loop 执行完毕");
                handler2.obtainMessage(3).sendToTarget();
            }
        }).start();
    }

    Handler handler2 = new Handler() {//普通写法
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.e(TAG, "handle2: " + msg.what + "-thread2-" + Thread.currentThread().getName());
        }
    };
}

6、如何避免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 的方法

7、说一下内存泄漏的场景和解决办法?

  • 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 也需要及时移除

滴滴打车Android客户端面经分析

1、说一下Activity的生命周期?

典型情况下生命周期全面分析

异常情况下的生命周期分析

2、如何实现进程保活?

  • a: Service 设置成 START_STICKY kill 后会被重启(等待 5 秒左右),重传 Intent,保 持与重启前一样

  • b: 通过 startForeground 将进程设置为前台进程, 做前台服务,优先级和前台 应用一个级别,除非在系统内存非常缺,否则此进程不会被 kill

  • c: 双进程 Service: 让 2 个进程互相保护对方,其中一个 Service 被清理后,另 外没被清理的进程可以立即重启进程

  • d: 用 C 编写守护进程(即子进程) : Android 系统中当前进程(Process)fork 出来的子 进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以 存活,并不受影响(Android5.0 以上的版本不可行)联系厂商,加入白名单

  • e.锁屏状态下,开启一个一像素 Activity

3、说一下冷启动与热启动是什么?区别?如何优化、使用场景?

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() –> 测量、布 局、绘制显示

冷启动的优化主要是视觉上的优化,解决白屏问题,提高用户体验,所以通过上 面冷启动的过程。能做的优化如下:

  • 1、减少 onCreate()方法的工作量
  • 2、不要让 Application 参与业务的操作
  • 3、不要在 Application 进行耗时操作
  • 4、不要以静态变量的方式在 Application 保存数据
  • 5、减少布局的复杂度和层级
  • 6、减少主线程耗时

4、ANR 的原因?

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

5、说一下LruCache 底层实现原理?

LruCache 中 Lru 算法的实现就是通过 LinkedHashMap 来实现的。

LinkedHashMap 继承于 HashMap,它使用了一个双向链表来存储 Map 中的 Entry 顺序关系, 对于 get、put、remove 等操作,LinkedHashMap 除了要做 HashMap 做的事情, 还做些调整 Entry 顺序链表的工作。 LruCache 中将 LinkedHashMap 的顺序设置为 LRU 顺序来实现 LRU 缓存,每次调用 get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。

调用 put 插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值 时,将链表头部的对象(近期最少用到的)移除。

6、说一下View 树的绘图流程?

当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 draw。

整个 View 树的绘图流程在 ViewRoot.java 类的 performTraversals()函数展开,该函数 所做 的工作可简单概况为是否需要重新计算视图大小(measure)、是否需要重新 安置视图的位置(layout)、以及是否需要重绘(draw)

因为简书篇幅限制,只分享一部分。完整版的面经、答案解析和Android面试复习资料已经被我整理成(高清版)PDF,PDF中包含很多知识脉络 + 诸多细节,所以实际上比预期多花费了不少的时间和精力。

总结

其实对于Android程序员来说,要学习的知识内容和核心技术有太多太多,要想在面试中不被淘汰就只能提前建立起自己的知识体系,做好学习规划、在学习中不断提升自己,才能在互联网市场的寒冬下走下去。

请记住,从来都是我们去适应环境,而不是环境来适应我们!

想要免费领取①《Android面经总结PDF》和②《2020Android复习资料汇总PDF》学习的朋友————(此处加入免费打包领取)

猜你喜欢

转载自blog.csdn.net/qq_39477770/article/details/108798569