四大组件之service

服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使当程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。不过需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。另外,也不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。


线程的基本用法


Android 多线程编程其实并不比 Java 多线程编程特珠,基本都是使用相同的语法。比如说,定义一个线程只需要新建一个类继承自 Thread,然后重写父类的 run()方法,并在
里面编写耗时逻辑即可

class MyThread extends Thread {
   @Override
      public void run() {
      // 处理具体的逻辑
}
}
只需要 new 出 MyThread 的实例,然后调用它的 start()方法,这样 run()方法中的代码就会在子线程当中运行了
new MyThread().start();

用继承的方式耦合性有点高,更多的时候我们都会选择使用实现 Runnable接口的方式来定义一个线程

class MyThread implements Runnable {
  @Override
     public void run() {
     // 处理具体的逻辑
}
}
MyThread myThread = new MyThread();
new Thread(myThread).start();
可以看到, Thread 的构造函数接收一个 Runnable 参数, 而我们 new 出的 MyThread正是一个实现了 Runnable 接口的对象,所以可以直接将它传入到 Thread 的构造函数里。接着调用 Thread 的 start()方法,run()方法中的代码就会在子线程当中运行了如果你不想专门再定义一个类去实现 Runnable 接口, 也可以使用匿名类的方式,这种写法更为常见


   new Thread(new Runnable() {
    @Override
      public void run() {
        // 处理具体的逻辑
    }
 }).start();

在子线程中更新 UI

Android 的 UI 也是线程不安全的。也就是说,如果想要更新应用程序里的 UI 元素,则必须在主线程中进行,否则就会出现异常


package com.example.service;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener{

	public static final int UPDATE_TEXT=1;
	TextView txt;
	Button btn;
	private Handler handler=new Handler(){
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case UPDATE_TEXT:
				txt.setText("Nice");
				break;

			default:
				break;
			}
		};
	};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        
        txt=(TextView) findViewById(R.id.textView1);
        btn=(Button) findViewById(R.id.button1);
        
        btn.setOnClickListener(this);
    }

	@Override
	public void onClick(View arg0) {
		// TODO Auto-generated method stub
		switch (arg0.getId()) {
		case R.id.button1:
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					// TODO Auto-generated method stub
					Message message=new Message();
					message.what=UPDATE_TEXT;
					handler.sendMessage(message);
				}
			}).start();
			break;

		default:
			break;
		}
	}
    
}

这里我们先是定义了一个整型常量 UPDATE_TEXT,用于表示更新 TextView 这个动作。然后新增一个 Handler 对象,并重写父类的 handleMessage 方法,在这里对具体的
Message 进行处理。如果发现 Message 的 what 字段的值等于 UPDATE_TEXT,就将TextView 显示的内容改成 Nice to meet you。下面再来看一下 Change Text 按钮的点击事件中的代码。可以看到,这次我们并没有在子线程里直接进行 UI 操作,而是创建了一个 Message(android.os.Message)对象,并将它的 what 字段的值指定为 UPDATE_TEXT,然后调用 Handler 的 sendMessage()方法将这条 Message 发送出去。很快,Handler 就会收到这条 Message,并在handleMessage()方法中对它进行处理。 注意此时 handleMessage()方法中的代码就是在主线程当中运行的了,所以我们可以放心地在这里进行 UI 操作。接下来对 Message 携带的 what 字段的值进行判断,如果等于 UPDATE_TEXT,就将 TextView 显示的内容改成Nice to meet you。


解析异步消息处理机制

Android 中 的 异 步 消 息 处 理 主 要 由 四 个 部 分 组 成 , 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 对象。了解了 Message、Handler、MessageQueue 以及 Looper 的基本概念后,我们再来对异步消息处理的整个流程梳理一遍。首先需要在主线程当中创建一个 Handler 对象,并重写 handleMessage()方法。 然后当子线程中需要进行 UI 操作时, 就创建一个 Message对象,并通过 Handler 将这条消息发送出去。之后这条消息会被添加到 MessageQueue的队列中等待被处理, 而 Looper 则会一直尝试从 MessageQueue 中取出待处理消息, 最后分发回 Handler 的 handleMessage()方法中。由于 Handler 是在主线程中创建的,所以此时 handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行 UI 操作了。整个异步消息处理机制的流程示意图如图



使用 AsyncTask

