Android面试题(二)Android基础3

 (三)常见的一些原理性问题

1、Handler机制和底层实现

Android面试题(二)Android基础3_第1张图片

上面一共出现了几种类,ActivityThread,Handler,MessageQueue,Looper,msg(Message),对这些类作简要介绍:

ActivityThread:程序的启动入口,该类就是我们说的主线程,它对Looper进行操作的。

Handler:字面意思是操控者,该类有比较重要的地方,就是通过handler来发送消息(sendMessage)到MessageQueue和 操作控件的更新(handleMessage)。handler下面持有这MessageQueue和Looper的对象。

MessageQueue:字面意思是消息队列,就是封装Message类。对Message进行插入和取出操作。

Message:这个类是封装消息体并被发送到MessageQueue中的,给类是通过链表实现的,其好处方便MessageQueue的插入和取出操作。还有一些字段是(int what,Object obj,int arg1,int arg2)。what是用户定义的消息和代码,以便接收者(handler)知道这个是关于什么的。obj是用来传输任意对象的,arg1和arg2是用来传递一些简单的整数类型的。

下面,我们按照启动顺序来进行源码分析:

Android面试题(二)Android基础3_第2张图片

先获取looper,如果没有就创建

创建过程:

ActivityThread 执行looperMainPrepare(),该方法先实例化MessageQueue对象,然后实例化Looper对象,封装mQueue和主线程,把自己放入ThreadLocal中

再执行loop()方法,里面会重复死循环执行读取MessageQueue。

(接着ActivityThread 执行Looper对象中的loop()方法)

此时调用sendMessage()方法,往MessageQueue中添加数据,其取出消息队列中的handler,执行dispatchMessage(),进而执行handleMessage(),Message的数据结构是基于链表的

2、Handler、Thread和HandlerThread的差别

①Handler:在android中负责发送和处理消息,通过它可以实现其他支线线程与主线程之间的消息通讯。

②Thread:Java进程中执行运算的最小单位,亦即执行处理机调度的基本单位。某一进程中一路单独运行的程序。

③HandlerThread:一个继承自Thread的类HandlerThread,Android中没有对Java中的Thread进行任何封装,而是提供了一个继承自Thread的类HandlerThread类,这个类对Java的Thread做了很多便利的封装。

3、ThreadLocal原理,实现及如何保证Local属性?

  当需要使用多线程时,有个变量恰巧不需要共享,此时就不必使用synchronized这么麻烦的关键字来锁住,每个线程都相当于在堆内存中开辟一个空间,线程中带有对共享变量的缓冲区,通过缓冲区将堆内存中的共享变量进行读取和操作,ThreadLocal相当于线程内的内存,一个局部变量。每次可以对线程自身的数据读取和操作,并不需要通过缓冲区与 主内存中的变量进行交互。并不会像synchronized那样修改主内存的数据,再将主内存的数据复制到线程内的工作内存。ThreadLocal可以让线程独占资源,存储于线程内部,避免线程堵塞造成CPU吞吐下降。

  在每个Thread中包含一个ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的对象,value是独享数据。

4、请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系

单的说,Handler获取当前线程中的looper对象,looper用来从存放Message的MessageQueue中取出Message,再有Handler进行Message的分发和处理.

Message Queue(消息队列):

用来存放通过Handler发布的消息,通常附属于某一个创建它的线程,可以通过Looper.myQueue()得到当前线程的消息队列

Handler:

可以发布或者处理一个消息或者操作一个Runnable,通过Handler发布消息,消息将只会发送到与它关联的消息队列,然也只能处理该消息队列中的消息

Looper:

是Handler和消息队列之间通讯桥梁,程序组件首先通过Handler把消息传递给Looper,Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的

Handler:Handler接受到消息后调用handleMessage进行处理

Message:

消息的类型,在Handler类中的handleMessage方法中得到单个的消息进行处理。

在单线程模型下,为了线程通信问题,Android设计了一个Message Queue(消息队列), 线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。

5、请描述一下View事件传递分发机制

https://blog.csdn.net/github_37130188/article/details/89112087

6、Touch事件传递流程

https://www.jianshu.com/p/62b638e1712a

7、事件分发中的onTouch 和onTouchEvent 有什么区别,又该如何使用?

 

Touch事件传递流程

