[初试Android]Service+跨线程操作GUI的一个小例子
1、实验内容
本例子练习使用Service以及控件的跨线程操作:主Activity界面用来启动、停止一个服务,该服务启动后会产生一个线程,该线程每隔一秒产生一个随机双精度数,并将该随机数显示在主Activity界面的一个TextView控件中。
2、实现过程
2.1 建立工程
首先建立一个空白的Android工程:工程名为ServiceDemo,程序包存储为per.elton.servicedemo,主Activity为默认名MainActivity。
2.2 添加Service实现类
在per.elton.servicedemo下新建一个Service实现类:RandomService,该类继承android.app.Service。并重载该类的三个方法:
public class RandomService extends Service {
// 服务启动时调用一次
@Override
public void onCreate(){}
// 服务每次启动都调用一次
@Override
public void onStart(Intent intent, int startID){}
// 停止服务时调用
@Override
public void onDestory(){}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
}
2.3 布置MainActivity界面
添加两个按钮分别用来启动(btnStartService)和停止服务(btnStopService),添加一个TextView(tvShowRandom)用来显示随机数。布局如下图所示:
Fig 1
2.4 添加事件监听
使MainActivity实现android.view.OnClickListener接口,并添加该接口的实现方法:
@Override
public void onClick(View v) {}
对于“Start Service”按钮和“Stop Service”:添加对按钮点击事件的处理为如下代码。
@Override
public void onClick(View v) {
Intent intent = new Intent(this,RandomService.class);
switch (v.getId()) {
case R.id.btnStartService:
// 显示启动服务,知道服务的类以及路径
startService(intent);
// 隐式启动服务,不清楚类的更详细细节
/*
* Intent intentHiden = new Intent();
* intentHiden.setAction("MY_RANDOM_SERVICE");
* startService(intentHiden);
*/
break;
case R.id.btnStopService:
stopService(intent);
break;
}
}
既可以显式启动服务,也可以通过服务在AndroidManifest.xml中的名字(本例服务的名字名为“MY_RANDOM_SERVICE”)来启动服务。服务在XML文件中的设置如下:
<service android:name="RandomService">
<intent-filter>
<action android:name="MY_RANDOM_SERVICE"/>
</intent-filter>
</service>
本例中采用直接指定服务的实现类来启动RandomService这个服务。
在Activity启动时,将事件响应注册到两个按钮上。在onCreate方法中添加如下事件监听器。
Button btnStartService = (Button)findViewById(R.id.btnStartService);
Button btnStopService = (Button)findViewById(R.id.btnStopService);
btnStartService.setOnClickListener(this);
btnStopService.setOnClickListener(this);
2.5 添加updateGUI方法
为了实现RandomService服务所启动的线程对MainActivity中tvShowRandom控件的操作,在MainAcitivity中添加一个静态的方法updateGUI(double),以方便服务中线程的调用。
private static TextView tvRandom; // 在onCreate方法中完成赋值
private static double dbRandom; // 服务传递过来的随机数
private static int intIndex=0; // 记录产生的随机数的次数
private static Handler handler = new Handler(); // 接受子线程传递来的数据,并更新UI
private static Runnable refreshRunnable = new Runnable() {
@Override
public void run() {
tvRandom.setText(String.valueOf(intIndex++)+" 产生的随机数:"+String.valueOf(dbRandom));
}
};
public static void updateGUI(double dbTmp){
dbRandom = dbTmp;
handler.post(refreshRunnable);
}
关于Handler的更多介绍,http://www.pin5i.com/showtopic-android-handler.html。由于updateGUI方法是静态的,所以该方法涉及到的相关变量也都为静态的。
至此,只要RandomService服务通过调用静态方法updateGUI即可更新主界面的空间内容,也就是显示一个随机数。接下来实现RandomService。
2.6 实现RandomService中的关键方法
RandomServie需要实现2.2中所列出的三个主要方法:onCreate,onStart和onDestory。分别用来在初始化服务阶段、启动服务阶段和注销服务阶段调用。在onCreate方法中需要初始化一个线程,并通过一个Runnable变量指定其要实现的功能。相关代码如下:
private Thread rsThread;
private Runnable runableWork = new Runnable() {
public void run() {
try{
while(!Thread.interrupted()){
double dbRandom = Math.random();
MainActivity.updateGUI(dbRandom);// 更新界面
Thread.sleep(1000); // 子线程休眠一秒钟
}
}
catch (Exception e) {
Log.i("RANDOM_SERVICE", "Exception found ... ...");
}
}
};
@Override
public void onCreate(){
super.onCreate();
Toast.makeText(this, "Random service has been created now ... ...", Toast.LENGTH_LONG).show();
// 初始化线程
rsThread = new Thread(null,runableWork,"rsThread");
}
服务的onStart方法在每次RandomService恢复运行或者启动时调用此方法,因此可在此方法中启动子线程,代码如下:
@Override
public void onStart(Intent intent, int startID){
super.onStart(intent, startID);
Toast.makeText(this, "Random service starts ... ...", Toast.LENGTH_LONG).show();
// 启动线程产生并显示随机数
if (!rsThread.isAlive()){
rsThread.start();
}
}
停止RandomService服务:
@Override
public void onDestroy(){
super.onDestroy();
Toast.makeText(this, "Random service has been destroied now ... ...", Toast.LENGTH_LONG).show();
rsThread.interrupt();
}
现在所有的编码就完成了,可以直接编译放到机器上运行。本例中,模拟机采用的系统是基于android2.3.3平台的。
3、代码部分以及运行
代码在第二章节已有较详细的介绍,此处将所有的代码打包,点击下载。
运行界面如下所示:
Fig 2
4、总结
本例主要是练习如何实现一个服务,以及如何在子线程中操作主线程的GUI。