首先来看一下 AsyncTask 的基本用法,由于 AsyncTask 是一个抽象类,所以如果我们想使用它,就必须要创建一个子类去继承它。在继承时我们可以为 AsyncTask 类指定三个泛型参数,

1.  Params

在执行 AsyncTask 时需要传入的参数,可用于在后台任务中使用。
2.  Progress
后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。

3.  Result
当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。因此,一个最简单的自定义 AsyncTask 就可以写成如下方


class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
⋯⋯
}

这里我们把 AsyncTask 的第一个泛型参数指定为 Void,表示在执行 AsyncTask 的时候不需要传入参数给后台任务。第二个泛型参数指定为 Integer,表示使用整型数据来作
为进度显示单位。第三个泛型参数指定为 Boolean,则表示使用布尔型数据来反馈执行结果。当然,目前我们自定义的 DownloadTask 还是一个空任务,并不能进行任何实际的操作,我们还需要去重写 AsyncTask 中的几个方法才能完成对任务的定制。经常需要去重写的方法有以下四个。


1.  onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
2.  doInBackground(Params...)
这个方法中的所有代码都会在子线程中运行, 我们应该在这里去处理所有的耗时任务。 任务一旦完成就可以通过 return 语句来将任务的执行结果返回, 如果 AsyncTask
的第三个泛型参数指定的是 Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行 UI 操作的,如果需要更新 UI 元素,比如说反馈当前任务的执行进度,可
以调用 publishProgress(Progress...)方法来完成。
3.  onProgressUpdate(Progress...)
当在后台任务中调用了 publishProgress(Progress...)方法后, 这个方法就会很快被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对 UI 进
行操作,利用参数中的数值就可以对界面元素进行相应地更新。
4.  onPostExecute(Result)
当后台任务执行完毕并通过 return 语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些 UI 操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。因此,一个比较完整的自定义 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();
   }
 }

在这个 DownloadTask 中, 我们在 doInBackground()方法里去执行具体的下载任务。这个方法里的代码都是在子线程中运行的,因而不会影响到主线程的运行。注意这里虚构了一个 doDownload()方法,这个方法用于计算当前的下载进度并返回,我们假设这个方法已经存在了。在得到了当前的下载进度后,下面就该考虑如何把它显示到界面上了,由于doInBackground()方法是在子线程中运行的,在这里肯定不能进行 UI 操作,所以我们可以调用 publishProgress()方法并将当前的下载进度传进来,这样 onProgressUpdate()方法就会很快被调用,在这里就可以进行 UI 操作了。当 下 载 完 成 后 , doInBackground() 方 法 会 返 回 一 个 布 尔 型 变 量 , 这 样onPostExecute()方法就会很快被调用, 这个方法也是在主线程中运行的。 然后在这里我们会根据下载的结果来弹出相应的 Toast 提示,从而完成整个 DownloadTask 任务。简单来说,使用AsyncTask 的诀窍就是,在 doInBackground()方法中去执行具体的耗时任务, 在 onProgressUpdate()方法中进行 UI 操作, 在 onPostExecute()方法中执行一些任务的收尾工作。如果想要启动这个任务,只需编写以下代码即可
new DownloadTask().execute();

定义一个服务

public class MyService extends Service {
  @Override
   public IBinder onBind(Intent intent) {
   return null;
  }
}

然是定义一个服务,自然应该在服务中去处理一些事情了,那处理事情的逻辑应该写在哪里呢?这时就可以重写 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()这三个方法,它们是每个服务中最常用到的三个方法了。其中 onCreate()方法会在服务创建的
时候调用,onStartCommand()方法会在每次服务启动的时候调用,onDestroy()方法会在服务销毁的时候调用。通常情况下,如果我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在onStartCommand()方法里。而当服务销毁时,我们又应该在 onDestroy()方法中去回收那些不再使用的资源。另外需要注意,每一个服务都需要在 AndroidManifest.xml 文件中进行注册才能生效,不知道你有没有发现,这是 Android 四大组件共有的特点

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.servicetest"
  android:versionCode="1"
  android:versionName="1.0" >
⋯⋯
 <application
   android:allowBackup="true"
   android:icon="@drawable/ic_launcher"
   android:label="@string/app_name"
   android:theme="@style/AppTheme" >
⋯⋯
  <service android:name=".MyService" >
  </service>
  </application>
  </manifest>