1.Touch事件类型

 Touch事件被封装成MotionEvent,用户当前的touch事件主要类型有:

       ACTION_DOWN: 表示用户开始触摸

     ACTION_MOVE: 表示用户在移动(手指或者其他)

     ACTION_UP:表示用户抬起了手指

     ACTION_CANCEL:表示手势被取消了

     ACTION_OUTSIDE: 表示用户触碰超出了正常的UI边界.

     ACTION_POINTER_DOWN:有一个非主要的手指按下了.

     ACTION_POINTER_UP:一个非主要的手指抬起来了

2.Touch事件元数据

     touch事件的元数据主要包括:touch的位置、手指的个数、touch事件的时间。一个touch手势被定义为以ACTION_DOWN开始和以 ACTION_UP结束。

3.事件传递流程:

Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);

能够响应这些方法的控件包括:ViewGroup、View、Activity。

方法与控件的对应关系如下:

  Touch 事件相关方法 方法功能 ViewGroup View Activity

  public boolean dispatchTouchEvent(MotionEvent ev) 事件分发 Yes Yes Yes

  public boolean onInterceptTouchEvent(MotionEvent ev) 事件拦截 Yes No No

  public boolean onTouchEvent(MotionEvent ev) 事件响应 Yes Yes Yes

 

区别:

        onTouch方法优先级比onTouchEvent高,会先触发。假如onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。

使用:

1、onTouch()方法:

  onTouch方式是View的OnTouchListener接口中定义的方法。当一个View绑定了OnTouchListener后,当有Touch事件触发时,就会调用onTouch方法。

testBt.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN://0 Log.e("TAG", " onTouch按住"); break; case MotionEvent.ACTION_UP://1 Log.e("TAG", " onTouch抬起"); break; case MotionEvent.ACTION_MOVE://2 Log.e("TAG", " onTouch移动"); break; } //事件分发 //1、setOnTouchListener单独使用的时候返回值需要true,这样才能保证移动的时候获取相应的监听,而非一次监听(即只有按下事件) //返回false,表示没有被处理,将向父View传递。只能监听到view的"按下"事件,"移动"和"抬起"都不能监听到。因为down事件未结束 //返回true,消耗此事件,表示正确接收并处理,不在分发。"按下""抬起""移动"都能监听到了 //2、setOnTouchListener和setOnClickListener同时使用时, //返回true,事件被onTouch消耗掉了,因而不会在继续向下传递。只能监听"按下""抬起""移动",不能监听到"点击"; //返回false,"按下""抬起""移动""点击"都能监听 //onTouch是优先于onClick的,并且执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(可能还会有多次ACTION_MOVE)因此事件传递的顺序是先经过OnTouch,再传递给onClick return false; } });

2、onTouchEvent()方法:

  onTouchEvent方法时重载的Activity的方法 重写了Acitivity的onTouchEvent方法后,当屏幕有Touch事件时,此方法就会被调用。

@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN://0 Log.e("TAG", " onTouchEvent 按住"); break; case MotionEvent.ACTION_UP://1 Log.e("TAG", " onTouchEvent 抬起"); break; case MotionEvent.ACTION_MOVE://2 Log.e("TAG", " onTouchEvent 移动"); break; } return super.onTouchEvent(event); }

8、View刷新机制

在Android的View刷新机制中,父View负责刷新(invalidateChild)、布局(layoutChild)显示子View。而当子View需要刷新时,则是通知父View刷新子view来完成。

刷新代码如下(mParent为view的父view):

void invalidate(boolean invalidateCache) { final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; //noinspection PointlessBooleanExpression,ConstantConditions if (!HardwareRenderer.RENDER_DIRTY_REGIONS) { if (p != null && ai != null && ai.mHardwareAccelerated) { // fast-track for GL-enabled applications; just invalidate the whole hierarchy // with a null dirty rect, which tells the ViewAncestor to redraw everything p.invalidateChild(this, null); return; } } if (p != null && ai != null) { final Rect r = ai.mTmpInvalRect; r.set(0, 0, mRight - mLeft, mBottom - mTop); // Don't call invalidate -- we don't want to internally scroll // our own bounds p.invalidateChild(this, r); } } }

invalidate()和postInvalidate() 的区别及使用

当Invalidate()被调用的时候,View的OnDraw()就会被调用;Invalidate()是刷新UI,UI更新必须在主线程,所以invalidate必须在UI线程中被调用,如果在子线程中更新视图的就调用postInvalidate()。

postInvalidate()实际调用的方法,mHandler.sendMessageDelayed,在子线程中用handler发送消息,所以才能在子线程中使用。

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); }

9、View绘制流程

