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

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

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,告知其在界面上显示消息内容

<!-- wp:paragraph -->
<p>/*</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>Tell Activity to show message on screen<br>
*/<br>
public void showToast(String str) {<br>
Intent intent = new Intent();<br>
intent.putExtra("cmd", CMD_SHOW_TOAST);<br>
intent.putExtra("str", str);<br>
intent.setAction("android.intent.action.cmdactivity");<br>
sendBroadcast(intent);  <br>
}</li></ul>
<!-- /wp:list -->

同时考虑一下,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);
}

/*
 * 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.csdn.net/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.csdn.net/cen616899547/article/details/6728040
[5] Broadcast简单实例, http://blog.csdn.net/xyylchq/article/details/6824992
[6] Android多个按钮的处理方法,  http://blog.csdn.net/woshixuye/article/details/8331335
————————————————
版权声明:本文为CSDN博主「staticnetwind」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/stayneckwind2/article/details/45132253

0 0 投票数
文章评分
订阅评论
提醒
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x