Android面试题整理(二)
1.Service的相关知识
Service默认并不会运行在子线程中,它也不会运行在一个独立的进程中,它同样执行在UI线程中,所以,不要在Service中执行耗时操作,如果需要执行耗时操作,可以在Service中创建子线程来完成耗时操作。
1.1Service与Thread的区别?
- Thread:Thread是程序执行的最小单元,它是分配CPU的基本单位。可以用Thread来执行一些异步操作。
- Service:Service是Android的一种机制,当它运行的时候如果是Local Service,那么对应的Service是运行在主进程的main线程上的。如果是Remote Service,那么对应的Service则是运行在独立进程的main线程上。
- 因此不要将Service理解为线程,它与线程没有任何关系。
1.2Service的生命周期
Service的生命周期如下图所示:
service.jpgService有两种启动方式的生命周期:
- startService():
- onCreate()
- onStartCommand()
- onDestory()
- bindService():
- onCreate()
- onBind()
- onUnbind()
- onDestory()
Service的两种启动方式的区别?
- 在Context中通过bindService()方法来进行Service与Context的关联并启动,并且Service的生命周期依附于Context;
- 通过startService()方法去启动一个Service,此时Service的生命周期与启动它的Context无关;
- 注意,无论如何都需要在AndroidManifest.xml中注册Service,如下:
<service
android:name=".packnameName.youServiceName"
android:enabled="true" />
2.Android的数据存储形式
- SQLite:SQLite是一个轻量级的数据库,支持基本的SQL语法,是常被采用的一种数据存储方式。Android为此数据库提供了一个名为SQLiteDatabase的类,封装了一些操作数据库的方法。
- SharePreference:以键值对的形式储存。其本质就是一个XML文件,常用于存储较简单的参数设置。
- File:就是常说的文件(I/O)存储方式,常用于存储大量的数据,但是缺点就是更新困难。
- ContentProvider:Android系统中能实现所有应用程序共享的一种数据存储方式,由于数据通常在各应用之间是相互私密的,所以此存储方式使用较少,但也是一种必不可少的存储方式。
3.Android中跨进程通讯有几种方式?
- 访问其他应用程序的Activity(调用系统通话应用)
- ContentProvider(访问系统相册)
- 广播(Broadcast)(显示系统时间)
- AIDL服务
4.Handler的原理
Handler、Looper、Message这三者都与Android异步消息处理线程相关。
4.1什么是异步消息处理线程?
异步消息处理线程启动后会进入一个无线循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数执行完成一个消息后则继续循环。如果消息队列为空,线程则会阻塞等待。
4.2Looper
对于Looper主要是prepare()和loop()两个方法。
Looper的主要作用:
- 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
- loop()方法,不断从MessageQueue中取消息,交给消息的target属性的dispatchMessage去处理。
4.3Handler
Handler有两个工作,一是发送任务或者消息,二是处理消息或者执行任务。
Handler和Looper并不是UI线程独有的。任何一个线程,都可以创建自己的Looper,创建自己的Handler。但是要注意吗在创建Handler前,必须先创建Looper。
- 首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
- Looper.loop()会让当前线程进入一个无线循环,不断从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。
- Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue相关联。
- Handler的sendMessage()方法,会给msg的target赋值为handler自身,然后加入到MessageQueue中。
- 在构造Handler的实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。
- 在Activity的启动代码中,已经在当前UI线程中调用了Looper.prepare()和Looper.loop()方法。
4.4Handler post
注意:产生一个Message对象,可以new,也可以使用Message.obtain()方法;两者都可以产生一个Message对象,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new重新分配内存。
4.5小结
Handler不是独立存在的,一个Handler,一定有一个专属的线程,一个消息队列和一个Looper与之关联。协同工作的步骤简单概括如下:
- Handler发送消息到MessageQueue,这个消息可以是Message,可以是一个Runnable;
- Looper负责从MessageQueue中取消息;
- Looper把消息dispatch给Handler;
- Handler处理消息(handleMessage或者执行Runnable)。
Handler和Looper的关系类似于生产者与消费者的关系,Handler是生产者,生产消息然后添加到MessageQueue中;Looper是消费者,从MessageQueue取出消息。
5.Android内存泄漏
5.1为什么会产生内存泄漏?
当一个对象已经不需要再使用了,本该被回收时,而有另一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏。
5.2内存泄漏对程序的影响?
内存泄漏是造成应用程序OOM的主要原因之一。Android系统为每个应用程序分配有限的内存,而当一个应用中产生的内存泄漏比较多时,就会导致应用所需要的内存超过这个系统分配的内存限额,从而造成内存溢出而导致应用Crash。
5.3Android常见的内存泄漏
- 单例造成的内存泄漏
- 由于单例的静态特性使得单例的生命周期和应用的生命周期一样长,如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象就不能被正常回收,从而导致内存泄漏。
- 非静态内部类创建静态实例造成的内存泄漏
- Handler造成的内存泄漏
- 线程造成的内存泄漏
- 异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成,那么将导致Activity的内存资源无法回收,造成内存泄漏。
- 资源未关闭造成的内存泄漏
- 对于使用了BroadcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
5.4防止内存泄漏的一些建议
- 对于生命周期比Activity长的对象如果需要应该使用ApplicationContext;
- 对于需要在静态内部类中使用非静态外部成员变量,可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏;
- 对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null;
- 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等生命周期;
- 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以如下做法避免内存泄漏:
- 将内部类改为静态内部类;
- 静态内部类中使用弱引用来引用外部类的成员变量。
- 在涉及到Context时先考虑ApplicationContext,当然它并不是万能的对于有些地方则必须使用Activity的Context。
6.ANR相关知识
6.1什么是ANR?
ANR,全称是Application Not Responding。在Android中,如果应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框被称作应用程序无响应(ANR)。
6.2ANR产生的原因
ANR产生的根本原因是APP阻塞了UI线程。在Android系统中每个APP只有一个UI线程,是在APP创建时默认生成的,UI线程默认初始化了一个消息循环来处理UI消息,ANR通常就是处理UI消息超时了。
6.3怎样避免ANR?
- UI线程尽量只做跟UI相关的工作,但一些复杂的UI操作,还是需要一些技巧来处理;
- 让耗时的工作(比如数据库操作,I/O,连接网络等)放入单独的线程处理。
- 尽量用Handler来处理UI Thread和其他Thread之间的交互。
网友评论