启动和停止服务


public class MainActivity extends Activity implements OnClickListener{

	
	Button btn1,btn2;
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        
        btn1=(Button) findViewById(R.id.button1);
        btn2=(Button) findViewById(R.id.button2);
        
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

	@Override
	public void onClick(View arg0) {
		// TODO Auto-generated method stub
		switch (arg0.getId()) {
		case R.id.button1:
			Intent start=new Intent(this,MyService.class);
			startService(start);
			break;
		case R.id.button2:
			Intent stop=new Intent(this,MyService.class);
			stopService(stop);
			break;
		default:
			break;
		}
	}
    
可以看到,这里在 onCreate()方法中分别获取到了 Start Service 按钮和 StopService 按钮的实例, 并给它们注册了点击事件。 然后在 Start Service 按钮的点击事件里,
我们构建出了一个 Intent 对象,并调用 startService()方法来启动 MyService 这个服务。在 Stop Serivce 按钮的点击事件里,我们同样构建出了一个 Intent 对象,并调用
stopService()方法来停止 MyService 这个服务。startService()和 stopService()方法都是定义在 Context 类中的,所以我们在活动里可以直接调用这两个方法。注意,这里完全
是由活动来决定服务何时停止的,如果没有点击 Stop Service 按钮,服务就会一直处于运行状态。那服务有没有什么办法让自已停止下来呢?当然可以,只需要在 MyService 的任何一个位置调用 stopSelf()方法就能让这个服务停止下来了。



package com.example.servicex;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {

	@Override
	public IBinder onBind(Intent arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		Log.i("MyService","oncreate executed");
		super.onCreate();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		Log.i("MyService","onStartCommand executed");
		return super.onStartCommand(intent, flags, startId);
		
	}

	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		Log.i("MyService","onDestroy executed");
		super.onDestroy();
	}
}





点击一下 Start Service 按钮




MyService 中的 onCreate()和 onStartCommand()方法都执行了,说明这个服务确
实已经启动成功


然后再点击一下 Stop Service 按钮


由此证明,MyService 确实已经成功停止下来了


活动和服务进行通信

比如说目前我们希望在 MyService 里提供一个下载功能,然后在活动中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的 Binder 对象来
对下载功能进行管理,修改 MyService 中的代码


	
	private DownloadBinder mBinder=new DownloadBinder();
	

	class DownloadBinder extends Binder{
	
		public void startDownload(){
			Log.i("MyService","startDownload executed");
		}
		public int getProgress(){
			Log.i("MyService","getProgress executed");
			return 0;
			
		}

	}
	@Override
	public IBinder onBind(Intent arg0) {
		// TODO Auto-generated method stub
		return null;
	}

public class MainActivity extends Activity implements OnClickListener{

	
	Button btn1,btn2;
	
	private MyService.DownloadBinder downloadBinder;
	
	private ServiceConnection connection=new ServiceConnection() {
		
		@Override
		public void onServiceDisconnected(ComponentName arg0) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onServiceConnected(ComponentName arg0, IBinder arg1) {
			// TODO Auto-generated method stub
			downloadBinder=(MyService.DownloadBinder)arg1;
			downloadBinder.startDownload();
			downloadBinder.getProgress();
		}
	};
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        
        btn1=(Button) findViewById(R.id.button1);
        btn2=(Button) findViewById(R.id.button2);
        
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

