安卓学习 之 Service服务(九)
服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。
一、多线程编程
先介绍一下四个组件:
- Message
Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上一小节中我们使用到了 Message 的 what 字段,除此之外还可以使用 arg1 和 arg2 字段来携带一些整型数据,使用 obj 字段携带一个 Object 对象。
- Handler
Handler 顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用 Handler 的 sendMessage()方法,而发出的消息经过一系列地辗转处理后, 最终会传递到 Handler 的 handleMessage()方法中。
- MessageQueue
MessageQueue是消息队列的意思,它主要用于存放所有通过 Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个 MessageQueue 对象。
- Looper
Looper 是每个线程中的 MessageQueue 的管家,调用 Looper 的 loop()方法后,就会进入到一个无限循环当中,然后每当发现 MessageQueue 中存在一条消息,就会将它取出,并传递到 Handler 的 handleMessage()方法中。每个线程中也只会有一个 Looper 对象。
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
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;
}
}
使用 AsyncTask
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();
}
}
}
-
在 doInBackground()方法里去执行具体的下载任务。这个方法里的代码都是在子线程中运行的,因而不会影响到主线程的运行。注意这里虚构了一个doDownload()方法,这个方法用于计算当前的下载进度并返回。
-
调用 publishProgress()方法并将当前的下载进度传进来,这样 onProgressUpdate()方法就会很快被调用,在这里就可以进行 UI 操作了。
-
当下载完成后,doInBackground()方法会返回一个布尔型变量,这样 onPostExecute()方法就会很快被调用,这个方法也是在主线程中运行的。然后在这里我们会根据下载的结果来弹出相应的 Toast 提示,从而完成整个 DownloadTask 任务。
简单来说,使用 AsyncTask 的诀窍就是,在 doInBackground()方法中去执行具体的耗时任务,在 onProgressUpdate()方法中进行 UI 操作,在 onPostExecute()方法中执行一些任务的收尾工作。
二、服务的基本用法
- 新建一个服务继承Service类。
public class MyService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
其中 onCreate()方法会在服务创建的时候调用,onStartCommand()方法会在每次服务启动的时候调用,onDestroy()方法会在服务销毁的时候调用。onBind()方法目前暂时将它忽略掉。
- 在AndroidManifest.xml注册该服务(四大组件都需要注册)
<service android:name=".MyService" >
</service>
- 启动和停止服务
// 启动服务
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent);
// 停止服务
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);
二、活动与服务之间的通信
启动服务之后,活动与服务无关。只是活动通知服务一下:你可以启动了,具体做什么不知道,完成的如何也不知道。 那么如何让活动与服务更紧密一些:那就要用上面的onbind方法了。
目的:在 MyService 里提供一个下载功能,然后在活动中可以决定何时开始下载,以及随时查看下载进度。
- 首先要知道ServiceConnection这个类的onServiceConnected和onServiceDisconnected这两个方法分别会在绑定服务和取消绑定服务的时候回调用。我们写一个匿名内部类。
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
};
可以看到onServiceConnected传进一个Ibinder实例。
- 我们创建一个Ibinder实例
private DownloaderBinder mBinder = new DownloaderBinder();
class DownloaderBinder extends Binder{
public void startDownload(){
Log.d(TAG, "startDownload: ");
}
public int getProgress(){
Log.d(TAG, "getProgress: ");
return 0;
}
}
- 重写ServcieConnection的onServiceConnected方法,让服务和活动连接时产生联系。
public void onServiceConnected(ComponentName name, IBinder service) {
downloaderBinder = (MyService.DownloaderBinder) service;
downloaderBinder.startDownload();
downloaderBinder.getProgress();
}
三、服务的生命周期
有onCreate、onStartCommand、onBind和onDestroy等方法都是在服务生命周期内可能调用的方法。
-
任何位置调用startService方法,服务会启动起来,并回调onStartCommand方法;若服务未曾创建,则onCreate会先于onStartCommand方法;服务启动后会运行,直到onStopService或StopSelf方法被调用;
-
还可以调用bindService来获得服务持久连接,此时回调bind方法,若未创建过,则会先onCreate再onbind,onBind方法会返回IBinder对象实例。自由通信了已经可以。
-
bindService绑定后用unbindService解绑,此时会调用onDestroy方法;startService启动后会调用stopService来结束服务。若同时调用了startService,bindService两个方法,则必须同时调用stopService和unbindservice才可会执行onDestroy方法,因为android机制规定了服务被启动和绑定之后,会一直处于运行状态。
四、服务的更多技巧
- 使用前台服务
前台服务与普通服务最大区别在于:他有一个正在运行的图标在系统状态栏显示,下拉通知栏可看到更加详细,类似通知。比 如:彩云天气,后台更新天气数据,在系统栏一直显示当前天气信息。
与Notifiction比较类似,构建的notification并没有使用NotificationManager显示出来,而是调用startForeGround来使MyService变成一个前台服务,并在系统状态栏显示处出来。
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate executed ");
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("This is contnet title").setContentText("This is contnet Text")
.setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi).build();
startForeground(1,notification);
}
- 使用IntentService
服务默认在主线程中进行,若在服务中处理耗时逻辑,则很容易出现ANR。这是采用多线程编程技术,在服务的每个方法中开启一个子线程去处理耗时逻辑。服务执行完后自动停止,可以使用StopSelf方法。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand executed ");
new Thread(new Runnable() {
@Override
public void run() {
//处理具体逻辑
//服务执行完后自动停止,可以使用StopSelf方法
stopSelf();
}
});
return super.onStartCommand(intent, flags, startId);
}
IntentService主要用于解决程序员忘记开启线程,或者忘记调用stopSelf()方法
public class MyIntentService extends IntentService {
protected void onHandleIntent(Intent intent) {
//处理具体逻辑
}
}