Android程序员面试笔试宝典
上QQ阅读APP看书,第一时间看更新

1.2.5 常见面试笔试真题

1)说一说Service的启动方式以及分别对应的生命周期。

思路:

①如果启动方式为startService(),那服务的生命周期为:

②如果启动方式为bindService(),那服务的生命周期为:

解答:

Service的启动有两种:一种是调用Context的startService()方法,然后服务就会依次执行onCreate()、onStartCommand(),当然,如果下次再执行启动方法,则服务只执行onStartCommand()方法;然后当调用Context的stopService()方法后,此时服务会执行onDestroy()方法;而另外一种启动方式就是绑定服务,就是调用bindService()方法,然后服务会依次执行onCreate()、onBind()方法,其中onCreate()也是当第一次调用启动方法才会执行,要解绑就调用unbindService()方法,然后服务就会调用onDestroy()方法。

这个生命周期可以用Google官方文档对Service的生命周期总结的概念图很好地描述出来,如图1.7所示。

图1.7 Service生命周期

图1.7左边就是启动方式为startService()的生命周期,而右边则是启动方式为bindService()的生命周期。

2)Service是怎样和Activity互相通信的?

思路:

很明显,如果采用第一种启动方式去启动服务,那服务就会马上执行onStartCommand()方法自行处理逻辑,而启动它的活动根本无法与服务进行交互,也无法控制它。所以如果想要它们能够互相通信,则需要选择用绑定服务的方式去启动服务。

解答:

调用bindService(),服务会执行onBind()方法,返回Binder对象,所以可以在服务里自定义一个Binder类,定义变量和方法,这样活动那边一旦调用方法绑定了服务后,就可以通过创建ServiceConnection对象来调用onServiceConnected()方法,拿到服务的变量同时还可以调用服务里定义的方法,这样就能实现活动与服务互相通信了。

3)谈一谈你对Handler的认识,例如机制和实现等。

思路:

还是一样,先把Handler机制的概念图在脑海中过一遍,如图1.8所示。

图1.8 Handler机制流程示意图

解答:

①Android的异步消息机制Handler,由Message、Handler、MessageQueue和Looper组成;

②Message:线程间传递的消息,可在消息里添加一些字段,用于识别不同的线程。Handler:消息处理者,用于发送和处理消息。用sendMessage()发送消息,用handleMessage()接收消息并处理。MessageQueue:消息队列,存储所有发送的消息,等待着被处理。每个线程只有一个MessageQueue对象。Looper:相当于管理者,执行loop(),就会进入循环状态,每发现MessageQueue里有一条消息,就取出该条消息将它传递给Handler的handleMessage()中;

③在要更新UI控件的那个子线程里创建Message对象,并且定义一个what字段并赋值,然后调用Handler对象的sendMessage()将这条Message发送出去,最后再使用之前创建的Handler对象,接收刚刚发出去的消息,在其handleMessage()里面进行处理,因为handleMessage()是在主线程运行的,所以能在这里对UI控件进行更新操作。

4)描述下Message、Handler、Message Queue、Looper之间的关系。

思路:

基本上,第4题与第3题问法虽然不一样,但其实考查的核心都是一样的,就是Handler机制,所以,只要把Handler机制以及要怎么使用描述清楚即可。

解答:

①Message:线程间传递的消息,可在消息里添加一些字段,用于识别不同的线程;

②Handler:消息处理者,用于发送和处理消息。用sendMessage()发送消息,用handleMessage()接收消息并处理;

③MessageQueue:消息队列,存储所有发送的消息,等待着被处理,每个线程只有一个MessageQueue对象;

④Looper:相当于管理者,执行loop(),就会进入循环状态,每发现MessageQueue里有一条消息,就取出该条消息将它传递给Handler的handleMessage()中。

5)说到服务启动,那其中绑定启动中的Activity是怎样和Service绑定的?

思路:

考查对绑定服务的启动方式是否熟悉,所以在这里用代码去分析。

解答:

①首先,自定义一个服务类继承Service,把该重写的方法重写,然后再定义一个内部类继承Binder,在这个类中定义变量与方法,最后在onBind()方法中返回Binder对象:

②现在回到Activity中去,创建刚刚在服务里定义好的Binder对象,然后再创建ServiceConnection对象,在它的onServiceConnected()方法里就能获取IBinder对象,从而也就能拿到服务的变量与调用其方法。当然最后,还要调用bindService(serviceConnection),这样就绑定成功了:

以上就是就是完整的BActivity里跟自定义服务TestService的交互过程了。

6)谈一谈你对AsyncTask的认识。

思路:同样的思路,AsyncTask是什么?有什么用?怎么使用。

解答:

AsyncTask是一个工具类,它可以更加方便开发者在子线程中对UI操作。它的使用方法为:创建一个类继承它,在继承时给AsyncTask类指定3个参数,下面是这3个参数的说明:

①Params:传入的参数,用于任务中使用;

②Progress:进度单位,显示进度;

③Result:任务执行完后返回的结果。

然后重写AsyncTask的几个方法:

①onPreExecute():在执行任务前调用,可以做一些UI显示的初始化操作;

②doInBackground(Params参数):这个方法用来处理耗时任务,因为该方法里的代码都会在子线程中运行。执行完任务后就返回结果,返不返回结果以及返回的结果类型跟在继承时给AsyncTask类指定的第3个参数Result有关;

