Android创建Service后台常驻服务并使用Broadcast通信

本文介绍如何使用Android Service实现后台任务处理,并通过Broadcast在Activity和服务间进行通信。Service使用startService方式启动,确保长时间运行,而Broadcast实现了Activity与Service间的简单有效通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、概述

1.1 背景

一直想玩智能小车,考虑了手机作为上位机的角色,所以还是重拾一下Android。花了半天时间搭建环境eclipse + Java + Android SDK,又花了大半天时间查了查资料,最终把这份代码给整理出来,与大家分享一下。

1.2 需求

需求非常简单:用户点击按钮,APP获得事件进行处理并反馈,处理的事件为后续再扩展。

需求分解为:
1)界面启动(Activity),监听用户点击按钮的事件;
2)界面启动后运行后台服务(Service),Service负责处理复杂逻辑事件(后续扩展);
3)后台服务不随界面退出而退出;
4)后台服务主线程执行不耗时的逻辑,子线程执行耗时操作(阻塞的操作)。

2、知识点

2.1 service

概念:Service是Android程序中四大基础组件之一,它和Activity一样都是Context的子类,只不过它没有UI界面,是在后台运行的组件。其它应用的组件可以启动一个服务运行于后台,即使用户切换到另一个应用也会继续运行[1]。

使用方法:Service对象不能自己启动,需要通过某个Activity、Service或者其他Context对象来启动。文章[2] 详细地讲述Service有三种启动方法,以下为摘要:

1) context.startService() 启动流程(后台处理工作)
只能实现启动和停止服务使用Intent进行数据传递,通过服务中的onStartCommand方法进行接受
生命周期:context.startService() -> onCreate() -> onStartCommand() -> Service running ->
     context.stopService() -> onDestroy() -> Service stop
2) context.bindService() 启动流程(在本地同一进程内与Activity交互),单向交互
生命周期:context.bindService() -> onCreate() -> onBind() -> Service running ->
     onUnbind() -> onDestroy() -> Service stop
3) 使用AIDL方式的Service(进行跨进程通信),双向交互
高级:跨进程通信时使用。


2.2 Broadcast

老罗书中提到在Android系统中,广播(Broadcast)是在组件之间传播数据(Intent)的一种机制;这些组件甚至是可以位于不同的进程中,这样它就像Binder机制一样,起到进程间通信的作用
文章[3]给出使用方法 以及Demo:
1) 首先在需要发送信息的地方,把要发送的信息和用于过滤的信息(如Action、Category)装入一个Intent对象,然后通过调用 Context.sendBroadcast()、sendOrderBroadcast()或sendStickyBroadcast()方法,把 Intent对象以广播方式发送出去。
2) 当Intent发送以后,所有已经注册的BroadcastReceiver会检查注册时的IntentFilter是否与发送的Intent相匹配,若匹配则就会调用BroadcastReceiver的onReceive()方法。
另外,广播注册的方式上又分为静态与动态的方式,下面的例子将使用动态注册的广播完成Activity 与 Service间通信的方法。

3、code

code阶段最大的帮助就是season同学的文章[4],这也是后续智能小车的思路,而回到本次主题《Android创建Service后台常驻服务并使用Broadcast通信》,所以做了一个精简的版本。下面对代码进行关键点的说明。

3.1 Service

使用的是startService的方式,回顾一下生命周期:  context.startService() -> onCreate() -> onStartCommand() -> Service running -> context.stopService() -> onDestroy() -> Service stop
首先在 onStartCommand方法里面动态注册广播,广播将用于接收Activity发送过来的命令。
/* Called when Activity startService */
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
		
	cmdReceiver = new CommandReceiver();
	IntentFilter filter = new IntentFilter();
	filter.addAction("android.intent.action.cmdservice");
	registerReceiver(cmdReceiver, filter);
		
	myStartService();
	return super.onStartCommand(intent, flags, startId);
}
然后进入myStartService()准备开启子线程,随后的耗时操作将放置到子线程中,如串口的数据读取。
/*
 * Called by onStartCommand, initialize and start runtime thread 
 */
private void myStartService() {
	//TODO initialize device
	threadFlag = true;
	mThread = new MyThread();
	mThread.start();
}
线程类,threadFlag控制enable,现暂时放置的“耗时操作”由showToast消息提示客串呵呵。
/*
 * Thread runtime
 */
