本节内容:服务是什么、Android 多线程编程、服务的基本用法、服务的生命周期、服务的更多技巧
服务是什么------------------------------------------------
服务(Service)是Android 中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使当程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。
不过需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。
另外,也不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
Android 多线程编程--------------------------------------
线程的基本用法:
1、新建一个类继承自Thread,然后重写父类的run()方法:使用继承的方式耦合性有点高
1
2
3
4
5
6
7
8
9
10
|
//定义线程 class MyThread extends Thread
{ @Override public void run()
{ //
处理具体的逻辑 } } //启动线程 new MyThread().start(); |
2、更多的时候我们都会选择使用实现Runnable 接口:
1
2
3
4
5
6
7
8
9
10
11
|
//定义线程 class MyThread implements Runnable
{ @Override public void run()
{ //
处理具体的逻辑 } } //启动线程 MyThread
myThread = new MyThread(); new Thread(myThread).start(); |
3、也可以使用匿名类的方式,这种写法更为常见,如下所示:
1
2
3
4
5
6
|
new Thread( new Runnable()
{ @Override public void run()
{ //
处理具体的逻辑 } }).start(); |
在子线程中更新UI:
和许多其他的GUI 库一样,Android 的UI 也是线程不安全的。也就是说,如果想要更新应用程序里的UI 元素,则必须在主线程中进行,否则就会出现异常。
Android 确实是不允许在子线程中进行UI 操作的。但是有些时候,我们必须在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的UI 控件,Android 提供了一套异步消息处理机制,完美地解决了在子线程中进行UI 操作的问题。
新建项目,布局如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
< RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android" android:layout_width = "match_parent" android:layout_height = "match_parent" > < Button android:id = "@+id/change_text" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:text = "Change
Text" /> < TextView android:id = "@+id/text" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_centerInParent = "true" android:text = "Hello
world" android:textSize = "20sp" /> </ RelativeLayout > |
主活动中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
public class MainActivity extends Activity implements OnClickListener
{ public static final int UPDATE_TEXT
= 1 ; private TextView
text; private Button
changeText; private Handler
handler = new Handler()
{ public void handleMessage(Message
msg) { switch (msg.what)
{ case UPDATE_TEXT: //
在这里可以进行UI操作 text.setText( "Nice
to meet you" ); break ; default : break ; } } }; @Override protected void onCreate(Bundle
savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); text
= (TextView) findViewById(R.id.text); changeText
= (Button) findViewById(R.id.change_text); changeText.setOnClickListener( this ); } @Override public void onClick(View
v) { switch (v.getId())
{ case R.id.change_text: new Thread( new Runnable()
{ @Override public void run()
{ Message
message = new Message(); message.what
= UPDATE_TEXT; handler.sendMessage(message); //
将Message对象发送出去 } }).start(); break ; default : break ; } } } |
我们在子线程里创建了一个Message(android.os.Message)对象,并将它的what 字段的值指定为UPDATE_TEXT,然后调用Handler 的sendMessage()方法将这条Message 发送出去。很快,Handler 就会收到这条Message,并在handleMessage()方法中对它进行处理。注意此时handleMessage()方法中的代码就是在主线程当中运行的了,所以我们可以放心地在这里进行UI 操作。接下来对Message 携带的what 字段的值进行判断,如果等于UPDATE_TEXT,就将TextView 显示的内容改成Nice to meet you。
解析异步消息处理机制:
四个部分:Message、Handler、MessageQueue 和Looper。
1. Message
Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上一小节中我们使用到了Message 的what 字段,除此之外还可以使用arg1 和arg2 字段来携带一些整型数据,使用obj 字段携带一个Object 对象。
2. Handler
Handler 顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用Handler 的sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler 的handleMessage()方法中。
3. MessageQueue
MessageQueue 是消息队列的意思,它主要用于存放所有通过Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
4. Looper
Looper 是每个线程中的MessageQueue 的管家,调用Looper 的loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue 中存在一条消息,就会将它取出,并传递到Handler 的handleMessage()方法中。每个线程中也只会有一个Looper 对象。
首先需要在主线程当中创建一个Handler 对象,并重写handleMessage()方法。然后当子线程中需要进行UI 操作时,就创建一个Message 对象,并通过Handler 将这条消息发送出去。之后这条消息会被添加到MessageQueue 的队列中等待被处理,而Looper 则会一直尝试从MessageQueue 中取出待处理消息,最后分发回Handler的handleMessage()方法中。由于Handler 是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI 操作了。
使用AsyncTask:
为了更加方便我们在子线程中对UI 进行操作,Android 还提供了另外一些好用的工具,AsyncTask 就是其中之一。借助AsyncTask,即使你对异步消息处理机制完全不了解,也可以十分简单地从子线程切换到主线程。当然,AsyncTask 背后的实现原理也是基于异步消息处理机制的,只是Android 帮我们做了很好的封装而已。
首先必须要创建一个子类去继承它。在继承时我们可以为AsyncTask 类指定三个泛型参数,这三个参数的用途如下。
1. Params
在执行AsyncTask 时需要传入的参数,可用于在后台任务中使用。
2. Progress
后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
3. Result
当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
一个最简单的自定义AsyncTask 就可以写成如下方式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
……
}
然后需要去重写AsyncTask 中的几个方法才能完成对任务的定制。经常需要去重写的方法有以下四个。
1. onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
2. doInBackground(Params...)----执行具体的耗时任务
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return 语句来将任务的执行结果返回(然后调用onPostExecute()方法),如果AsyncTask 的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI 操作的,如果需要更新UI 元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法(从子线程切换到UI线程)来完成。
3. onProgressUpdate(Progress...)----进行UI 操作
当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就会很快被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI 进行操作,利用参数中的数值就可以对界面元素进行相应地更新。
4. onPostExecute(Result)(在主线程中运行)----执行一些任务的收尾工作
当后台任务执行完毕并通过return 语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI 操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
因此,一个比较完整的自定义AsyncTask 就可以写成如下方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
class DownloadTask extends AsyncTask<Void,
Integer, Boolean> { @Override protected void onPreExecute()
{ progressDialog.show(); //
显示进度对话框 } @Override protected Boolean
doInBackground(Void... params) { try { while ( true )
{ int downloadPercent
= doDownload(); //
这是一个虚构的方法 publishProgress(downloadPercent); //////////////////// if (downloadPercent
>= 100 )
{ break ; } } } catch (Exception
e) { return false ; } return true ; } @Override protected void onProgressUpdate(Integer...
values) { //
在这里更新下载进度 progressDialog.setMessage( "Downloaded
" +
values[ 0 ]
+ "%" ); } @Override protected void onPostExecute(Boolean
result) { progressDialog.dismiss(); //
关闭进度对话框 //
在这里提示下载结果 if (result)
{ Toast.makeText(context, "Download
succeeded" ,Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, "
Download failed" ,Toast.LENGTH_SHORT).show(); } } } |
要启动这个任务,只需编写以下代码即可:
new DownloadTask().execute();
服务的基本用法-------------------------------------------
定义一个服务:新建一个类,继承Service,实现onBind()方法,并重写onCreate()、onStartCommand()和onDestroy()方法。最后,每一个服务都需要在AndroidManifest.xml 文件中进行注册才能生效,这是Android 四大组件共有的特点。
启动和停止服务:
在MyService 的几个方法中加入打印日志,如下所示:
onCreate()方法是在服务第一次创建的时候调用的,而onStartCommand()方法则在每次启动服务的时候都会调用;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public class MyService extends Service
{ @Override public IBinder
onBind(Intent intent) { return null ; } @Override public void onCreate()
{ super .onCreate(); Log.d( "MyService" , "onCreate
executed" ); } @Override public int onStartCommand(Intent
intent, int flags, int startId)
{ Log.d( "MyService" , "onStartCommand
executed" ); return super .onStartCommand(intent,
flags, startId); } @Override public void onDestroy()
{ super .onDestroy(); Log.d( "MyService" , "onDestroy
executed" ); } } |
主活动布局,添加一个开始服务和停止服务按钮:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" android:layout_width = "match_parent" android:layout_height = "match_parent" android:orientation = "vertical" > < Button android:id = "@+id/start_service" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:text = "Start
Service" /> < Button android:id = "@+id/stop_service" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:text = "Stop
Service" /> </ LinearLayout > |
修改MainActivity 中的代码,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public class MainActivity extends Activity implements OnClickListener
{ private Button
startService; private Button
stopService; @Override protected void onCreate(Bundle
savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); startService
= (Button) findViewById(R.id.start_service); stopService
= (Button) findViewById(R.id.stop_service); startService.setOnClickListener( this ); stopService.setOnClickListener( this ); } @Override public void onClick(View
v) { switch (v.getId())
{ case R.id.start_service: Intent
startIntent = new Intent( this ,
MyService. class ); startService(startIntent); //
启动服务 break ; case R.id.stop_service: Intent
stopIntent = new Intent( this ,
MyService. class ); stopService(stopIntent); //
停止服务 break ; default : break ; } } } |
只需要在MyService 的任何一个位置调用stopSelf()方法就能让这个服务停止。
活动和服务进行通信:==================
服务的生命周期------------------------------------------
一旦在项目的任何位置调用了Context 的startService()方法,相应的服务就会启动起来,
并回调onStartCommand()方法。如果这个服务之前还没有创建过,onCreate()方法会先于
onStartCommand()方法执行。服务启动了之后会一直保持运行状态,直到stopService()或
stopSelf()方法被调用。注意虽然每调用一次startService()方法,onStartCommand()就会执行
一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,
只需调用一次stopService()或stopSelf()方法,服务就会停止下来了。
另外,还可以调用Context 的bindService()来获取一个服务的持久连接,这时就会回调
服务中的onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于
onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder 对象的实例,这
样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保
持运行状态。
当调用了startService()方法后,又去调用stopService()方法,这时服务中的onDestroy()
方法就会执行,表示服务已经销毁了。类似地,当调用了bindService()方法后,又去调用
unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。但是需要注意,
我们是完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,
这种情况下该如何才能让服务销毁掉呢?根据Android 系统的机制,一个服务只要被启动或
者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被
销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才
会执行。
服务的更多技巧------------------------------------------