③onProgressUpdate(Progress参数):这个方法用来对UI进行操作。该方法是在调用publishProgress(Progress参数)后触发,可以在doInBackground(Params参数)里去调用publishProgress(Progress参数),然后onProgressUpdate(Progress参数)方法就能拿到Progress参数,也就是进度参数,就可以进行UI进度等操作;

④onPostExecute(Result参数):当doInBackground(Params参数)方法执行完返回执行结果后,该方法执行,而执行结果就会返回到该方法中,然后就可以在这个方法中对结果进行操作。定义一个AsyncTask的步骤代码如下:

7)你认识IntentService吗?它有什么作用?

解答:

①它由独立的线程来处理Intent请求和各种耗时操作,所以不用开发者自己创建线程。另外,当处理完所有请求后,它会自动停止,所以也不用调用stopService()方法;

②创建一个类继承IntentService,调用父类构造方法,重写onHandleIntent()、onDestroy()等。其中onHandleIntent()就是要处理耗时操作的方法,该方法是在子线程中运行,所以不会出现ANR(Application Not Responding,应用无响应)情况。最后在调用方里调用startService(intent)即可。定义一个IntentService的步骤代码如下:

8)在使用Handler中,new Handler()是在什么线程下进行的?

解答:

new Handler()都是在主线程下进行的。例如要刷新UI组件,主线程中代码为:

而子线程中的代码为:

Looper.getMainLooper()表示切到主线程里。另外,如果不刷新UI组件,仅处理消息,而这时在主线程里还是:

如果是子线程则调用Looper.loop()或者Handler handler=new Handler(Looper.getMain Looper())。

9)在整个Handler机制工作的过程中,当handler对象发送消息给子线程时,looper是怎样启动的?

思路:这里需要从源码中一步步去分析。

解答:

①在主线程中创建Handler实例与处理消息:

②接着进入到Handler源码中,看到它其中一个构造方法:

可以看到,这几行代码的作用就是获取当前主线程中的looper实例,也就意味着当handler发送消息的时候主线程就可以获取到消息。当looper实例为空,就会抛出没有looper实例的异常,所以当在子线程中没有创建looper实例时创建handler就会报错,而在主线程中创建handler没有报错是因为main方法里的Looper.prepareMainLooper()和Looper.loop()分别了创建looper实例和开始了消息循环,所以发送消息的实例在主线程实例化的时候就已经有looper实例了。

以上便是looper启动的过程。

10)为什么不能在子线程中更新UI?

解答:

Google在设计Android时设定了UI控件线程是不安全的,所以在多线程中并发访问可能会导致UI控件处于不可预期的状态。如果像Java那样,给UI控件的访问加上锁机制,会让UI控件变得复杂和低效,也可能会阻塞某些进程的执行。

所以当一定要在子线程中对UI进行操作时,就要使用异步消息机制如Handler和AsyncTask等。

11)一般在什么情况下会使用Handler?

思路:考察Handler使用的熟悉度,一般要结合实例来阐述比较好。

解答:

当在开发中,假如UI界面上面有一个按钮,当点击这个按钮的时候,会进行网络连接,并把网络上的一个字符串拿下来显示到界面上的一个TextView上面,这时就出现了一个问题,如果这个网络连接的延迟过大,可能是十多秒甚至更长,那界面将处于一直卡顿状态,造成主线程阻塞,所以这时用线程来解决再合适不过了。

在按钮点击方法里面开启一个线程来进行网络请求,然后把请求回来的数据更新到UI上。以上思路是对的,但此时却又会报错,因为在Android中的UI线程是不安全的,这个新创建的线程不能进行UI操作,所以只能在主线程里进行UI操作,那么可以用Handler异步消息机制来解决,在子线程中进行网络请求,把请求回来的数据使用Message方法发送,然后在主线程中创建Handler对象来接收并处理消息,因为是主线程,所以拿到数据后就可以对TextView进行更新操作。这样就可以实现UI操作了。

12)在使用Handler时要注意什么?

思路:其实该题考察的内容跟第8题类似,转个思路把它阐述清楚即可。

解答:

①如果要刷新UI组件,主线程里Handler handler=new Handler(),而子线程里就要Handler handler=new Handler(Looper.getMainLooper()),而Looper.getMainLooper()是表示切到主线程里;

②如果不刷新UI组件,只是处理消息,而这时在主线程里还是Handler handler=new Handler(),而如果是子线程就调用Looper.loop()或者Handler handler=new Handler(Looper.get MainLooper())。

13)Service的两种启动方式有什么区别?

思路:该题也是老生常谈了,分别以它们的生命周期和特点来阐述区别即可。

解答:

①首先以生命周期来讲:

如果启动方式为startService(),那服务的生命周期为:

如果启动方式为bindService(),那服务的生命周期为:

②startService()启动服务,那服务就会马上执行onStartCommand()方法自行处理逻辑,而启动它的活动根本无法与服务进行交互,也无法控制它的相关方法;而bindService()启动,服务会执行onBind()方法,返回Binder对象,所以可以在服务里自定义一个Binder类,定义变量和方法,这样当活动一旦调用方法绑定了服务后,可以通过创建ServiceConnection对象来调用onServiceConnected()方法,拿到服务的变量与调用其方法。