Android 组件 — Service 剖析

简介
 Service 是Android的四大组件之一,当应用程序希望在应用程序空闲的时候去运行一个耗时较长的操作,或者为其他应用提供功能实现的时候可以使用Service来完成。每个service都必须在AndroidManifest.xml中有一个相应的声明,Services 可以通过Context.bindService() 或者Context.startService()来启动Service。
 需要注意的是,Service与其他应用程序的对象(UI Activity...)一样是运行在主进程的main Thread中,这就意味着,如果你的service打算做密集的CPU运算(比如播放媒体文件)或者做阻塞的操作(如联网),都应该在Service中单独开启一个子线程来处理。而IntentService 继承了Service,当你希望Service在一个单独的线程中运行时,IntentService是一个标准的实现。

这篇文章涉及的主题:

  • 什么是Service?
  • Service的生命周期
  • Service 权限的定义与声明
  • 进程的声明周期
  • Local Service 范例
  • Remote Messenger Service 范例

1.什么是Service?

 实际上,大多数的困惑都是在说Service 不是什么:
 1)Service不是一个单独的进程,Service的对象实体并不一定运行在自己特有的进程中,它作为应用程序的一部分与应用程序运行在同一个进程空间。
  如果希望Service运行在自己独立的进程:
  a.可以在AndroidManifest.xml中通过android:process="" 为Service 指定一个进程名称,则Service会重新开启一个进程。
  b.即便是不在AndroidManifest.xml指定android:process="",如果某个应用程序A期望使用其他应用程序B中的Service,那么B的中Service与A 肯定不在同一个进程中。
 2)Service 不是一个线程,这就意味着它并不能脱离主线程(主要是为了避免应用程序没有反应的错误发生)
 
 所以Service实际上非常简单,提供了两个主要的特性:
 1)某个应用程序的功能告诉系统需要将一些事物放在后台运行,只要调用了 Context.startService(),系统就会安排时间让Service在后台处理这些请求,除非你明确的去关闭Service,否则Service不会停止。
 2)如果说应用程序A希望使用应用程序B中的Service完成某件事,需要调用Context.bindService()来建立连接,bindService()允许一种长期的连接来保证B中的Service能正常与它交互。
 当Service这个组件已经创建,之后何时调用onCreate()和onBind()完全取决于Service自己,系统会让Service在合适的时间点去调用这些方法
 需要注意的是,因为Service本身的设计非常简单,你可以按照具体的需求来完善Service:让它成为一个本地的Java对象然后直接调用它的方法(可以参照下文中的 Local Service 范例),也可以通过AIDL提供一个完全远程的接口。
 
2.Service 的生命周期
 启动Service的方式有两种:
 1)Context.startService()
 context.startService()  -> onCreate()  -> onStartCommand(可能多次调用)  -> Service running  -> context.stopService()  -> onDestroy()  -> Service stop
 如果Service尚未运行,Client执行context.startService()时,则调用吮吸onCreate() -> onStartCommand()。
 如果Service已经运行,当Client多次执行context.startService(Intent service)时则只执行onStartCommand(),Client的数据可放在intent中传递到Service。
 无论Client执行多少次context.startService(),Client只需要执行一次context.stopService()就能关闭Service(调用onDestroy())。
 即使多次调用startService(Intent service),但是只要你Intent中启动的是同一个Service,则不会重新建立一个Service。

  •  onStartCommand() 方法是在2.0 以后才引入的,代替了已经不建议使用的onStart()。它提供了和onStart()一样的功能,但是还允许你告诉系统,如果系统在显示调用stopService或stopSelf之前终止了Service,该如何重启Service。
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		startBackgroundTask(intent,startId);
//		return Service.START_STICKY;
//		or
//		return Service.START_NOT_STICKY;
//		or
		return Service.START_REDELIVER_INTENT;
	}

 

 2)Context.bindService()
 context.bindService()  -> onCreate()  -> onBind()(只执行一次)  -> Service running  -> onUnbind()  -> onDestroy()  -> Service stop
 onBind()将返回给客户端一个IBind接口实例,IBind 实际上可以简单理解成某个定义在Service中的类A(这个接口至少需要extend IBind),它是通向Service的入口。当Client 拿到IBind之后可以将其向下转型成类A,然后回调类A中的方法,类A中的方法就可以任意操作Service了。
 bindService()会让Client与Service绑定在一起,当所有连接到Service的Client退出时,Service也会退出。
 Client接口IBind允许客户端回调服务的方法,比如得到Service的实例、运行状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind->onDestroy相应退出。

