美文网首页
Android 面试

Android 面试

作者: 半称心x度 | 来源:发表于2021-09-08 18:38 被阅读0次

1,Activity 生命周期

onCreate()  创建 

onRestart() 重新启动 

onStart()启动   

onResume()可见 

onPause()正在停止,不能交互 

onStop()不可见 

onDestroy() 即将销毁。

第一次启动: onCreate() > onStart> onResume();

A activity 到B activity        A_onPause()>B_onCreate()>B_onStart()>B_onResume()>A_onStop()

B activity 返回A activity    B_onPause >A_onRestart()> A_onStart()> A_onResume()>B_onStop()>B_onDestroy()

显示和隐藏标准的 AlertDialog 不会对 MainActivity 的生命周期有任何的影响。

显示和隐藏全屏的 AlertDialog 不会对 MainActivity 的生命周期有任何的影响。

主题为 Dialog 的 Activity 会对 MainActivity 的生命周期有影响,并且跳转主题为 Dialog 的 Activity 与跳转普通的 Activity 的生命周期变化相同。

横竖屏切换时的生命周期调用为:onPause() -> onStop() -> onDestory() -> onCreate() -> onStart() -> onResume() 。就是一个销毁再重建的过程。

将 MainActivity 的 android:configChanges 设置为 orientation【o:rui:ten:tion】 ,之后切换横竖屏并不会有任何的生命周期方法的调用

内存不足时,杀死应用,前台的 Activity 生命周期为 onPause() -> onStop() 并没有调用 onDestory() 方法,所以主进程现在属于后台进程。

fragment被创建的时候,经历包含onAttach、onCreate、onCreateView、onActivityCreated方法;fragment对用户可见的时候,经历包含onStart、onResume方法;fragment进入“后台模式”的时候,经历onPause、onStop方法;fragment被销毁了(或者持有它的activity被销毁了),经历包含onPause、onStop、onDestroyView、onDestroy、onDetach方法;并且可用onCreate、onCreateView、onActivityCreated方法Bundle对象保存一个fragment的对象。

onAttach:当Fragment与Activity发生关联时调用

onCreate:创建Fragment时被回调,经历暂停或停止状态继而恢复后,想保留Fragment的基本组件,则在此进行初始化。

onCreateView:首次绘制页面时候调用,在此可以创建View,也可以返回null,这样不建议耗时操作。

onActivityCreated:Fragment绑定Activity,在onCreate方法已经执行完成并返回,在该方法内可以进行与Activity交互的UI操作,不能在此之前跟Activity进行交互。

onStart:启动 Fragment 时被回调,此时Fragment可见,只是还没有在前台显示,因此无法与用户进行交互

onResume:Fragment在前台可见,处于活动状态,用户可与之交互

onPause:Fragment处于暂停状态,但依然可见,用户不能与之交互

onStop:停止Fragment回调,Fragment完全不可见

onDestoryView:销毁与Fragment有关的视图,但未与Activity解除绑定

onDestory:销毁 Fragment 时被回调,通常按Back键退出或者Fragment被回收时调用此方法,此后接onDetach

onDetach:与onAttach相对应,当Fragment与Activity关联被取消时调用

setUserVisibleHint:调用次方法可以设置Fragment可见或者不可见。可以调用getUserVisibleHint()获得Fragment的可见或不可见状态,如果可见则进行懒加载操作

在异常情况下,系统默认恢复 TextView 的文本信息

在切换横竖屏时,onPause() 方法中打印了 TextView 的文本内容,切换重建后,在 onResume() 中获取到 TextView 的内容与之前的内容相同,并且要注意,重建后 onCreate() 与 onStart() 方法中获取 TextView 的文本内容是布局文件中的默认内容。

2, Activity 启动模式

standard 标准模式 每启动一个都会创建一个实例

singleTop 栈顶复用 如果在栈顶就调用onNewintent ,从onResume()开始

singleTask 栈内复用 栈内有,就复用,并将其上的Activity 移出