	@Override
	public void onClick(View arg0) {
		// TODO Auto-generated method stub
		switch (arg0.getId()) {
		case R.id.button1:
			Intent bind=new Intent(this,MyService.class);
			bindService(bind, connection, BIND_AUTO_CREATE);
			break;
		case R.id.button2:
			unbindService(connection);
			break;
		default:
			break;
		}
	}
    


可以看到,这里我们首先创建了一个 ServiceConnection 的匿名类,在里面重写了onServiceConnected()方法和 onServiceDisconnected()方法,这两个方法分别会在活
动与服务成功绑定以及解除绑定的时候调用。在 onServiceConnected()方法中,我们又通过向下转型得到了 DownloadBinder 的实例,有了这个实例,活动和服务之间的关系就变得非常紧密了。现在我们可以在活动中根据具体的场景来调用 DownloadBinder 中的任何 public 方法,即实现了指挥服务干什么,服务就去干什么的功能。这里仍然只是做了个简 单 的 测 试 , 在 onServiceConnected() 方 法 中 调 用 了 DownloadBinder 的startDownload()和 getProgress()方法。当然,现在活动和服务其实还没进行绑定呢,这个功能是在 Bind Service 按钮的点击事件里完成的。可以看到,这里我们仍然是构建出了一个 Intent 对象,然后调用bindService()方法将 MainActivity 和 MyService 进行绑定。 bindService()方法接收三个参数,第一个参数就是刚刚构建出的 Intent 对象,第二个参数是前面创建出的ServiceConnection 的 实 例 , 第 三 个 参 数 则 是 一 个 标 志 位 , 这 里 传 入BIND_AUTO_CREATE 表示在活动和服务进行绑定后自动创建服务。 这会使得 MyService中的 onCreate()方法得到执行,但 onStartCommand()方法不会执行。然后如果我们想解除活动和服务之间的绑定该怎么办呢?调用一下 unbindService()方法就可以了,这也是 Unbind Service 按钮的点击事件里实现的功能



点击一下 Bind Service 按钮




可以看到, 首先是 MyService 的 onCreate()方法得到了执行, 然后 startDownload()和 getProgress()方法都得到了执行, 说明我们确实已经在活动里成功调用了服务里提供的方法了。另外需要注意,任何一个服务在整个应用程序范围内都是通用的,即 MyService 不仅可以和 MainActivity 绑定,还可以和任何一个其他的活动进行绑定,而且在绑定完成后它们都可以获取到相同的 DownloadBinder 实例

服务的生命周期

onCreate()、onStartCommand()、onBind()和 onDestroy()等方法都是在服务的生命周期内可能回调的方法。一旦在项目的任何位置调用了 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()方法才会执行。这样你就已经把服务的生命周期完整地走了一遍


使用前台服务



public class MyService extends Service {
⋯⋯
@Override
public void onCreate() {
super.onCreate();
   Notification notification = new Notification(R.drawable.ic_launcher,"Notification comes", System. currentTimeMillis());
   Intent notificationIntent = new Intent(this, MainActivity.class);
   PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
   notificationIntent, 0);
   notification.setLatestEventInfo(this, "This is title", "This is content",<span style="font-family: Arial, Helvetica, sans-serif;">pendingIntent);</span>
   startForeground(1, notification);
   Log.d("MyService", "onCreate executed");
 }
⋯⋯
 }

以看到,这里只是修改了 onCreate()方法中的代码,相信这部分的代码你会非常眼熟。 没错! 这就是我们在上一章中学习的创建通知的方法。 只不过这次在构建出 Notification对 象 后 并 没 有 使 用 NotificationManager 来 将 通 知 显 示 出 来 , 而 是 调 用 了startForeground()方法。 这个方法接收两个参数, 第一个参数是通知的 id, 类似于 notify()方法的第一个参数, 第二个参数则是构建出的 Notification 对象。 调用 startForeground()方法后就会让 MyService 变成一个前台服务,并在系统状态栏显示出来现在重新运行一下程序,并点击 Start Service 或 Bind Service 按钮,MyService 就会以前台服务的模式启动了,并且在系统状态栏会显示一个通知图标,下拉状态栏后可以看到该通知的详细内容

使用 IntentService


话说回来,在本章一开始的时候我们就已经知道,服务中的代码都是默认运行在主线程
当中的,如果直接在服务里去处理一些耗时的逻辑,就很容易出现 ANR(Application Not
Responding)的情况。
所以这个时候就需要用到 Android 多线程编程的技术了,我们应该在服务的每个具体
的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。因此,一个比较标准的服务
就可以写成如下形式


public class MyService extends Service {
  @Override
 public IBinder onBind(Intent intent) {
   return null;
  }
  @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
  new Thread(new Runnable() {
  @Override
   public void run() {
  // 处理具体的逻辑
  }
  }).start();
    return super.onStartCommand(intent, flags, startId);
  }
}
但是,这种服务一旦启动之后,就会一直处于运行状态,必须调用 stopService()或者stopSelf()方法才能让服务停止下来。所以,如果想要实现让一个服务在执行完毕后自动停止的功能,就可以这样写