3.Service 权限的定义与声明
 当Service 在它的AndroidManifest.xml <service> 标签下声明了android:permission="",则其他app在访问该Service的时候必须在各自的AndroidManifest.xml <uses-permission>中声明访问权限,才能去start, stop, or bind service。

 1)权限的定义

 <manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.me.app.myapp" >
    <permission
        android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY"
        android:description="@string/permdesc_deadlyActivity"
        android:label="@string/permlab_deadlyActivity"
        android:permissionGroup="android.permission-group.COST_MONEY"
        android:protectionLevel="dangerous" />
     ... 
</manifest>

 2)权限的声明

    <application>
    ...
        <service
            android:name="PrivService"
            android:permission="com.me.app.myapp.permission.DEADLY_SERVICE" />
    ...
    </application>

 另外,Service也可以通过上述方式来保护它的IPC方法调用,在调用service 的某个方法前可以调用contex.checkCallingPermission()来检查是否拥有执行权限。

4.进程的生命周期
 只要一个进程的Service已经启动或者有客户端绑定到该Service,那么Android 系统会尽可能保证该进程能正常运行。当系统的内存不足需要杀死一些现有的进程来释放资源的时候,Service所在进程的优先级只有在下面几种情况下才会更高一些:

  •  如果Service正在执行onCreate(), onStartCommand(), or onDestroy()这些方法,Service所在的进程的优先级会提高成前台进程,以保证这些代码能正常执行。
  •  如果Service 已经启动,Service所在的进程的重要性只比用户可见的进程低,但是比其他任何不可见的进程的优先级要高。因为很少有进程对用户来说是可见的(比如UI进程是可见的),所以只有内存极端缺乏的时候才会终止Service所在的进程。
  •  一旦有客户端绑定到Service,只要绑定到service中的其中一个客户端变成对用户可见的进程,那么也可以认为service也是可见的。
     一个已经启动的service可以使用startForeground(int, Notification)方法将service切换到前台线程状态,这样当系统内存不足时不会不会终止此service(但是理论上如果在内存极端缺乏的情况下还是会终止service进程)。
  •  需要注意的是,如果你的service长时间运行,当系统出现内存紧张的情况下系统可能终止service的运行。如果发生这样的情况,系统之后会重新启动service。如果你重写了onStartCommand()方法,并在此方法中去新开线程或者异步的执行一些操作,为了避免service在运行的过程中被终止后重启丢失intent数据,你可以在onStartCommand()中return Service.START_FLAG_REDELIVERY,这样在系统重启Service的时候会重新传递intent数据(否则重新启动时inten数据的值为null)。

5.Local Service 范例:

1)界面

package sunmedia.chenjian.localservice;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.widget.Toast;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		doBindService();// 与service建立连接
	}

	private LocalService mBoundService;
	private ServiceConnection mConnection = new ServiceConnection() {
		public void onServiceConnected(ComponentName className, IBinder service) {

			mBoundService = ((LocalService.LocalBinder) service).getService();
			Toast.makeText(MainActivity.this, R.string.local_service_connected,
					Toast.LENGTH_SHORT).show();
		}

		public void onServiceDisconnected(ComponentName className) {
			mBoundService = null;
			Toast.makeText(MainActivity.this,
					R.string.local_service_disconnected, Toast.LENGTH_SHORT)
					.show();
		}
	};

	private boolean mIsBound;

	void doBindService() {
		// 由于事先知道service与client运行在同一进程,所以可以直接指定类名来启动service
		bindService(new Intent(MainActivity.this, LocalService.class),
				mConnection, Context.BIND_AUTO_CREATE);
		mIsBound = true;
	}

	void doUnbindService() {
		if (mIsBound) {
			// 断开连接
			unbindService(mConnection);
			mIsBound = false;
		}
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		doUnbindService();
	}
}

2)Service

package sunmedia.chenjian.localservice;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

public class LocalService extends Service {

	public static final String NAME = "sunmedia.chen.jian.localservice";
	private NotificationManager mNM;
	// Unique Identification Number for the Notification.
	// We use it on Notification start, and to cancel it.
	private int NOTIFICATION = R.string.local_service_started;

	/**
	 * 为client提供访问,因为事先知道service与client运行在同一进程,所以不需要 考虑IPC的影响
	 */
	public class LocalBinder extends Binder {
		LocalService getService() {
			return LocalService.this;
		}
	}

	@Override
	public void onCreate() {
		mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
		// 显示service的启动状态,启动时会在状态现显示一个图标
		showNotification();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.i("LocalService", "Received start id " + startId + ": " + intent);
		// 由于我们希望service一直在运行,除非调用stop终止进程,所以返回值可以设置成
		// START_STICKY
		return START_STICKY;
	}

