天天看点

Android面试之高级篇

结合自己之前去很多大公司的面试经历和自己面别人的一些题,这里做一些总结,android面试中常见的面试题。

1,android的handler运行机制

    要解释handler的运行机制就要讲几个对象: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组件进行信息交换。下面将对它们进行分别介绍:

1. message

    message消息,理解为线程间交流的信息,处理数据后台线程需要更新ui,则发送message内含一些数据给ui线程。

2. handler

    handler处理者,是message的主要处理者,负责message的发送,message内容的执行处理。后台线程就是通过传进来的 handler对象引用来sendmessage(message)。而使用handler,需要implement 该类的 handlemessage(message)方法,它是处理这些message的操作内容,例如update ui。通常需要子类化handler来实现handlemessage方法。

3. message queue

    message queue消息队列,用来存放通过handler发布的消息,按照先进先出执行。

    每个message queue都会有一个对应的handler。handler会向message queue通过两种方法发送消息:sendmessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:通过sendmessage发送的是一个message对象,会被 handler的handlemessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。

4. looper

    looper是每条线程里的message queue的管家。android没有global的message queue,而android会自动替主线程(ui线程)建立message queue,但在子线程里并没有建立message queue。所以调用looper.getmainlooper()得到的主线程的looper不为null,但调用looper.mylooper() 得到当前线程的looper就有可能为null。对于子线程使用looper,api doc提供了正确的使用方法:这个message机制的大概流程:

    1. 在looper.loop()方法运行开始后,循环地按照接收顺序取出message queue里面的非null的message。

    2. 一开始message queue里面的message都是null的。当handler.sendmessage(message)到message queue,该函数里面设置了那个message对象的target属性是当前的handler对象。随后looper取出了那个message,则调用 该message的target指向的hander的dispatchmessage函数对message进行处理。在dispatchmessage方法里,如何处理message则由用户指定,三个判断,优先级从高到低:

    1) message里面的callback,一个实现了runnable接口的对象,其中run函数做处理工作;

    2) handler里面的mcallback指向的一个实现了callback接口的对象,由其handlemessage进行处理;

    3) 处理消息handler对象对应的类继承并实现了其中handlemessage函数,通过这个实现的handlemessage函数处理消息。

    由此可见,我们实现的handlemessage方法是优先级最低的!

    3. handler处理完该message (update ui) 后,looper则设置该message为null,以便回收!

    在网上有很多文章讲述主线程和其他子线程如何交互,传送信息,最终谁来执行处理信息之类的,个人理解是最简单的方法——判断handler对象里面的looper对象是属于哪条线程的,则由该线程来执行!

    1. 当handler对象的构造函数的参数为空,则为当前所在线程的looper;

2,android的activity的四种启动模式和用途

standerd

默认模式,可以不用写配置。在这个模式下,都会默认创建一个新的实例。因此,在这种模式下,可以有多个相同的实例,也允许多个相同activity叠加。应用场景:绝大多数activity。

singletop

栈顶复用模式,如果要开启的activity在任务栈的顶部已经存在,就不会创建新的实例,而是调用 onnewintent() 方法。避免栈顶的activity被重复的创建。

singleinstance

“singleinstance”独占一个task,其它activity不能存在那个task里;如果它启动了一个新的activity,不管新的activity的launch mode 如何,新的activity都将会到别的task里运行(如同加了flag_activity_new_task参数)。

singletask

栈内复用模式, activity只会在任务栈里面存在一个实例。如果要激活的activity,在任务栈里面已经存在,就不会创建新的activity,而是复用这个已经存在的activity,调用 onnewintent() 方法,并且清空这个activity任务栈上面所有的activity。

3,解释一下activity、 intent 、intent filter、service、broadcase、broadcasereceiver