public class MyService extends Service {
 @Override
 public IBinder onBind(Intent intent) {
  return null;
  }
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
  new Thread(new Runnable() {
  @Override
  public void run() {
  // 处理具体的逻辑
   stopSelf();
  }
  }).start();
 return super.onStartCommand(intent, flags, startId);
  }
}
虽说这种写法并不复杂, 但是总会有一些程序员忘记开启线程, 或者忘记调用 stopSelf()方法。为了可以简单地创建一个异步的、会自动停止的服务,Android 专门提供了一个
IntentService 类,这个类就很好地解决了前面所提到的两种尴尬,下面我们就来看一下它的用法虽说这种写法并不复杂, 但是总会有一些程序员忘记开启线程, 或者忘记调用 stopSelf()方法。为了可以简单地创建一个异步的、会自动停止的服务,Android 专门提供了一个IntentService 类,这个类就很好地解决了前面所提到的两种尴尬,下面我们就来看一下它的用法


public class MyIntentService extends IntentService {
  public MyIntentService() {
   super("MyIntentService"); // 调用父类的有参构造函数
  }
    @Override
   protected void onHandleIntent(Intent intent) {
  // 打印当前线程的id
    Log.d("MyIntentService", "Thread id is " + Thread.currentThread(). getId());
  }
  @Override
  public void onDestroy() {
 super.onDestroy();
  Log.d("MyIntentService", "onDestroy executed");
 }
<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_intent_service"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="Start IntentService" />
 </LinearLayout>

public class MainActivity extends Activity implements OnClickListener {
⋯⋯
  private Button startIntentService;
  @Override
   protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
 ⋯⋯
   startIntentService = (Button) findViewById(R.id.start_intent_service);
   startIntentService.setOnClickListener(this);
 }
  @Override
  public void onClick(View v) {
  switch (v.getId()) {
⋯⋯
  case R.id.start_intent_service:
// 打印主线程的id
  Log.d("MainActivity", "Thread id is " + Thread.currentThread(). getId());
  Intent intentService = new Intent(this, MyIntentService.class);
  startService(intentService);
  break;
  default:
  break;
 }

可以看到, 我们在 Start IntentService 按钮的点击事件里面去启动 MyIntentService这个服务,并在这里打印了一下主线程的 id,稍后用于和 IntentService 进行比对。你会发
现,其实 IntentService 的用法和普通的服务没什么两样。最后仍然不要忘记,服务都是需要在 AndroidManifest.xml 里注册的


后台执行的定时任务

Android 中的定时任务一般有两种实现方式,一种是使用 Java API 里提供的 Timer类,一种是使用 Android 的 Alarm 机制。这两种方式在多数情况下都能实现类似的效果,
但 Timer 有一个明显的短板,它并不太适用于那些需要长期在后台运行的定时任务。我们都知道,为了能让电池更加耐用,每种手机都会有自己的休眠策略,Android 手机就会在长时间不操作的情况下自动让 CPU 进入到睡眠状态, 这就有可能导致 Timer 中的定时任务无法正常运行。而 Alarm 机制则不存在这种情况,它具有唤醒 CPU 的功能,即以保证每次需要执行定时任务的时候 CPU 都能正常工作。需要注意,这里唤醒 CPU 和唤醒屏幕完全不是同一个概念,千万不要产生混淆。那么首先我们来看一下 Alarm 机制的用法吧,其实并不复杂,主要就是借助了AlarmManager 类来实现的。这个类和 NotificationManager 有点类似,都是通过调用Context 的 getSystemService()方法来获取实例的,只是这里需要传入的参数是Context.ALARM_SERVICE。因此,获取一个 AlarmManager 的实例就可以写成:

AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
接下来调用 AlarmManager 的 set()方法就可以设置一个定时任务了,比如说想要设定一个任务在 10 秒钟后执行,就可以写成:
long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent);
上面的两行代码你不一定能看得明白,因为 set()方法中需要传入的三个参数稍微有点复杂, 下面我们就来仔细地分析一下。 第一个参数是一个整型参数, 用于指定 AlarmManager的 工 作 类 型 , 有 四 种 值 可 选 , 分 别 是 ELAPSED_REALTIME 、ELAPSED_REALTIME_WAKEUP、 RTC 和 RTC_WAKEUP。 其中 ELAPSED_REALTIME表 示 让 定 时 任 务 的 触 发 时 间 从 系 统 开 机 开 始 算 起 , 但 不 会 唤 醒 CPU 。ELAPSED_REALTIME_WAKEUP 同样表示让定时任务的触发时间从系统开机开始算起,但会唤醒 CPU。RTC 表示让定时任务的触发时间从 1970 年 1 月 1 日 0 点开始算起,但不会唤醒 CPU。RTC_WAKEUP 同样表示让定时任务的触发时间从 1970 年 1 月 1 日 0点开始算起, 但会唤醒 CPU。 使用 SystemClock.elapsedRealtime()方法可以获取到系统开机至今所经历时间的毫秒数,使用 System.currentTimeMillis()方法可以获取到 1970年 1 月 1 日 0 点至今所经历时间的毫秒数。然后看一下第二个参数,这个参数就好理解多了,就是定时任务触发的时间,以毫秒为单 位 。 如 果 第 一 个 参 数 使 用 的 是 ELAPSED_REALTIME 或ELAPSED_REALTIME_WAKEUP,则这里传入开机至今的时间再加上延迟执行的时间。果第一个参数使用的是 RTC 或 RTC_WAKEUP,则这里传入 1970 年 1 月 1 日 0 点至今的时间再加上延迟执行的时间。

第三个参数是一个 PendingIntent, 对于它你应该已经不会陌生了吧。 这里我们一般会调用 getBroadcast()方法来获取一个能够执行广播的 PendingIntent。这样当定时任务被
触发的时候,广播接收器的 onReceive()方法就可以得到执行。了解了 set()方法的每个参数之后,你应该能想到,设定一个任务在 10 秒钟后执行还可以写成:
long triggerAtTime = System.currentTimeMillis() + 10 * 1000;
manager.set(AlarmManager.RTC_WAKEUP, triggerAtTime, pendingIntent);
好了,现在你已经掌握 Alarm 机制的基本用法,下面我们就来创建一个可以长期在后台 执 行 定 时 任 务 的 服 务 。 创 建 一 个 ServiceBestPractice 项 目 , 然 后 新 增 一 个
LongRunningService 类

public class LongRunningService extends Service {
 @Override
  public IBinder onBind(Intent intent) {
 return null;
 }
 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
 new Thread(new Runnable() {
 @Override
  public void run() {
 Log.d("LongRunningService", "executed at " + new Date(). toString());
 }
 }).start();
  AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
  int anHour = 60 * 60 * 1000; // 这是一小时的毫秒数
  long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
  Intent i = new Intent(this, AlarmReceiver.class);
  PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
  manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
  return super.onStartCommand(intent, flags, startId);
 }
}

