2019 Android中级面试题集(小厂篇)

老是乱锁定文章,不得不重发,擦

非大厂
个人收集常见中级程度面试题,题目和答案仅供参考
相关知识点不清楚可以自行搜索技术博客

致自己:学会理解代替记忆,而不是去死记硬背

将不定期持续更新
上次更新时间:2019.10.1

目录

(1 出场频率高 2 可能会问)

1-1 Activity生命周期
1-2 Service生命周期
1-3 理解Activity、View、Window三者关系
1-4 Activity四种LaunchMode启动模式及其使用场景
1-5 Touch事件传递机制
1-6 View的绘制流程
1-7 Android中的动画有哪些
1-8 谈谈你对binder机制的理解
1-9 Android中进程间通信有哪些实现方式
1-10 实现一个自定义view的基本流程
1-11 ANR是什么?怎样避免和解决ANR?
1-12 谈谈AIDL机制
1-13 Android异步消息处理机制
1-14 Android性能优化
1-15 内存泄漏是什么?一般怎么处理内存泄漏?
1-16 谈谈IntentService
1-17 广播注册方式与区别
1-18 如何进程保活?能否保证service不被杀死?
1-19 怎么避免OOM?怎么避免图片加载导致的OOM?
1-20 Android UI适配
1-21 JAVA GC原理
1-22 Fragment与Fragment、Activity通信的方式
1-23 RecyclerView和ListView的区别
1-24 图片框架为什么要用Glide?它和Universal-ImageLoader,Picasso,Fresco对比?
1-25 网络框架为什么要用OKhttp? 它和Xutils, Volley, Retrofit对比?
1-26 熟悉哪些设计模式?

2-1 Activity在屏幕旋转时的生命周期
2-2 怎样解决方法数65K的问题
2-3 浅谈MVC、 MVP、 MVVM区别与联系
2-4 什么是三级缓存?三级缓存的原理?
2-5 热修复的原理?
2-6 AsyncTask的内部实现、适用场景
2-7 谈谈Context


1 常问

1-1 Activity生命周期

  • onCreate() 创建活动,做一些数据初始化操作
    onStart() 由不可见变为可见
    onResume() 可以与用户进行交互,位于栈顶
    onPause() 暂停,启动或恢复另一个活动时调用
    onStop() 停止,变为不可见
    onDestroy() 销毁
    onRestart() 由停止状态变为运行状态
2019 Android中级面试题集(小厂篇)_第1张图片

详细分析可参考
https://www.jianshu.com/p/f2a81c6b6c00


1-2 Service生命周期

  • onCreate()
    首次创建服务时,系统将调用此方法。如果服务已在运行,则不会调用此方法,该方法只调用一次。
  • onStartCommand()
    当另一个组件通过调用startService()请求启动服务时,系统将调用此方法。
  • onDestroy()
    当服务不再使用且将被销毁时,系统将调用此方法。
  • onBind()
    当另一个组件通过调用bindService()与服务绑定时,系统将调用此方法。
  • onUnbind()
    当另一个组件通过调用unbindService()与服务解绑时,系统将调用此方法。
  • onRebind()
    当旧的组件与服务解绑后,另一个新的组件与服务绑定,onUnbind()返回true时,系统将调用此方法。

Service生命周期分为三种情况,非绑定模式、绑定模式 、混合模式

  • 第一种
    用startService()启动服务、stopService()停止服务:
    startService()->onCreate() -> onStartCommand() ->stopService() ->onDestory
  • 第二种
    用bindService()绑定服务、unbindService()解绑服务:
    bindService()->onCreate() -> onBind() ->unbindService()-> onUnBind() -> onDestory
  • 第三种
    如果同时使用startService()和bindService(),则需要同时停止服务和解绑服务
    startService()->bindService()->onCreate() -> onStartCommnad() -> onBind() ->stopService() ->unbindService()-> onUnBind() -> onDestory

此外要注意
startService()开启Service后,调用者退出后Service仍然存在。
bindService()开启Service后,调用者退出后Service也随即退出。

2019 Android中级面试题集(小厂篇)_第2张图片

详细分析可参考
https://www.jianshu.com/p/6ad110dad2b0