https://www.jianshu.com/p/c151efe22d0d

10、自定义控件原理

https://www.jianshu.com/p/46563c44756e

11、自定义View如何提供获取View属性的接口?

自定义属性的实现流程: 

      1.在values目录下定义一个attrs.xml :在res/values/attr.xml中定义相关属性。

      2.在对应的类文件里生成某些组件 :在对应类的构造函数中通过 obtainStyledAttributes()方法获得自定义属性的相关值

      3.在layout布局文件里为这些属性赋值:在布局中添加为该自定义组件设置一个命名空间,并且相关属性赋值

[xmlns:myandroid=”http://schemas.android.com/apk/res/cn.com.androidtest”]

也可以采用命名空间写法:xmlns:空间名=”http://schemas.android.com/apk/res/自定义组件所在顶级包名”**

12、Android代码中实现WAP方式联网

https://blog.csdn.net/asce1885/article/details/7844159

13、为什么不能在子线程更新UI?

为什么谷歌要提出:“UI更新一定要在UI线程里实现”这一规则呢?原因如下:

目的在于提高移动端更新UI的效率和和安全性,以此带来流畅的体验。原因是:

Android的UI访问是没有加锁的,多个线程可以同时访问更新操作同一个UI控件。也就是说访问UI的时候,android系统当中的控件都不是线程安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个UI控件时容易发生不可控的错误,而这是致命的。所以Android中规定只能在UI线程中访问UI,这相当于从另一个角度给Android的UI访问加上锁,一个伪锁。

14、ANR产生的原因是什么?

ANR的全称是application not responding,意思就是程序未响应,类似于我们在windows上见到的程序未响应。ANR发生会使用户觉得我们的程序不友好,那么什么情况会导致ANR的发生呢?

首先ANR的发生是有条件限制的,分为以下三点:

1.只有主线程才会产生ANR,主线程就是UI线程;

2.必须发生某些输入事件或特定操作,比如按键或触屏等输入事件,在 BroadcastReceiver或Service的各个生命周期调用函数;

3.上述事件响应超时,不同的context规定的上限时间不同

    a.主线程对输入事件5秒内没有处理完毕

    b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕

    c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。

那么导致ANR的根本原因是什么呢?简单的总结有以下两点:

1.主线程执行了耗时操作,比如数据库操作或网络编程

2.其他进程(就是其他程序)占用CPU导致本进程得不到CPU时间片,比如其他进程的频繁读写操作可能会导致这个问题。

细分的话,导致ANR的原因有如下几点:

1.耗时的网络访问

2.大量的数据读写

3.数据库操作

4.硬件操作(比如camera)

5.调用thread的join()方法、sleep()方法、wait()方法或者等待线程锁的时候

6.service binder的数量达到上限

7.system server中发生WatchDog ANR

8.service忙导致超时无响应

9.其他线程持有锁,导致主线程等待超时

10.其它线程终止或崩溃导致主线程一直等待

那么如何避免ANR的发生呢或者说ANR的解决办法是什么呢?

1.避免在主线程执行耗时操作,所有耗时操作应新开一个子线程完成,然后再在主线程更新UI。

2.BroadcastReceiver要执行耗时操作时应启动一个service,将耗时操作交给service来完成。

3.避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。

15、oom是什么?

1、什么是OOM?

程序申请内存过大,虚拟机无法满足我们,然后自杀了。这个现象通常出现在大图片的APP开发,或者需要用到很多图片的时候。通俗来讲就是我们的APP需要申请一块内存来存放图片的时候,系统认为我们的程序需要的内存过大,及时系统有充分的内存,比如1G,但是系统也不会分配给我们的APP,故而抛出OOM异常,程序没有捕捉异常,故而弹窗崩溃了

2、为什么会有OOM?

因为Android系统的APP每个进程或者虚拟机有最大内存限制,一旦超过这个限制系统就会抛出OOM错误。跟手机剩余内存是否充足没有多少关系。

3、为什么Android会有APP的内存限制

(1)要开发者使用内存更加合理。限制每个应用可用内存上限,避免恶意程序或单个程序 使用过多内存导致其他程序的不可运行。有了限制,开发者就必须合理使用资源,优化资源使用

(2)屏幕显示内容有限,内存足够即可。即使有万千图片千万数据需要使用到,但在特定时刻需要展示给用户看的总是有限的,因为屏幕显示就那么大,上面可以放的信息就是很有限的。大部分信息都是处于准备显示状态,所以没必要给予太多heap内存。必须一个ListView显示图片,打个比方这个ListView含有500个item,但是屏幕显示最多有10调item显示,其余数据是处于准备显示状态。

(3)Android多个虚拟机Davlik的限制需要。android设备上的APP运行,每打开一个应用 就会打开至少一个独立虚拟机。这样可以避免系统崩溃,但代价是浪费更多内存。

4、有GC自动回收资源,为什么还会有OOM?

Android的GC会按照特定的算法来回收不使用的资源,但是gc一般回收的是无主的对象内存或者软引用资源。

使用软引用的图片资源在一定程度上可以避免OOM。

ps:不用的对象设置为null,是一个好习惯。不过更好的方法是,不用的图片直接recycle。因为有时候通过设置null让gc来回收还是来不及。

5、怎么来避免OOM产生呢?

简单通过SoftReference引用方式管理图片资源

建一个SoftReference的hashmap,使用图片时,先检查这个hashmap是否有softreference,softreference的图片是否为空,如果为空将图片加载到softreference并加入haspmap。

16、什么情况导致OOM问题及如何优化

一、前期基础知识储备

(1)OOM定义—out of memory,内存溢出,一个程序中,已经不需要使用某个对象,但是因为仍然有引用指向它垃圾回收器就无法回收它,当该对象占用的内存无法被回收时,就容易造成内存泄露。多个内存泄漏最终会导致内存溢出,即OOM。

(内存泄漏和内存溢出两者之间的关系,可参考笔者之前的文章《Android中内存泄漏详解》)

(2)Java内存概念、内存泄漏相关知识点都可以参考笔者的文章《Android中内存泄漏详解》

二、出现OOM错误的常见原因

(1)资源对象没关闭造成的内存泄露,try catch finally中将资源回收放到finally语句可以有效避免OOM。资源性对象比如:

①Cursor游标对象没有关闭;

②调用registerReceiver后未调用unregisterReceiver();

③未关闭InputStream/OutputStream;

④Bitmap使用后未调用recycle()

(2)作用域不一样,导致对象不能被垃圾回收器回收,比如:

①非静态内部类会隐式地持有外部类的引用,

②Context泄露——概括一下,避免Context相关的内存泄露,记住以下事情:

不要保留对Context-Activity长时间的引用(对Activity的引用的时候,必须确保拥有和Activity一样的生命周期)

尝试使用Context-Application来替代Context-Activity

如果你不想控制内部类的生命周期,应避免在Activity中使用非静态的内部类,而应该使用静态的内部类,并在其中创建一个对Activity的弱引用。

③Thread 引用其他对象也容易出现对象泄露。

④onReceive方法里执行了太多的操作

(3)内存压力过大—最直接:

①图片资源加载过多,超过内存使用空间,例如Bitmap 的使用,bitmap分辨率越高,所占用的内存就越大,这个是以2为指数级增长的;

②重复创建view,listview应该使用convertview和viewholder,ListView相关的知识点有两个:1)ListView的实现;2)ListView的效率优化,其中第二点对于开发者在使用ListView时是需要重点关注的,《第一行代码》中有相关介绍。