public class MyThread extends Thread {        
	@Override
	public void run() {
		super.run();
		// TODO runtime
		while( threadFlag ) {
			
			Log.d(TAG, "Thread pulse");
			showToast("Service Thread pulse");
			try{
				Thread.sleep(10000);
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}	
}
showToast其实就是发送广播给Activity,告知其在界面上显示消息内容
/*
 * Tell Activity to show message on screen
 */
public void showToast(String str) {
	Intent intent = new Intent();
	intent.putExtra("cmd", CMD_SHOW_TOAST);
	intent.putExtra("str", str);
	intent.setAction("android.intent.action.cmdactivity");
	sendBroadcast(intent); 	
}
同时考虑一下,Service在主线程中也是得需要接收来着Activity的命令,这时就看看CommandReceiver这个类了。
广播数据中定义了两个变量,cmd 和 value, cmd表示系统类型, value则是具体操作的数据,这时myHandlerData就可以自己发挥了,如发送串口命令到下位机...
/*
 * BroadcastReceiver for Activity
 */
private class CommandReceiver extends BroadcastReceiver{
	@Override
	public void onReceive(Context context, Intent intent) {
		if ( intent.getAction().equals("android.intent.action.cmdservice") ){
			int cmd = intent.getIntExtra("cmd", -1);
			int value = intent.getIntExtra("value", -1);
			if ( cmd == CMD_STOP_SERVICE ) {
				myStopService();
			}
			else if ( cmd == CMD_SEND_DATA ) {
				myHandlerData(value);
			}
		}
	}
}
然后就是Service生命周期的结束,取消广播的注册,结束进程。
其实这里也有疑问点:如何才能触发onDestroy?Activity显然是不能关闭他了。
@Override
public void onDestroy() {
	Log.i(TAG, "onDestroy()");
	super.onDestroy();
	this.unregisterReceiver(cmdReceiver);
	threadFlag = false;
	boolean retry = true;
	while ( retry ) {
		try {
			retry = false;
			mThread.join();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

3.2 Activity

主进程通过startService(mIntent) 来开启服务,当然也定义了4个按钮来测试发送命令
@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	
	mBtn1 = (Button)findViewById(R.id.button1);
	mBtn1.setTag(1);
	mBtn1.setOnClickListener(new mButtonSendClickListener());
	
	mBtn2 = (Button)findViewById(R.id.button2);
	mBtn2.setTag(2);
	mBtn2.setOnClickListener(new mButtonSendClickListener());
	
	mBtn3 = (Button)findViewById(R.id.button3);
	mBtn3.setTag(3);
	mBtn3.setOnClickListener(new mButtonSendClickListener());
	
	mBtn4 = (Button)findViewById(R.id.button4);
	mBtn4.setTag(4);
	mBtn4.setOnClickListener(new mButtonSendClickListener());
	
	mIntent = new Intent(MainActivity.this, MyService.class); 
	startService(mIntent);
	Log.i(TAG, "startService");
}
然后咱们先来看看发送命令到Service这一部分,先是监听点击事件,然后对应不同按钮的tag值发送不同的广播内容。
到这里仍然不清楚广播用法的话你可以参照一下文章[4]的Demo。
这里面用多个按钮共用一个监听进行处理的方式[6]。
/*
 * Send message to service
 */
public void mSendBroadcast(int cmd, int value) {
	Intent intent = new Intent();
	intent.setAction("android.intent.action.cmdservice");
	intent.putExtra("cmd", cmd);
	intent.putExtra("value", value); 
	sendBroadcast(intent);
	Log.d(TAG, "sendBroadcast: " + CMD_SEND_DATA + " " + value);
}
	
/*
 * Handle click event
 */
public class mButtonSendClickListener implements OnClickListener{
	@Override
	public void onClick(View v) {
		// send broadcast
		int cmd = CMD_SEND_DATA;
		int value = (Integer) v.getTag();
		mSendBroadcast(cmd, value);
	}
}
/*
 * Send message to service
 */
public void mSendBroadcast(int cmd, int value) {
<span style="white-space:pre">	</span>Intent intent = new Intent();
<span style="white-space:pre">	</span>intent.setAction("android.intent.action.cmdservice");
<span style="white-space:pre">	</span>intent.putExtra("cmd", cmd);
<span style="white-space:pre">	</span>intent.putExtra("value", value); 
<span style="white-space:pre">	</span>sendBroadcast(intent);
<span style="white-space:pre">	</span>Log.d(TAG, "sendBroadcast: " + CMD_SEND_DATA + " " + value);
}
另外不要忘了等待Service的消息回馈,先注册一个广播
@Override
protected void onResume() {
	super.onResume();
	Log.i(TAG, "onResume");
	mReceiver = new MyReceiver();
	IntentFilter mFilter=new IntentFilter();
	mFilter.addAction("android.intent.action.cmdactivity");
	MainActivity.this.registerReceiver(mReceiver, mFilter);
}
监听方法,主要就是对接收到Service的字符串进行显示
/*
 * BroadcastReceiver for Service
 */
public class MyReceiver extends BroadcastReceiver {
	@Override
	public void onReceive(Context context, Intent intent) {
		if(intent.getAction().equals("android.intent.action.cmdactivity")){
			Bundle bundle = intent.getExtras();
			int cmd = bundle.getInt("cmd");
			
			if(cmd == CMD_SHOW_TOAST){
			<span style="white-space:pre">	</span>String str = bundle.getString("str");
			<span style="white-space:pre">	</span>myShowToast(str);
			}
			else if(cmd == CMD_SYSTEM_EXIT){
				System.exit(0);
			}
		}
	}
}
/*
 * Show message on screen
 */
public void myShowToast(String str) {
<span style="white-space:pre">	</span>Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();<span style="white-space:pre">	</span>
}

4、结束

界面的效果图如下,这次先简单地将框架准备好,方便下次进行扩展。


Broadcast主要就是将数据插入到Intent对象中进行发送、接收。
这次使用startService将启动在后台服务一直执行,后续检查是否会造成CPU浪费,是否要改回Bind的方式。

工程下载地址:http://download.youkuaiyun.com/detail/stayneckwind2/8610921

参考文章:
[1] Android Service 详解, http://www.cnblogs.com/mengdd/archive/2013/03/24/2979944.html
[2] 浅谈Service, http://www.2cto.com/kf/201504/390385.html
[3] Android之Broadcast, BroadcastReceiver , http://www.cnblogs.com/playing/archive/2011/03/23/1992030.html
[4] Android 与Arduino蓝牙串口的通信设计, http://blog.youkuaiyun.com/cen616899547/article/details/6728040
[5] Broadcast简单实例, http://blog.youkuaiyun.com/xyylchq/article/details/6824992
[6] Android多个按钮的处理方法,  http://blog.youkuaiyun.com/woshixuye/article/details/8331335
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值