1-3 理解Activity、View、Window三者关系

  • 比喻:Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)LayoutInflater像剪刀,Xml配置像窗花图纸。
  • Activity构造的时候会初始化一个Window,准确的说是PhoneWindow
  • 这个PhoneWindow有一个ViewRoot,这个ViewRoot是一个View或者说ViewGroup,是最初始的根视图。
  • ViewRoot通过addView方法来一个个的添加View。比如TextView,Button等
  • 这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。比如onClickListener,onKeyDown等。

详细分析可参考
https://blog.csdn.net/A448955639/article/details/77430263
https://blog.csdn.net/qq_21399461/article/details/79836806


1-4 四种LaunchMode启动模式及其使用场景

(1)standard
默认模式,每次启动活动都重新创建一个新的实例
(2)singleTop
在配置文件activity中加入android:launchMode="singleTop"
在启动活动时如果发现返回栈的栈顶已经是该活动,则直接使用它(并调用onNewIntent方法),否则会重新创建一个新的实例
(3)singleTask
每次启动该活动时,系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例(并调用onNewIntent方法),并把这个活动之上所有活动统统出栈,如果没有发现则会创建一个新的实例
(4)singleInstance
该模式的活动每次会请求一个新的返回栈来管理这个活动
主要适用于,当前程序和其他程序共享一个活动实例的时候才用到这个模式


1-5 Touch事件传递机制

  • 在我们点击屏幕时,会有下列事件发生:
    Activity调用dispathTouchEvent()方法,把事件传递给Window;
    Window再将事件交给DecorView(DecorView是View的根布局);
    DecorView再传递给ViewGroup;
  • Activity ——> Window ——> DecorView ——> ViewGroup——> View
  • 事件分发的主要有三个关键方法
    dispatchTouchEvent() 分发
    onInterceptTouchEvent() 拦截 ,只有ViewGroup独有此方法
    onTouchEvent() 处理触摸事件
  • Activity首先调用dispathTouchEvent()进行分发,接着调用super向下传递
  • ViewGroup首先调用dispathTouchEvent()进行分发,接着会调用onInterceptTouchEvent()(拦截事件)。若拦截事件返回为true,表示拦截,事件不会向下层的ViewGroup或者View传递;false,表示不拦截,继续分发事件。默认是false,需要提醒一下,View是没有onInterceptTouchEvent()方法的
  • 事件在ViewGroup和ViewGroup、ViewGroup和View之间进行传递,最终到达View;
  • View调用dispathTouchEvent()方法,然后在OnTouchEvent()进行处理事件;OnTouchEvent() 返回true,表示消耗此事件,不再向下传递;返回false,表示不消耗事件,交回上层处理。

上面只是简单的介绍流程
很多细节如三种方法各自返回true或false接下来会怎么走,这涉及到的就比较多了
如果可以的话,我宁愿画一张图,凭此图就可以说明所有可能发生的情况,也是分发机制的精髓所在,记得在心里过一遍流程

详细分析可参考
https://www.jianshu.com/p/f00da2a98db1
https://www.cnblogs.com/yishujun/p/5578947.html
https://blog.csdn.net/cai_iac/article/details/59110959


1-6 View的绘制流程

  • View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()
    各步骤的主要工作:
  • OnMeasure():测量View大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。
  • OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。
  • OnDraw():绘制View。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布的图层(Layer);③、绘制View的内容;④、绘制View子视图,如果没有就不用;⑤、还原图层(Layer);⑥、绘制滚动条。

详细分析可参考
https://blog.csdn.net/qidingquan/article/details/79226510
https://www.jianshu.com/p/b2ba4402d7ed


1-7 Android中的动画有哪些

  • 逐帧动画(Frame Animation)
    加载一系列Drawable资源来创建动画,简单来说就是播放一系列的图片来实现动画效果,可以自定义每张图片的持续时间
  • 补间动画(Tween Animation)
    Tween可以对View对象实现一系列动画效果,比如平移,缩放,旋转,透明度等。但是它并不会改变View属性的值,只是改变了View的绘制的位置,比如,一个按钮在动画过后,不在原来的位置,但是触发点击事件的仍然是原来的坐标。
  • 属性动画(Property Animation)
    动画的对象除了传统的View对象,还可以是Object对象,动画结束后,Object对象的属性值被实实在在的改变了