singleInstance 单例模式 系统会给该Activity 创建一个栈

3 .Service

onCreat()创建服务

onStartCommand()开始服务

onDestroy()销毁服务

onBind()绑定服务

onUnbind()解绑服务

1)启动Service服务

单次:startService() —> onCreate() —> onStartCommand()

多次:startService() —> onCreate() —> onStartCommand() —> onStartCommand()

2)停止Service服务

stopService() —> onDestroy()

3)绑定Service服务

bindService() —> onCreate() —> onBind()

4)解绑Service服务

unbindService() —> onUnbind() —> onDestroy()

5)启动绑定Service服务

startService() —> onCreate() —> onStartCommand() —> bindService() —> onBind()

6)解绑停止Service服务

unbindService() —> onUnbind() —> stopService() —> onDestroy()

7)解绑绑定Service服务

unbindService() —> onUnbind(ture) —> bindService() —> onRebind()

8 ,stopSelf()也可以停止服务

。如果是多个组件绑定到一个服务上,当绑定到该服务的所有组件都被销毁时,服务才会停止。

4,什么时候用Service

service是一个应用组件,长时间运行在后台,不需要直接跟用户交互。

Service不是运行在独立的线程,所以不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。

1) 默认情况下,Service其实是运行在主线程中的,如果需要执行复杂耗时的操作,必须在Service中再创建一个Thread来执行任务。

(2) Service的优先级高于后台挂起的Activity,当然,也高于Activity所创建的Thread,因此,系统可能在内存不足的时候优先杀死后台的Activity或者Thread,而不会轻易杀死Service组件,即使被迫杀死Service,也会在资源可用时重启被杀死的Service

在Service中创建的Thread,适合长期执行一些独立于APP的后台任务,比较常见的就是:在Service中保持与服务器端的长连接

5,intentService

Service不是运行在独立的线程,所以不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。

1,可用于执行后台耗时的任务,任务执行后会自动停止。

2,具有高优先级,适合高优先级的后台任务,且不容易被系统杀死。

3,可以多次启动,每个耗时操作都会以工作队列的方式在IntentService的onHandleIntent回调方法中执行

内部其实就是 HandlerThread + Handler ,  Handler发消息调用onHandleIntent()

执行完任务会 执行 stopSelf(), 自己会自杀,

优先级高, 设为0 ,为高优先级

6,Android 里的多线程

多线程的应用在Android开发中是非常常见的,常用方法主要有:

继承Thread类

实现Runnable接口

Handler

AsyncTask

HandlerThread

HandlerThread的本质:继承Thread类 & 封装Handler类

// 步骤1:创建HandlerThread实例对象

// 传入参数 = 线程名字,作用 = 标记该线程

  HandlerThread mHandlerThread = new HandlerThread("handlerThread");

/ 步骤2:启动线程

  mHandlerThread.start();

// 步骤3:创建工作线程Handler & 复写handleMessage()

// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信

// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行

  Handler workHandler = new Handler( handlerThread.getLooper() ) {

            @Override

            public boolean handleMessage(Message msg) {

                ...//消息处理

                return true;

            }

        });

// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息

// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作

  // a. 定义要发送的消息

  Message msg = Message.obtain();

  msg.what = 2; //消息的标识

  msg.obj = "B"; // 消息的存放

  // b. 通过Handler发送消息到其绑定的消息队列

  workHandler.sendMessage(msg);

// 步骤5:结束线程,即停止线程的消息循环

  mHandlerThread.quit();

由run方法可知HandlerThrea线程运行创建了Looper实例,并开启了Looper循环,循环从消息队列中获取消息并给Handler进行处理。

注:子线程 用Handler 用法

7,Looper

1,主线程(UI线程)

UI线程中Looper已经都创建好了,不用我们去创建和循环。

2,普通线程

普通线程中使用Looper需要我们自己去prepare()、loop()。

看一下普通线程中创建使用Looper的方式,代码如下:

class LooperThread extends Thread {

    public Handler mHandler;

