美文网首页Android面试
Android面试问题及回答

Android面试问题及回答

作者: 王小凡wxf | 来源:发表于2019-07-30 19:32 被阅读0次

    1.Activity的四种启动方式

    Activity的启动模式分为四种。(standard、singleTop、singTask、singleInstance);启动模式可在AndroidManifest.xml中,通过<activity>标签的android:launchMode属性设置。

    1.默认启动模式standard:

    该模式可以被设定,不在manifest设定时候,Activity的默认模式就是standard。在该模式下,启动的Activity会依照启动顺序被依次压入Task中:

     特点:1.Activity的默认启动模式

                 2.每启动一个Activity就会在栈顶创建一个新的实例。例如:闹钟程序

     缺点:当Activity已经位于栈顶时,而再次启动Activity时还需要在创建一个新的实例,不能直接复用。

    2.栈顶复用模式singleTop:

    在该模式下,如果栈顶Activity为我们要新建的Activity(目标Activity),那么就不会重复创建新的Activity。

       特点:该模式会判断要启动的Activity实例是否位于栈顶,如果位于栈顶直接复用,否则创建新的                实例。 例如:浏览器的书签

     缺点:如果Activity并未处于栈顶位置,则可能还会创建多个实例。

    3.栈内复用模式singleTask:

    与singleTop模式相似,只不过singleTop模式是只是针对栈顶的元素,而singleTask模式下,如果task栈内存在目标Activity实例,则:

    1.将task内的对应Activity实例之上的所有Activity弹出栈。

    2.将对应Activity置于栈顶,获得焦点

    特点:使Activity在整个应用程序中只有一个实例。每次启动Activity时系统首先检查栈中是否存在当前Activity实例,如果存在 则直接复用,并把当前Activity之上所有实例全部出栈。例如:浏览器主界面

    4.全局唯一模式singleInstance:

    这是我们最后的一种启动模式,也是我们最恶心的一种模式:在该模式下,我们会为目标Activity分配一个新的affinity,并创建一个新的Task栈,将目标Activity放入新的Task,并让目标Activity获得焦点。新的Task有且只有这一个Activity实例。       如果已经创建过目标Activity实例,则不会创建新的Task,而是将以前创建过的Activity唤醒(对应Task设为Foreground状态)

    特点:该模式的Activity会启动一个新的任务栈来管理Activity实例,并且该势力在整个系统中只有一个。无论从那个任务栈中    启动该Activity,都会是该Activity所在的任务栈转移到前台,从而使Activity显示。主要作用是为了在不同程序中共享一个Activity

    2.Handler机制的原理

    Handler主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。

    Looper:一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。 

    Handler:你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出)所送来的消息。

    Message Queue(消息队列):用来存放线程放入的消息。

    线程:UIthread 通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。

    Handler创建消息:

    每一个消息都需要被指定的Handler处理,通过Handler创建消息便可以完成此功能。Android消息机制中引入了消息池。Handler创建消息时首先查询消息池中是否有消息存在,如果有直接从消息池中取得,如果没有则重新初始化一个消息实例。使用消息池的好处是:消息不被使用时,并不作为垃圾回收,而是放入消息池,可供下次Handler创建消息时使用。消息池提高了消息对象的复用,减少系统垃圾回收的次数。

    Handler发送消息:

    UI主线程初始化第一个Handler时会通过ThreadLocal创建一个Looper,该Looper与UI主线程一一对应。使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。之后其他Handler初始化的时候直接获取第一个Handler创建的Looper。Looper初始化的时候会创建一个消息队列MessageQueue。至此,主线程、消息循环、消息队列之间的关系是1:1:1。

    Handler处理消息:

    UI主线程通过Looper循环查询消息队列UI_MQ,当发现有消息存在时会将消息从消息队列中取出。首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处理。

    3.MVC、MVP和MVVM的优缺点

    1.MVC:

    简介:MVC是模型(model)-视图(view)-控制器(controller)的缩写,用一种业务逻辑、数据、界面显示分离的方法组织代码。

    其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的作用,来控制V层和M层通信以此来达到分离视图显示和业务逻辑层

    Android中的MVC

    模型层(M):针对业务模型,建立的数据结构以及相关的类,例如对数据库的操作,对网络数据的操作以及业务中的计算等操作应该放在该层。

    视图层(V):Android中的xml文件可以理解为视图层

    控制层(C):Android中的逻辑处理在Activity中体现。

    MVC的优点:

    1):分工明确,各司所职。

    2):一定程度上降低了代码间的耦合性。

    MVC的缺点:

    1):随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿。

    2):视图和控制器间过于紧密的联系,妨碍了各自的重用。

    2.MVP

    简介:MVP是模型(model)-视图(view)-协调者(presenter)的缩写。

    模型层(M):负责存储,检索以及操纵数据。

    视图层(V):负责绘制UI,与用户进行交互(在Android中体现为Activity)

    协调者(P):作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。

    MVP的优点:

    1)复杂的逻辑处理放在presenter进行处理,减少了activity的臃肿。

    2)M层与V层完全分离,修改V层不会影响M层,降低了耦合性。

    3)可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。

    3)P层与V层的交互是通过接口来进行的,便于单元测试。

    MVP的缺点:

    由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁,视图需要改变,一般presenter也需要跟着改变

    3.MVVM:

    简介:MVP的升级版本,VM是ViewModel的缩写,VM可以理解为View的数据模型和Presenter的合体,ViewModel和View之间的交互通过data binding完成。

    ViewModel(VM):ViewModel就是包含View的一些数据属性和操作,关键点就是使用databinding,View的变化会直接影响ViewModel,ViewModel的变化或者内容也会直接体现在View上。

    MVVM的优点:

    Data Binding可以实现双向的交互,使得视图和控制层之间的耦合程度进一步降低,分离更为彻底,同时减轻了Activity的压力。

    4.Android性能优化

    1.APP启动优化

    启动缓慢导致的黑屏,白屏问题,大部分的答案都是做一个透明的主题,或者是做一个Splash界面

    1.启动的优化

    我们在style中自定义一个样式Lancher,在其中放一张背景图片,或是广告图片之类的

    2.布局的优化

    Android布局优化三剑客:include+merge+ViewStub

    1.include 

    include的中文意思是“包含”、“包括”,当你在一个主页面里使用include标签时,就表示当前的主布局包含标签中的布局,这样一来,就能很好地起到复用布局的效果了。在那些常用的布局比如标题栏和分割线等上面用上它可以极大地减少代码量的。它有两个主要的属性

    1.layout:必填属性,为你需要插入当前主布局的布局名称,通过R.layout.xx的方式引用;

    2.id:当你想给通过include添加进来的布局设置一个id的时候就可以使用这个属性,它可以重写插入主布局的布局id。

    2.merge

    include标签虽然解决了布局重用的问题,却也带来了另外一个问题:布局嵌套。因为把需要重用的布局放到一个子布局之后就必须加一个根布局,如果你的主布局的根布局和你需要include的根布局都是一样的(比如都是LinearLayout),那么就相当于在中间多加了一层多余的布局了。那么有没有办法可以在使用include时不增加布局层级呢?答案当然是有的,那就是使用merge标签。

    使用merge标签要注意一点:必须是一个布局文件中的根节点,看起来跟其他布局没什么区别,但它的特别之处在于页面加载时它的不会绘制的。打个比方,它就像是布局或者控件的搬运工,把“货物”搬到主布局之后就会功成身退,不会占用任何空间,因此也就不会增加布局层级了。这正如它的名字一样,只起“合并”作用

    3.ViewStub

    页面中有些布局在初始化时没必要显示,但是又不得不事先在布局文件中写好,虽然设置成了invisible或gone,但是在初始化时还是会加载,这无疑会影响页面加载速度。针对这一情况,Android为我们提供了一个利器————ViewStub。这是一个不可见的,大小为0的视图,具有懒加载的功能,它存在于视图层级中,但只会在setVisibility()和inflate()方法调用只会才会填充视图,所以不会影响初始化加载速度。它有以下三个重要属性:

    android:layout:ViewStub需要填充的视图名称,为“R.layout.xx”的形式;

    android:inflateId:重写被填充的视图的父布局id。

    与include标签不同,ViewStub的android:id属性是设置ViewStub本身id的,而不是重写布局id,这一点可不要搞错了。另外,ViewStub还提供了OnInflateListener接口,用于监听布局是否已经加载了。

    3.内存的优化

    避免因不正确使用内存 & 缺乏管理,从而出现 内存泄露(ML)、内存溢出(OOM)、内存空间占用过大 等问题,最终导致应用程序崩溃(Crash)

    数据结构优化: 频繁字符串拼接使用stringbuilder,它比+拼接高效;大数据量存储使用ArrayMap,SparseArray替代HaspMap;避免内存抖动,避免大量分配了一些局部变量

    对象复用:复用系统自带的资源,例如adapter中的ContentView;避免在onDraw里创建对象;对象池设计的应用

    避免oom: 主要是对占用内存大的资源---bitmap的避免:即使回收bitmap;避免bitmap浪费空间try catch提示用户内存使用太大;对bitmap进行处理;做bitmap cache管理时,软引用bitmap,可以在系统内存不够时会释放软引用的对象。

    使用LeakCanary工具查找内存泄漏

    1.新建线程引起的Activity内存泄漏 Runnable改为静态非匿名内部类即可。

    2.Activity添加监听器造成Activity内存泄漏想要修复这样的 Bug,其实相当简单,就是在你的 Acitivity 被销毁的时候,将他和 NastyManager 取消掉绑定就好了。

    3.Handler 匿名内部类造成内存溢出Handler已经使用了静态内部类,并且使用了弱引用。

    Java四种引用包括强引用,软引用,弱引用,虚引用。

    强引用:

    只要引用存在,垃圾回收器永远不会回收

    软引用:

    非必须引用,内存溢出之前进行回收,可以通过以下代码实现

    Object obj = new Object();

    SoftReference sf = new SoftReference(obj);

    obj = null;

    sf.get();//有时候会返回null

    这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;

    软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。

    弱引用:

    第二次垃圾回收时回收,可以通过如下代码实现

    Object obj = new Object();

    WeakReference wf = new WeakReference(obj);

    obj = null;

    wf.get();//有时候会返回null

    wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾

    弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。

    弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。

    虚引用:

    垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现

    Object obj = new Object();

    PhantomReference pf = new PhantomReference(obj);

    obj=null;

    pf.get();//永远返回null

    pf.isEnQueued();//返回是否从内存中已经删除

    虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。

    虚引用主要用于检测对象是否已经从内存中删除。

    Glide和Picasso的优缺点

    1.Picasso和Glide的withi后面的参数不同

    Picasso.with(这里只能传入上下文)     .

    Glide.with,后面可以传入上下文,activity实例,FragmentActivity实例,Fragement.传入的对象要比前者多.

    2.加载后图片质量不同

         Picasso采用的ARGB-8888,Glide采用的是RGB-565

     相对而言,Picasso加载的是全图,图片质量和清晰对要比Glide的要高,但是,因为加载的采样率过高,导致,出现OOM异常的概率要比Glide要大很多.

    3.加载Gif图片(备注:Gif图片消耗太对内存,尽量谨慎使用):

          Picasso不能加载git图片

        Glide可以加载缓存图片

    4.缓存策略和加载速度.

         Picasso缓存的是全尺寸,而 Glide的缓存的更ImageView的尺寸相同.

      讲ImageView调整为不同的大小,不管大小如何设置,Picasso只缓存一个全尺寸的,Glide则不同,他会为每种大小不一致的ImageView都缓存一次.

    Glide的这个特点,让加载显得特别的快,而Picasso则因为需要在显示之前重新调整大小而导致一些延迟,(即便是添加了 noFade)

    5.总结:

        Glide比Picasso加载速度要快,其实他是在Picasso的基础上进行了第二次封装,但是Glide比Picasso需要更多的空间来缓存;Glide加载图像以及磁盘缓存的方式,都优于Picasso,且Glide更有利于减少OutOfMemoryError的发生; Gif动画,是Glide的杀手锏.

    Context数量 = Activity数量 + Service数量 + 1

    首先Activity.this和getApplicationContext()返回的不是同一个对象,一个是当前Activity的实例,一个是项目的Application的实例,这两者的生命周期是不同的,它们各自的使用场景不同,this.getApplicationContext()取的是这个应用程序的Context,它的生命周期伴随应用程序的存在而存在;而Activity.this取的是当前Activity的Context,它的生命周期则只能存活于当前Activity,这两者的生命周期是不同的。getApplicationContext() 生命周期是整个应用,当应用程序摧毁的时候,它才会摧毁;Activity.this的context是属于当前Activity的,当前Activity摧毁的时候,它就摧毁

    5. 自定义view

      自定义组合控件                      多个控件组合成为一个新的控件,方便多处复用   

       继承系统View控件                 继承自TextView等系统控件,在系统控件的基础功能上进行扩展   

      继承View                                 不复用系统控件逻辑,继承View进行功能定义   

      继承系统ViewGroup                继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展   

    View的绘制基本由measure()、layout()、draw()这个三个函数完成。

    |姓名|性别|年龄|

    |----|:----:|----:|

    |张三|男|32|

    |李四|女|35|

    |王二麻子|男|46|


    相关文章

      网友评论

        本文标题:Android面试问题及回答

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