1-8 谈谈你对binder机制的理解

  • 在Android系统的Binder机制中,是有Client,Service,ServiceManager,Binder驱动程序组成的,其中Client,service,Service Manager运行在用户空间,Binder驱动程序是运行在内核空间的。而Binder就是把这4种组件粘合在一块的粘合剂,其中核心的组件就是Binder驱动程序,Service Manager提供辅助管理的功能,而Client和Service正是在Binder驱动程序和Service Manager提供的基础设施上实现C/S 之间的通信。其中Binder驱动程序提供设备文件/dev/binder与用户控件进行交互,Client、Service,Service Manager通过open和ioctl文件操作相应的方法与Binder驱动程序进行通信。而Client和Service之间的进程间通信是通过Binder驱动程序间接实现的。而Binder Manager是一个守护进程,用来管理Service,并向Client提供查询Service接口的能力。

1-9 Android中进程间通信有哪些实现方式

  • Android 跨进程通信,像intent,contentProvider,广播,service都可以跨进程通信。
    intent:这种跨进程方式并不是访问内存的形式,它需要传递一个uri,比如说打电话。
    contentProvider:这种形式,是使用数据共享的形式进行数据共享。
    service:远程服务,aidl
    广播

1-10 实现一个自定义view的基本流程

  • 1、自定义View的属性 编写attr.xml文件
    2、在layout布局文件中引用,同时引用命名空间
    3、在View的构造方法中获得我们自定义的属性 ,在自定义控件中进行读取(构造方法拿到attr.xml文件值)
    4、重写onMesure
    5、重写onDraw

详细分析可参考
https://www.jianshu.com/p/c84693096e41
https://www.gcssloop.com/category/customview


1-11 ANR是什么?怎样避免和解决ANR

  • Application Not Responding,即应用无响应
  • 出现的原因有三种:
    a)KeyDispatchTimeout(5 seconds)主要类型按键或触摸事件在特定时间内无响应
    b)BroadcastTimeout(10 seconds)BoradcastReceiver在特定的时间内无法处理
    c)ServiceTimeout(20 seconds)小概率类型Service在特定的时间内无法处理完成
  • 避免ANR最核心的一点就是在主线程减少耗时操作。通常需要从那个以下几个方案下手:
    a)使用子线程处理耗时IO操作
    b)降低子线程优先级,使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同
    c)使用Handler处理子线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程
    d)Activity的onCreate和onResume回调中尽量避免耗时的代码
    e)BroadcastReceiver中onReceiver代码也要尽量减少耗时操作,建议使用intentService处理。intentService是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题

1-12 谈谈AIDL机制

  • AIDL(Android Interface Definition Language), Android 接口定义语言,Android 提供的IPC(InterProcess Communication,进程间通信)的一种独特实现。
    使用AIDL只有在你允许来自不同应用的客户端夸进程通信访问你的service,并且想要在你的service中处理多线程的时候才时必要的。如果你不需要执行不同应用之间的IPC并发,你应该通过实现Binder建立你的接口,或者如果你想执行IPC,但是不需要处理多线程。那么使用Messenger实现你的接口
    a)建立.aidl文件
    b)实现这个接口
    c)暴露这个接口给客户端

1-13 Android异步消息处理机制

  • 异步消息处理机制主要是用来解决子线程更新UI的问题
  • 主要有四个部分:
    1. Message (消息)
    在线程之间传递,可在内部携带少量信息,用于不同线程之间交换数据
    可以使用what、arg1、arg2字段携带整型数据
    obj字段携带Object对象
    2. Handler (处理者)
    主要用于发送和处理消息,sendMessage()用来发送消息,最终会回到handleMessage()进行处理
    3. MessageQueue (消息队列)
    主要存放所有通过Handler发送的消息,它们会一直存在于队列中等待被处理
    每个线程只有一个MessageQueue
    4. Looper (循环器)
    调用loop()方法后,会不断从MessageQueue 取出待处理的消息,然后传递到handleMessage进行处理
  • 流程:
    首先在主线程中创建一个Handler对象,并重写handleMessage()方法
    然后当子线程需要更新UI的时候,就创建一个Message对象,并通过Handler的sendMessage()方法把消息发送出去,然后这条消息会被添加到MessageQueue的队列中等待被处理
    而Looper会一直从MessageQueue中取出待处理的消息,最后分发回Handler的handleMessage()进行处理,此时处于主线程,可以更新UI