    public void run() {

        Looper.prepare();

        mHandler = new Handler() {

            public void handleMessage(Message msg) {

                // process incoming messages here

            }

        };

        Looper.loop();

  }

}

印象中在UI线程没有出现过Looper相关的东东,这是因为UI线程中会自动创建Looper对象并进行消息循环,我们不再需要调用Looper.prepare()和Looper.loop(),但是在子线程中如果想要创建使用Handelr则需要向如上所示。

我们通过源码看一下Looper实例创建的方法:

public static void prepare() {

    prepare(true);

}

private static void prepare(boolean quitAllowed) {

    if (sThreadLocal.get() != null) {

        throw new RuntimeException("Only one Looper may be created per thread");

    }

    sThreadLocal.set(new Looper(quitAllowed));

}

sThreadLocal为ThreadLocal类型变量,用来存储线程中的Looper对象。prepare方法中首先判断sThreadLocal是否存储对象,如果存储了则抛出异常,这是因为在同一个线程中Loop.prepare()方法不能调用两次,也就是同一个线程中最多有一个Looper实例

接着看Looper的构造器:

private Looper(boolean quitAllowed) {

  mQueue = new MessageQueue(quitAllowed);

    mRun = true;

  mThread = Thread.currentThread();

}

在构造器中,创建了一个MessageQueue消息队列;然后获取当前的线程,使Looper实例与线程绑定。由prepare方法可知一个线程只会有一个Looper实例,所以一个Looper实例也只有一个MessageQueue实例。但这并不代表一个线程只能有一个MessageQueue实例,这是为什么呢?很简单,我们可以自己new 一个MessageQueue实例就可以了,但这个MessageQueue并不是该线程中Handelr对应的消息队列。

总结:

UI线程会自动创建Looper实例、并且调用loop()方法,不需要我们再调用prepare()和loop().

Looper与创建它的线程绑定,确保一个线程最多有一个Looper实例,同时一个Looper实例只有一个MessageQueue实例。

loop()函数循环从MessageQueue中获取消息,并将消息交给消息的target的dispatchMessage去处理。如果MessageQueue中没有消息则获取消息可能会阻塞。

通过调用Looper的quit或quitsafely终止消息循环。

相同点:

将不在接受新的事件加入消息队列。

不同点

当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。

当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。

6, Parcelable 与 Serializable 区别

两者最大的区别在于存储媒介的不同,Serializable使用IO读写存储在硬盘上,而Parcelable是直接在内存中读写,很明显内存的读写速度通常大于IO读写,

android上应该尽量采用Parcelable,效率至上

编码上:

Serializable代码量少,写起来方便

Parcelable代码多一些

效率上:

Parcelable的速度比高十倍以上

serializable的迷人之处在于你只需要对某个类以及它的属性实现Serializable 接口即可。Serializable 接口是一种标识接口(marker interface),这意味着无需实现方法,Java便会对这个对象进行高效的序列化操作。

这种方法的缺点是使用了反射,序列化的过程较慢。这种机制会在序列化的时候创建许多的临时对象,容易触发垃圾回收。

Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了

7内存抖动;

避免在ondraw()创建对象,因为你会频繁创建只使用一次的对象,就会导致内存的迅速攀升,就很可能触犯GC的回收,

这种在短时间内反复发生内存增长和回收的循环。有界面卡顿的风险。Android 官方叫Memory churn

在onDraw()里创建对象往往跟绘制相关,这些对象又经常包含通往系统下层Native的对象引用,这就导致回收耗时会更高。

8 MVC、MVP 和 MVVM 区别

1.1 MVC

1.1.1 MVC三个字母的含义

View :对应的XML文件

Model:实体类javabean

Controllor: 对应Activity或者Fragment

  1.1.3 优缺点(以android的角度讲)

优点

Xml就是View层,与java逻辑代码解耦,具有一定的解耦

缺点

没有固定的model层,Activity中代表Controllor,网络接口的model也在Activity中,一个逻辑稍微复杂的页面代码行数都上千行,影响阅读,Activity既然控制着View,又含有Model。相当耦合