一个activity呈现了一个用户可以操作的可视化用户界面;一个service不包含可见的用户界面,而是在后台运行,可以与一个activity绑定,通过绑定暴露出来接口并与其进行通信;一个broadcast receiver是一个接收广播消息并做出回应的component,broadcast receiver没有界面;一个intent是一个intent对象,它保存了消息的内容。对于activity和service来说,它指定了请求的操作名称和待操作数据的uri,intent对象可以显式的指定一个目标component。如果这样的话,android会找到这个component(基于manifest文件中的声明)并激活它。但如果一个目标不是显式指定的,android必须找到响应intent的最佳component。它是通过将intent对象和目标的intent filter相比较来完成这一工作的;一个component的intent filter告诉android该component能处理的intent。intent filter也是在manifest文件中声明的。

4,、serializable和parcelable的区别

在使用内存的时候,parcelable 类比serializable性能高,所以推荐使用parcelable类。

1.serializable在序列化的时候会产生大量的临时变量,从而引起频繁的gc。

传智播客武汉校区就业部出品 务实、创新、质量、分享、专注、责任

32

2.parcelable不能使用在要将数据存储在磁盘上的情况。尽管serializable效率低点,但在这

种情况下,还是建议你用serializable 。

实现:

1.serializable 的实现,只需要继承 serializable 即可。这只是给对象打了一个标记,系统会

自动将其序列化。

2.parcelabel 的实现,需要在类中添加一个静态成员变量 creator,这个变量需要继承

parcelable.creator 接口。

5,什么是内存泄漏,android在什么情况下容易产生内存泄漏

说到内存泄漏就不得不提内存溢出。

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

内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重。内存溢出导致了内存泄漏。

在android中常见的内存泄漏原因:

1. 资源释放问题

程序代码的问题,长期保持某些资源,如context、cursor、io流的引用,资源得不到释放

造成内存泄露。

2. 对象内存过大问题

保存了多个耗用内存过大的对象(如bitmap、xml文件),造成内存超出限制。

3. static关键字的使用问题

static是java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是

该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费

过多的实例(context的情况最多),这时就要谨慎对待了。

public class classname {

private static context mcontext;

//省略

}

以上的代码是很危险的,如果将 activity 赋值到 mcontext 的话。那么即使该 activity 已经

ondestroy,但是由于仍有对象保存它的引用,因此该activity依然不会被释放。

4. 线程导致内存溢出

线程产生内存泄露的主要原因在于线程生命周期的不可控。

5,数据库游标忘记回收等

那么针对上面的问题我们怎么避免呢

1、图片过大导致oom

android 中用bitmap时很容易内存溢出,比如报如下错误:java.lang.outofmemoryerror :

bitmap size exceeds vm budget。

解决方法:

方法1: 等比例缩小图片

方法2:对图片采用软引用,及时地进行recyle()操作

2、查询数据库没有关闭游标

程序中经常会进行查询数据库的操作,但是经常会有使用完毕cursor后没有关闭的情况。如果

我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会出现内

存问题,这样就会给以后的测试和问题排查带来困难和风险。

3、构造adapter时,没有使用缓存的 convertview

在使用listview的时候通常会使用adapter,那么我们应该尽可能的使用convertview。

为什么要使用convertview?

当convertview为空时,用settag()方法为每个view绑定一个存放控件的viewholder对象。

当 convertview 不为空,重复利用已经创建的 view 的时候,使用 gettag()方法获取绑定的

viewholder对象,这样就避免了findviewbyid对控件的层层查询,而是快速定位到控件。

4、bitmap对象不再使用时调用recycle()释放内存

有时我们会手工的操作bitmap对象,如果一个bitmap对象比较占内存,当它不再被使用的时

候,可以调用bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。

6、 简述下android jni调用过程

1)安装和下载cygwin,下载androidndk

2)在ndk项目中jni接口的设计

3)使用c/c++实现本地方法

4)jni生成动态链接库.so文件

5)将动态链接库复制到java工程,在java工程中调用,运行java工程即可

以上就列举这么多了,其他的大家可以自行搜索。