	@Override
	public void onDestroy() {
		// 取消在状态栏的提示
		mNM.cancel(NOTIFICATION);
		Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT)
				.show();
	}

	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}

	// 用于与client交互,可以理解为service的入口
	private final IBinder mBinder = new LocalBinder();

	/** * Show a notification while this service is running. */
	private void showNotification() {
		// 获取用于状态栏提示的字串
		CharSequence text = getText(R.string.local_service_started);
		// 设置状态栏提示用的参数资源(图片/文字/时间戳)
		Notification notification = new Notification(R.drawable.stat_sample,
				text, System.currentTimeMillis());
		// PendingIntent 预先设置状态被点击时的动作
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
				new Intent(this, Operation.class), 0);
		// 设置状态栏中持续显示时的参数
		notification.setLatestEventInfo(this,
				getText(R.string.local_service_label), text, contentIntent);
		// 发送Notification
		mNM.notify(NOTIFICATION, notification);
	}
}

3)operation

package sunmedia.chenjian.localservice;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;

//用于测试状态栏被点击时的动作
public class Operation extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.operation);
		Toast.makeText(this, "Operation Create", Toast.LENGTH_LONG).show();
	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
	}

}

4)AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="sunmedia.chenjian.localservice"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="17"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="sunmedia.chenjian.localservice.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="sunmedia.chenjian.localservice.Operation"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name="sunmedia.chenjian.localservice.LocalService"
            android:enabled="true"
            android:exported="false" >
            <intent-filter android:name="LocalService" >
            </intent-filter>
        </service>
    </application>

</manifest>

6.Remote Mesager Service范例

1)界面

package sunmedia.chenjian.remoteservice;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mCallbackText = (TextView)findViewById(R.id.TextView);
		doBindService();
	}
	
	/** 用于与Service交互*/
	Messenger mService = null;
	/** 用于表示是否执行过bindService() */
	boolean mIsBound;
	/** 用于显示当前连接的状态 */
	TextView mCallbackText;

	/** 用于处理从Service 接收到的消息*/
	class IncomingHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case MessengerService.MSG_SET_VALUE:
				mCallbackText.setText("Received from service: " + msg.arg1);
				break;
			default:
				super.handleMessage(msg);
			}
		}
	}

	/** 用于发布消息到客户端. */
	final Messenger mMessenger = new Messenger(new IncomingHandler());
	/** 用于指示 Service 连接状态 */
	private ServiceConnection mConnection = new ServiceConnection() {
		/** 当Service建立完后后会调用此方法,并将Service作为参数传递进来,需要注意的是
		 * ServiceConnection中的方法都是在Activity所在的主线程调用的,也就是说其实是客
		 * 户端的主线程不断的轮询Service的状态再调用ServiceConnection中的方法。
		 * 导致不能连接的情况有几种:
		 * 1)Service 没有在AndroidManifest.xml中声明或者启动Service时名称不正确
		 * 2)如果当前Activity所在的主线程被卡主,则无法正常调用此方法
		 * */
		public void onServiceConnected(ComponentName className, IBinder service) {
			mService = new Messenger(service);
			mCallbackText.setText("Attached.");
			//将mMessenger传递到Service方便Service发消息到客户端
			try {
				Message msg = Message.obtain(null,
						MessengerService.MSG_REGISTER_CLIENT);
				msg.replyTo = mMessenger;
				mService.send(msg);
				//传递一个标识到Service,方便其测试连接
				msg = Message.obtain(null, MessengerService.MSG_SET_VALUE,
						this.hashCode(), 0);
				mService.send(msg);
			} catch (RemoteException e) {
				//Service异常终止,可以选择重新连接策略
			}

			Toast.makeText(MainActivity.this,
					R.string.remote_service_connected, Toast.LENGTH_SHORT)
					.show();
		}
		/**
		 * 当Service被异常终止的时,客户端所在的主线程会调用此方法,之后系统会重新启动
		 * Service并再次调用onServiceConnected()。
		 */
		public void onServiceDisconnected(ComponentName className) {
			mService = null;
			mCallbackText.setText("Disconnected.");
			
			Toast.makeText(MainActivity.this,
					R.string.remote_service_disconnected, Toast.LENGTH_SHORT)
					.show();
		}
	};

	void doBindService() {
		Intent intent = new Intent(MessengerService.NAME);
		bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
		mIsBound = true;
		mCallbackText.setText("Binding.");
	}

	void doUnbindService() {
		if (mIsBound) {
			//如果客户端正常退出,则需要注销与Service的连接
			if (mService != null) {
				try {
					Message msg = Message.obtain(null,
							MessengerService.MSG_UNREGISTER_CLIENT);
					msg.replyTo = mMessenger;
					mService.send(msg);
				} catch (RemoteException e) {
					//Service已经异常终止
				}
			}
			unbindService(mConnection);
			mIsBound = false;
			mCallbackText.setText("Unbinding.");
		}
	}
}
2)Service