1-14 Android性能优化

  • (一)布局优化
    1,就是尽量减少布局文件的层级.
    2,简单的布局能用1个LinearLayout搞定的,就不要用Relativelayout,因为Framelayout和LinearLayout都是一种简单高效的ViewGroup,Relativelayout功能比较复杂,相对来说,布局渲染要花费更多时间.
    但若是需要嵌套LinearLayout时,能用Relativelayout就用Relativelayout代替.
    3,使用include,merge标签引入布局,或者用Viewstub延时加载.
    可以通过手机开发者里面的调试GPU过度绘制来检测布局是否需要优化

  • (二)绘制优化
    绘制优化是指在View的onDrwa()方法里避免执行大量的操作.比如:
    1,不要在onDrwa()方法中创建对象,因为onDrwa()频繁被调用,这样就会产生很多临时对象,这样不近耗内存,还会导致系统频繁GC,降低运行效率.
    2,不要在onDrwa()方法中执行耗时操作.不停的执行onDrwa(),就会有很多耗时任务轮训,造成View绘制不流畅.

  • (三)线程优化
    线程的创建和销毁都比较耗性能,所以线程的优化是采用线程池
    线程池的优点:
    1,避免了线程创建销毁带来的消耗
    2,能够有效控制线程池的最大并发数,避免了大量的线程因互相抢占资源从而导致的阻塞现象.

  • (四)内存泄漏优化
    当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。

不停的内存泄漏,就会导致内存溢出.所以内存泄漏优化分为2个方面来解决:
A,代码中注意,
B,上线前通过LeakCanary、MAT等工具来检测

在敲代码时就要注意:

1,静态变量造成的内存泄漏

public class TestActivity extends Activity {
    public static Context mContext;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
    }
}  

解决方式:变量不用static修饰,或者上下文用Application的上下文.

2,Handler容易造成内存泄漏,当使用内部类或匿名内部类的方式创建Handler时,Handler对象会隐式地持有一个外部类对象的引用(这里的外部类是Activity)
一般在一个耗时任务中会开启一个子线程,如网络请求或文件读写操作,我们会使用到Handler对象。但是,如果在任务未执行完时,Activity被关闭了,Activity已不再使用,此时由GC来回收掉Activity对象。
由于子线程未执行完毕,子线程持有Handler的引用,而Handler又持有Activity的引用,这样直接导致Activity对象无法被GC回收,即出现内存泄漏。

public class TestActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //...
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
    private void loadData(){
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

解决方式:
2.1,将Handler声明为静态内部类。因为静态内部类不会持有外部类的引用,所以不会导致外部类实例出现内存泄露。
2.2,在Handler中添加对外部Activity的弱引用。由于Handler被声明为静态内部类,不再持有外部类对象的引用,导致无法在handleMessage()中操作Activity中的对象,所以需要在Handler中增加一个对Activity的弱引用。
2.3,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息

public class TestActivity  extends AppCompatActivity {
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler {
        private WeakReference reference;
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            TestActivity  activity = (TestActivity ) reference.get();
            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }

 

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}

3,资源未及时释放:
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

4,无限轮训动画造成泄漏:
解决方式:销毁时,取消动画

animator = ObjectAnimator.ofFloat(btn_home, "ratation", 0, 360).setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();     

@Override
protected void onDestroy() {
    super.onDestroy();
    animator.cancel();
}
  • (五)不要使用枚举,枚举占用的内存空间比整型大;使用一些特有的数据结构,比如AparryArray和Pair等,性能更佳.

详细分析可参考
https://blog.csdn.net/qq_41118173/article/details/80305043
https://www.jianshu.com/p/fb23c12b9b65


1-15 内存泄漏是什么?一般怎么处理内存泄漏?

  • 当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
  • 不停的内存泄漏,就会导致内存溢出
    所以内存泄漏优化分为2个方面来解决:
    A 代码中注意
    B 上线前通过LeakCanary、Android Profiler、MAT等工具来检测

具体分析:

4-1 单例和静态变量造成的内存泄漏

  • 单例模式在Android开发中会经常用到,但是如果使用不当就会导致内存泄露。因为单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露。
public class AppSettings {

    private static AppSettings sInstance;
    private Context mContext;

    private AppSettings(Context context) {
        this.mContext = context;
    }

    public static AppSettings getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new AppSettings(context);
        }
        return sInstance;
    }
}
  • 像上面代码中这样的单例,如果我们在调用getInstance(Context context)方法的时候传入的context参数是Activity、Service等上下文,就会导致内存泄露。
  • 以Activity为例,当我们启动一个Activity,并调用getInstance(Context context)方法去获取AppSettings的单例,传入Activity.this作为context,这样AppSettings类的单例sInstance就持有了Activity的引用,当我们退出Activity时,该Activity就没有用了,但是因为sIntance作为静态单例(在应用程序的整个生命周期中存在)会继续持有这个Activity的引用,导致这个Activity对象无法被回收释放,这就造成了内存泄露。
  • 解决:使用全局的上下文Application Context就是应用程序的上下文,和单例的生命周期一样长,这样就避免了内存泄漏。
    单例模式对应应用程序的生命周期,所以我们在构造单例的时候尽量避免使用Activity的上下文,而是使用Application的上下文