我们在 onStartCommand()方法里开启了一个子线程,然后在子线程里就可以执行具体的逻辑操作了。这里简单起见,只是打印了一下当前的时间。
创建线程之后的代码就是我们刚刚讲解的 Alarm 机制的用法了,先是获取到了AlarmManager 的实例,然后定义任务的触发时间为一小时后,再使用 PendingIntent 指定处理定时任务的广播接收器为 AlarmReceiver,最后调用 set()方法完成设定。显然,AlarmReceiver 目前还不存在呢,所以下一步就是要新建一个 AlarmReceiver类,并让它继承自 BroadcastReceiver

public class AlarmReceiver extends BroadcastReceiver {
@Override
 public void onReceive(Context context, Intent intent) {
   Intent i = new Intent(context, LongRunningService.class);
   context.startService(i);
 }
}

onReceive()方法里的代码非常简单,就是构建出了一个 Intent 对象,然后去启动LongRunningService 这个服务。那么这里为什么要这样写呢?其实在不知不觉中,这就
已经将一个长期在后台定时运行的服务完成了。因为一旦启动 LongRunningService,就会在 onStartCommand()方法里设定一个定时任务,这样一小时后 AlarmReceiver 的
onReceive()方法就将得到执行,然后我们在这里再次启动 LongRunningService,这样就形成了一个永久的循环,保证 LongRunningService 可以每隔一小时就会启动一次,一个长期在后台定时运行的服务自然也就完成了。接 下 来 的 任 务 也 很 明 确 了 , 就 是 我 们 需 要 在 打 开 程 序 的 时 候 启 动 一 次LongRunningService, 之后 LongRunningService 就可以一直运行了。 修改 MainActivity中的代码

public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, LongRunningService.class);
startService(intent);
}
}


我们所用到的服务和广播接收器都要在 AndroidManifest.xml 中注册才行


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值