package sunmedia.chenjian.remoteservice;

import java.util.ArrayList;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.Toast;

public class MessengerService extends Service {
	
	static final String NAME = "MyMessengerService";
	/**用于在状态栏显示通知. */
	NotificationManager mNM;
	/** 用于记录客户端的连接情况*/
	ArrayList<Messenger> mClients = new ArrayList<Messenger>();
	/** 用于存储最后一次连接的客户端标识符 */
	int mValue = 0;
	/**
	 * 此命令用于注册客户端,Message的 replyTo栏位需要放置客户端的Messenger,用于
	 * Service发消息到客户端
	 * */
	static final int MSG_REGISTER_CLIENT = 1;
	/**
	 * 此命令用于注销客户端,Message的 replyTo栏位需要放置客户端的Messenger,用于
	 * 将Messenger从记录中移除。
	 */
	static final int MSG_UNREGISTER_CLIENT = 2;
	/**
	 * 此命令主要用于测试Service与客户端的连接
	 */
	static final int MSG_SET_VALUE = 3;

	/** * Handler of incoming messages from clients. */
	class IncomingHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case MSG_REGISTER_CLIENT:
				mClients.add(msg.replyTo);
				break;
			case MSG_UNREGISTER_CLIENT:
				mClients.remove(msg.replyTo);
				break;
			case MSG_SET_VALUE:
				mValue = msg.arg1;
				for (int i = mClients.size() - 1; i >= 0; i--) {
					try {
						mClients.get(i).send(
								Message.obtain(null, MSG_SET_VALUE, mValue, 0));
					} catch (RemoteException e) {
						//如果发送消息给客户端失败,则认为客户端异常终止,将客户端从
						//记录中移除
						mClients.remove(i);
					}
				}
				break;
			default:
				super.handleMessage(msg);
			}
		}
	}

	/** 用于与客户端之间的信息交互 */
	final Messenger mMessenger = new Messenger(new IncomingHandler());

	@Override
	public void onCreate() {
		mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
		// Display a notification about us starting.
		showNotification();
	}

	@Override
	public void onDestroy() {
		// Cancel the persistent notification.
		mNM.cancel(R.string.remote_service_started);
		// Tell the user we stopped.
		Toast.makeText(this, R.string.remote_service_stopped,
				Toast.LENGTH_SHORT).show();
	}

	/**
	 * 将服务端的Messenger发送给客户端,方便客户端传递消息给Service
	 */
	@Override
	public IBinder onBind(Intent intent) {
		return mMessenger.getBinder();
	}

	/** * Show a notification while this service is running. */
	private void showNotification() {
		// In this sample, we'll use the same text for the ticker and the
		// expanded notification
		CharSequence text = getText(R.string.remote_service_started);

		// Set the icon, scrolling text and timestamp
		Notification notification = new Notification(R.drawable.stat_sample,
				text, System.currentTimeMillis());
		// The PendingIntent to launch our activity if the user selects this
		// notification
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
				new Intent(this, Operation.class), 0);
		// Set the info for the views that show in the notification panel.
		notification.setLatestEventInfo(this,
				getText(R.string.remote_service_label), text, contentIntent);
		// Send the notification.
		// We use a string id because it is a unique number. We use it later
		// to cancel.
		mNM.notify(R.string.remote_service_started, notification);
	}
}
3)AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="sunmedia.chenjian.remoteservice"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="sunmedia.chenjian.remoteservice.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="sunmedia.chenjian.remoteservice.Operation"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name="sunmedia.chenjian.remoteservice.MessengerService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote_service" >
            <intent-filter>
                <action android:name="MyMessengerService" />
            </intent-filter>
        </service>
    </application>

</manifest>


个人原创:转载请注明出处:http://blog.csdn.net/chenjianjk/article/details/9499271

Local Service、Remote Service代码下载地址(中文的编码格式为UTF-8):

http://download.csdn.net/detail/chenjianjk/5826495

http://download.csdn.net/detail/chenjianjk/5826501
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值