2.2 MVP

2.2.1 MVP三个字母的含义

View 对应于Activity和Xml,负责页面的展示

Model 实体类javabean

Presenter 控制器 负责完成View于Model间的交互与逻辑处理(网络和逻辑)

2.2.3 优缺点

优点:

Model和View之间解耦,两者交互由Presenter完成,把部分的逻辑的代码从Fragment和Activity业务的逻辑移出来

Activity或者Fragment只是用来展示控件,或者控制控件的显示和隐藏,以及View的变化

在MVP中View和presenter要相互持有,方便调用对方

缺点:

随着业务的增加,IView的接口和IPresenter的接口会越来越多

更换Xml里面的控件会引起IView接口和IPressenter接口的改动

2.3 MVVM(只从android考虑)

2.3.1 MVVM三个字母的含义

View 还是Activity或者fragment,也就是Xml,负责页面的展示,或者控制控件的显示和隐藏,以及View的变化,不参与任何逻辑和数据的处理

Viewmodel 主要负责业务逻辑和数据处理,本身不持有 View 层引用,通过LiveData(如果项目中有Rxjava可以不引用LiveData) 向 View 层发送数据,通过DataBinding更改View中的UI层

Model:实体类javabean,便是指这里的Repository ,主要负责从本地数据库或者远程服务器来获取数据,Repository统一了数据的入口,获取到数据,将数据发送 

2.3.3 优缺点

优点 :

View和数据双向绑定,当model变化时会自动同步给View

crash会降低,xml里面自己会有判断

View(Activity或者Fragment)层就是View层,不处理任何关于数据的逻辑

缺点 :

Xml里面写代码,对于android有点不习惯,并且会使xml臃肿

无法快速定位crash位置,Debug比较困难

双向绑定不利于View的复用,因为每个xml里面都有一个model,但是每个页面的mode有可能不一样

xml里面写java代码,不能用kotlin的简单语言

UI线程是不安全

悲观锁:

Binder 是基于文件的通信方式

RxJava

dispatch Touch Event  事件分发

On Intercept  touch Event 是否拦截

On Touch Event  消耗

ViewGroup和View 的dispatchTouchEvent 是做事件分发,那么这个事件可能分发出去的四个目标

注:------> 后面代表事件目标需要怎么做。

1、 自己消费,终结传递。------->return true ;

2、 给自己的onTouchEvent处理-------> 调用super.dispatchTouchEvent()系统默认会去调用 onInterceptTouchEvent,在onInterceptTouchEvent return true就会去把事件分给自己的onTouchEvent处理。

3、 传给子View------>调用super.dispatchTouchEvent()默认实现会去调用 onInterceptTouchEvent 在onInterceptTouchEvent return false,就会把事件传给子类。

4、 不传给子View,事件终止往下传递,事件开始回溯,从父View的onTouchEvent开始事件从下到上回归执行每个控件的onTouchEvent------->return false;

注: 由于View没有子View所以不需要onInterceptTouchEvent 来控件是否把事件传递给子View还是拦截,所以View的事件分发调用super.dispatchTouchEvent()的时候默认把事件传给自己的onTouchEvent处理(相当于拦截),对比ViewGroup的dispatchTouchEvent 事件分发,View的事件分发没有上面提到的4个目标的第3点。

ViewGroup和View的onTouchEvent方法是做事件处理的,那么这个事件只能有两个处理方式:

1、自己消费掉,事件终结,不再传给谁----->return true;

2、继续从下往上传,不消费事件,让父View也能收到到这个事件----->return false;View的默认实现是不消费的。所以super==false。

ViewGroup的onInterceptTouchEvent方法对于事件有两种情况:

1、拦截下来,给自己的onTouchEvent处理--->return true;

2、不拦截,把事件往下传给子View---->return false,ViewGroup默认是不拦截的,所以super==false;

相关文章

网友评论

      本文标题:Android 面试

      本文链接:https://www.haomeiwen.com/subject/bxlliltx.html