三、避免OOM的常用方法总结

1)资源文件需要选择合适的文件夹进行存放;

2)优化布局层次,越扁平化的视图布局,占用的内存就越少,效率越高;

3)减小Bitmap对象的内存占用;

4)使用更小的图片,是否存在可以压缩的空间,是否可以使用一张更小的图片;

5)复用系统自带的资源,比如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用;

6)注意在ListView/GridView等出现大量重复子组件的视图里面对ConvertView的复用;

7)类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动;

8)在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”;

9)考虑使用Application Context而不是Activity Context

17、Oom 是否可以try catch?为什么?

只有在一种情况下,这样做是可行的:

在try语句中声明了很大的对象,导致OOM,并且可以确认OOM是由try语句中的对象声明导致的,那么在catch语句中,可以释放掉这些对象,解决OOM的问题,继续执行剩余语句。

但是这通常不是合适的做法。

Java中管理内存除了显式地catch OOM之外还有更多有效的方法:比如SoftReference, WeakReference, 硬盘缓存等。

在JVM用光内存之前,会多次触发GC,这些GC会降低程序运行的效率。

如果OOM的原因不是try语句中的对象(比如内存泄漏),那么在catch语句中会继续抛出OOM

18、内存泄漏是什么?

  指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。 

你可能感兴趣的:(Android,Java,Android面试)