private AppSettings(Context context) {
    this.mContext = context.getApplicationContext();
}

静态变量导致内存泄露

  • 静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化后,它所持有的引用只有等到进程结束才会释放。
    比如下面这样的情况,在Activity中为了避免重复的创建info,将sInfo作为静态变量:
public class MainActivity extends AppCompatActivity {
 
    private static Info sInfo;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (sInfo != null) {
            sInfo = new Info(this);
        }
    }
}
 
class Info {
    public Info(Activity activity) {
    }
}
  • Info作为Activity的静态成员,并且持有Activity的引用,但是sInfo作为静态变量,生命周期肯定比Activity长。所以当Activity退出后,sInfo仍然引用了Activity,Activity不能被回收,这就导致了内存泄露。
  • 在Android开发中,静态持有很多时候都有可能因为其使用的生命周期不一致而导致内存泄露,所以我们在新建静态持有的变量的时候需要多考虑一下各个成员之间的引用关系,并且尽量少地使用静态持有的变量,以避免发生内存泄露。当然,我们也可以在适当的时候讲静态量重置为null,使其不再持有引用,这样也可以避免内存泄露。(比如Activity的onDestroy方法执行的时候设为null)

4-2 Handler容易造成内存泄漏

  • 当使用内部类或匿名内部类的方式创建Handler时,Handler对象会隐式地持有一个外部类对象的引用(这里的外部类是Activity)
  • 一般在一个耗时任务中会开启一个子线程,如网络请求或文件读写操作,我们会使用到Handler对象。但是,如果在任务未执行完时,Activity被关闭了,Activity已不再使用,此时由GC来回收掉Activity对象。
  • 由于子线程未执行完毕,子线程持有Handler的引用,而Handler又持有Activity的引用,这样直接导致Activity对象无法被GC回收,即出现内存泄漏。
public class TestActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //...
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
    private void loadData(){
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

(当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。)

解决方法一:

  • 先将Handler声明为静态内部类
    因为静态内部类不会持有外部类的引用,则不会导致外部类实例出现内存泄露。
  • 然后在Handler中添加对外部Activity的弱引用
    由于Handler被声明为静态内部类,不再持有外部类对象的引用,导致无法在handleMessage()中操作Activity中的对象,所以需要在Handler中增加一个对Activity的弱引用
  • java对于 强引用 的对象,就绝不收回,对于 软引用 的对象,是能不收回就不收回,这里的能不收回就是指内存足够的情况,对于 弱引用 的对象,是发现就收回,但是一般情况下不会发现
public class TestActivity  extends AppCompatActivity {
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler {
        private WeakReference reference;
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            TestActivity  activity = (TestActivity ) reference.get();
            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }

解决方法二:

  • 我们还可以在Activity的Destroy时或者Stop时应该移除消息队列中的消息
    在原因中我们说到,正是因为被延时处理的 message 持有 Handler 的引用,Handler 持有对 Activity 的引用,形成了message – handler – activity 这样一条引用链,导致 Activity 的泄露。因此我们可以尝试在当前界面结束时将消息队列中未被处理的消息清除,从源头上解除了这条引用链,从而使 Activity 能被及时的回收。
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

4-3 资源未释放

  • 解决:对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

4-4 无限轮训动画造成泄漏

  • 解决:销毁时,取消动画
animator = ObjectAnimator.ofFloat(btn_home, "ratation", 0, 360).setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();    

@Override
protected void onDestroy() {
    super.onDestroy();
    animator.cancel();
}

详细分析可参考
https://blog.csdn.net/wanghao200906/article/details/50426881


1-16 谈谈IntentService

  • IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们去手动控制。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。所有请求都在一个单线程中,不会阻塞应用程序的主线程(UI Thread),同一时间只处理一个请求
  • 优点:
  1. 我们省去了在Service中手动开线程的麻烦,
  2. 当操作完成时,我们不用手动停止Service

详细分析可参考
https://www.jianshu.com/p/6ad110dad2b0


1-17 广播注册方式与区别

  • Broadcast广播,注册方式主要有两种.
  • 第一种是静态注册,也可成为常驻型广播,这种广播需要在Androidmanifest.xml中进行注册,这中方式注册的广播,不受页面生命周期的影响,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动啊等等,由于这种注册的方式的广播是常驻型广播,所以会占用CPU的资源。
  • 第二种是动态注册,而动态注册的话,是在代码中注册的,这种注册方式也叫非常驻型广播,收到生命周期的影响,退出页面后,就不会收到广播,我们通常运用在更新UI方面。这种注册方式优先级较高。最后需要解绑,否会会内存泄露
    广播是分为有序广播和无序广播。

1-18 进程保活? 能否保证service不被杀死?

进程保活
先参考这篇 https://blog.csdn.net/Go_AheadY/article/details/79420027
后面会再详细深入学习

避免service被杀掉

  • 1.Service设置成START_STICKY
    kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样
  • 2.提升service进程优先级
    Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以在startForeground()使用startForeground()将service放到前台状态。这样在低内存时被kill的几率会低一些。
    【结论】如果在极度极度低内存的压力下,该service还是会被kill掉,并且不一定会restart()
  • 3.onDestroy方法里重启serviceservice +broadcast 方式
    就是当service走onDestory()的时候,发送一个自定义的广播,当收到广播的时候,重新启动service,也可以直接在onDestroy()里startService
    【结论】当使用类似口口管家等第三方应用或是在setting里-应用-强制停止时,APP进程可能就直接被干掉了,onDestroy方法都进不来,所以还是无法保证

1-19 怎么避免内存溢出OOM?怎么避免图片加载导致的OOM?

  • 内存溢出 out of memory:是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。内存溢出通俗的讲就是内存不够用。

避免内存溢出OOM

  • 防止内存泄露
    避免OOM就是要剩余足够堆内存供应用使用,要想内存足够呢,首先就需要避免应用存在内存泄漏的情况,内存泄漏后,可使用的内存空间减少,自然就会更容易产生OOM。
  • 减小对象的内存占用
    1.使用更加轻量的数据结构,例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构
    2.减小Bitmap对象的内存占用
  • 内存对象的重复利用
    1.复用系统自带的资源
    2.注意在ListView/GridView等出现大量重复子组件的视图里对ConvertView的复用
    3.Bitmap对象的复用
    4.代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”

图片加载导致的OOM怎么办?

  • 1.压缩图片
    在展示高分辨率图片的时候,最好先将图片进行压缩。压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响。
  • 2.使用图片缓存技术
    内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
  • 3.及时回收bitmap内存
    虽然android系统有自己的gc机制,但是bitmap分为java和C两部分,这个bitmap对象是由java部分分配的,不用的时候系统就会自动回收了,但是那个对应的C可用的内存区域,虚拟机时不能直接回收的,这个只能调用底层的功能释放,所以需要调用recycle()方法来释放C部分内存。
  • 4.捕获异常
    因为bitmap是吃内存大户,为了避免应用在分配bitmap内存的时候出现OutOfMemory异常以后Crash掉,对异常进行捕获,如果发生了OOM后,应用不会崩溃,而是得到一个默认的bitmap图。

1-20 Android UI适配

  • 字体使用sp,使用dp,多使用match_parent,wrap_content,weight
    图片资源,不同图片的的分辨率,放在相应的文件夹下可使用百分比代替。

1-21 JAVA GC原理

  • 垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象
    ,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能。

1-22 Fragment与Fragment、Activity通信的方式

  • 1.直接在一个Fragment中调用另外一个Fragment中的方法
    2.使用接口回调
    3.使用广播
    4.Fragment直接调用Activity中的public方法

1-23 RecyclerView和ListView的区别

  • RecyclerView可以完成ListView,GridView的效果,还可以完成瀑布流的效果。同时还可以设置列表的滚动方向(垂直或者水平);
    RecyclerView中view的复用不需要开发者自己写代码,系统已经帮封装完成了。
    RecyclerView可以进行局部刷新。
    RecyclerView提供了API来实现item的动画效果。
  • 在性能上:
    如果需要频繁的刷新数据,需要添加动画,则RecyclerView有较大的优势。
    如果只是作为列表展示,则两者区别并不是很大。

1-24 图片框架为什么要用Glide?它和Universal-ImageLoader,Picasso,Fresco区别?

  • ImageLoader :
    优点:
    ① 支持下载进度监听;
    ② 可以在 View 滚动中暂停图片加载;
    ③ 默认实现多种内存缓存算法这几个图片缓存都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等;
    ④ 支持本地缓存文件名规则定义;
    缺点:
    缺点在于不支持GIF图片加载, 缓存机制没有和http的缓存很好的结合, 完全是自己的一套缓存机制
  • Picasso:
    优点:
    ① 自带统计监控功能,支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。
    ② 支持优先级处理
    ③ 支持延迟到图片尺寸计算完成加载
    ④ 支持飞行模式、并发线程数根据网络类型而变,手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数。
    ⑤ “无”本地缓存。Picasso 自己没有实现本地缓存,而由okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。
    缺点:
    于不支持GIF,默认使用ARGB_8888格式缓存图片,缓存体积大。
  • Glide:
    优点:
    ① 图片缓存->媒体缓存 ,支持 Gif、WebP、缩略图。甚至是 Video。
    ② 支持优先级处理
    ③ 与 Activity/Fragment 生命周期一致,支持 trimMemory
    ④ 支持 okhttp、Volley。Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。
    ⑤ 内存友好,内存缓存更小图片,图片默认使用默认 RGB565 而不是 ARGB888
    缺点:
    清晰度差,但可以设置
  • Fresco:
    优点:
    ① 图片存储在安卓系统的匿名共享内存, 而不是虚拟机的堆内存中,所以不会因为图片加载而导致oom, 同时也减少垃圾回收器频繁调用回收Bitmap导致的界面卡顿,性能更高.
    ② 渐进式加载JPEG图片, 支持图片从模糊到清晰加载
    ③ 图片可以以任意的中心点显示在ImageView, 而不仅仅是图片的中心.
    ④ JPEG图片改变大小也是在native进行的, 不是在虚拟机的堆内存, 同样减少OOM
    ⑤ 很好的支持GIF图片的显示
    缺点:
    框架较大, 影响Apk体积,使用较繁琐

1-25 网络框架为什么要用OKhttp? 它和Xutils, Volley, Retrofit区别?

  • Xutils
    这个框架非常全面,可以进行网络请求,可以进行图片加载处理,可以数据储存,还可以对view进行注解,使用这个框架非常方便,但是缺点也是非常明显的,使用这个项目,会导致项目对这个框架依赖非常的严重,一旦这个框架出现问题,那么对项目来说影响非常大的

  • OKhttp
    Android开发中是可以直接使用现成的api进行网络请求的。就是使用HttpClient,HttpUrlConnection进行操作。okhttp针对Java和Android程序,封装的一个高性能的http请求库,支持同步,异步,而且okhttp又封装了线程池,封装了数据转换,封装了参数的使用,错误处理等。API使用起来更加的方便。但是我们在项目中使用的时候仍然需要自己在做一层封装,这样才能使用的更加的顺手。

  • Volley
    Volley是Google官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley里面也封装了ImageLoader,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,稍复杂点的需求还是需要用到专门的图片加载框架。Volley也有缺陷,比如不支持post大数据,所以不适合上传文件。不过Volley设计的初衷本身也就是为频繁的、数据量小的网络请求而生。

  • Retrofit
    Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格, 并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。

  • Volley VS OkHttp
    Volley的优势在于封装的更好,而使用OkHttp你需要有足够的能力再进行一次封装。而OkHttp的优势在于性能更高,因为 OkHttp基于NIO和Okio ,所以性能上要比 Volley更快。IO 和 NIO这两个都是Java中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作这种是最简单的也叫阻塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是 NIO 的方式,非阻塞式, 所以NIO当然要比IO的性能要好了,而 Okio是 Square 公司基于IO和NIO基础上做的一个更简单、高效处理数据流的一个库。理论上如果Volley和OkHttp对比的话,更倾向于使用 Volley,因为Volley内部同样支持使用OkHttp,这点OkHttp的性能优势就没了, 而且 Volley 本身封装的也更易用,扩展性更好些。

  • OkHttp VS Retrofit
    毫无疑问,Retrofit 默认是基于 OkHttp 而做的封装,这点来说没有可比性,肯定首选 Retrofit。

  • Volley VS Retrofit
    这两个库都做了不错的封装,但Retrofit解耦的更彻底,尤其Retrofit2.0出来,Jake对之前1.0设计不合理的地方做了大量重构, 职责更细分,而且Retrofit默认使用OkHttp,性能上也要比Volley占优势,再有如果你的项目如果采用了RxJava ,那更该使用 Retrofit 。所以这两个库相比,Retrofit更有优势,在能掌握两个框架的前提下该优先使用 Retrofit。但是Retrofit门槛要比Volley稍高些,要理解他的原理,各种用法,想彻底搞明白还是需要花些功夫的,如果你对它一知半解,那还是建议在商业项目使用Volley吧。


1-26 熟悉哪些设计模式?

  • 按自己的实际情况回答,当然越多越好
  • 比如我就只熟悉 单例和建造者,加以描述,最好能写出来

2 偶尔问

2-1 Activity在屏幕旋转时的生命周期

  • 不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
  • 设置Activity的android:configChanges=”orientation”时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
  • 设置Activity的android:configChanges=”orientation|keyboardHidden”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

2-2 怎样解决方法数65K的问题

  • 使用Android Studio的 gradle 可以构建MutilDex

2-3 浅谈MVC、 MVP、 MVVM区别与联系

  • (自由发挥,待补充)

2-4 什么是三级缓存?三级缓存的原理?

  • 网络加载,不优先加载,速度慢,浪费流量
    本地缓存,次优先加载,速度快
    内存缓存,优先加载,速度最快
    首次加载Android App时,肯定要通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中,之后运行APP时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中图片,最后选择访问网络

2-5 热修复的原理 (如果简历中有提到过此技术)

  • 我们知道Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件,
    而他们加载类的时候都需要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个
    数组——DexPathList,是用来存放dex文件,当BaseDexClassLoader通过调用findClass方法时,实际上就是遍历数组,
    找到相应的dex文件,找到,则直接将它return。而热修复的解决方法就是将新的dex添加到该集合中,并且是在旧的dex的前面,
    所以就会优先被取出来并且return返回。

2-6 AsyncTask的内部实现、适用场景

  • AsyncTask内部也是Handler机制来完成的,只不过Android提供了执行框架来提供线程池来执行相应地任务,因为线程池的大小问题,所以AsyncTask只应该用来执行耗时时间较短的任务,比如HTTP请求,大规模的下载和数据库的更改不适用于AsyncTask,因为会导致线程池堵塞,没有线程来执行其他的任务,导致的情形是会发生AsyncTask根本执行不了的问题。

2-7 谈谈Context

  • Context是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。Context下有两个子类,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类, ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity,所以Activity和Service以及Application的Context是不一样的,只有Activity需要主题,Service不需要主题。Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。
  • getApplicationContext()和getApplication()方法得到的对象都是同一个application对象,只是对象的类型不一样。
  • Context数量 = Activity数量 + Service数量 + 1 (1为Application)

持续更新中......


参考

https://blog.csdn.net/zl_china/article/details/77439915
https://www.jianshu.com/p/928dbf20d927
https://blog.csdn.net/carter_yu/article/details/52517475
https://yiweifen.com/html/news/WaiYu/74088.html
https://www.jianshu.com/p/62a22dc70048
https://www.jianshu.com/p/afc54b7e90cb
https://blog.csdn.net/tomcat0916/article/details/81902189

你可能感兴趣的:(2019 Android中级面试题集(小厂篇))