Android学习笔记_02 – Service的测试应用
在我的项目里,有些工作需要在后台进行,比如:手机采集到的地理位置信息需要定时上传到远程Web服务器;百度地图离线数据的下载管理等。此类后台应用基本都依赖网络连接质量、长时间运行的特点,所以不能对前台造成影响,比如卡顿、死机等等。所以这几天对Android的Service应用做了一些了解。
在下面的测试工程里,我会以示例来进行相关知识的梳理。测试工程的大体思路为:建立一个前台Activity、一个后台Service;该Service主要作用是提供一个大概1秒运行一次的定时器,为我实战项目的其他定时操作定时器服务。此用法我是参考以前用过的Delphi的TTimer控件的OnTimer事件来处理的。定时器使用的轮训线程采用“Handler +Runnable接口”方式来实现,Runnable接口也是常用的线程处理方法之一。
1、 很简单地创建一个基于Service应用。
1.1、 创建工程,生成一个默认的MainActivity类
应用程序名称:TimerService
包名:test.timerservice
Activity名称为:MainActivity
1.2、 修改布局文件
在手机的MainActivity的布局文件activity_main.xml里放置两个按钮,一个为启动服务,一个为停止服务。
文件“activity_main.xml”内容为:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<Button
android:id="@+id/btnStartService"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="StartService"
android:text="启动服务"/>
<Button
android:id="@+id/btnSopService"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="StopService"
android:text="停止服务"/>
</LinearLayout>
注意文件中的两行:
android:onClick="StartService"
和
android:onClick="StopService"
在布局文件里定义了按钮的点击事件函数名称,需要在MainActivity.java里定义好该函数后才能起作用。现在可以启动程序,会出现以下界面,当然,现在点击按钮会出现异常的。
1.3、 初步编写MainActivity的代码,实现按钮接口,也好避免运行时按钮弹出错误。
文件“”内容为:
package test.timerservice;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;
public classMainActivity extends Activity {
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public booleanonCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar ifit is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void StartService(Viewview){
Toast.makeText(this,"点击了“启动服务”按钮!", Toast.LENGTH_SHORT).show();
}
public void StopService(View view){
Toast.makeText(this,"点击了“停止服务”按钮!", Toast.LENGTH_SHORT).show();
}
}
在模拟器里调试运行后,点击按钮,会弹出相应的提示信息。
1.4、 编写服务类代码
下面的代码是一个比较标准的Service的格式,我基本上将基类所有的函数复写入口都预留出来了,便于读者理解和修改。
文件“TimerService.java”内容为:
package test.timerservice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public classTimerService extends Service {
private static String TAG = "TimerService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG,"onCreate()");
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent,startId);
Log.d(TAG,"onStart(intent, startId)");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG,"onDestroy()");
}
@Override
public IBinder onBind(Intentintent) {
Log.e(TAG,"onBind(Intent intent)");
return null;
}
@Override
public boolean onUnbind(Intent intent){
Log.e(TAG,"onUnbind(Intent intent)");
return super.onUnbind(intent);
}
}
1.5、 实现在MainActivity里启动TimerService
在启动一个新建的Service之前,需要对该Service进行注册,注册的方法是在AndroidManifest.xml里加上Service标签,如下图:
现在可以修改MainActivity.java文件里的两个按钮执行函数了:
public voidStartService(View view){
Toast.makeText(this,"点击了“启动服务”按钮!", Toast.LENGTH_SHORT).show();
Intentintent = newIntent(this,TimerService.class);
startService(intent);
}
public void StopService(View view){
Toast.makeText(this,"点击了“停止服务”按钮!", Toast.LENGTH_SHORT).show();
Intentintent = newIntent(this,TimerService.class);
stopService(intent);
}
运行程序,点击模拟器界面上的“启动服务”、“停止服务”按钮,在LogCat里输出了在代码里实现的几个和生命周期有关的输出信息。说明服务生成、启动、销毁都成功了。
到此就实现了Service的基本功能
2. 在服务TimerService里采用“Handler + Runnable接口”方式实现线程
2.1、 修改TimerService.java文件
此处的代码都在TimerService的类里面编写。
1)先定义和实例化一个Handler类对象handler:
private Handler handler = new Handler();
2)定义和生成Runnable线程timerThread:
RunnabletimerThread= newRunnable(){
public void run(){
Log.d(TAG,"TimerOnTimer");
handler.postDelayed(timerThread, 1000);
}
};
上面代码中handler.postDelayed(timerThread, 1000); 实现延时1秒后再运行下一个线程。
3)首次使用Handler调用timerThead线程
大家都知道Handler启动线程的语句为:
handler.post(timerThread);
但该代码放到哪里很关键,我们可以看看尝试多次点击“启动服务”按钮,看LogCat输出如下图:
从图中可以看到,程序刚开始运行第一次启动TimerService时,会触发顺序onCreate事件和onStart事件,但从第二次以后,startService只触发onStart事件;
尝试点击一下“停止服务”,再点击一下“启动服务”,看下图:
注意红框里的内容,服务停止时,触发onDestroy事件,服务重新启动时,又顺序触发onCreate和onStart事件。
由于启动线程语句handler.post(timerThread); 每运行一次,会启动一个新线程,所以该语句必须写到TimerService的onCreate事件里,而不能放到onStart事件里,因为startService可能会多次运行,会多次触发onStart事件。
4)、停止Runnable线程
语句 handler.removeCallbacks(timerThread);可以从Handler列表里去除timerThread线程,就不会再次运行该线程了。
线程的停止语句,当然是放到onDestroy事件里了,确保服务停止时,会同时停止线程的运行。
5)程序测试
在模拟器里运行程序,输出结果如下图:
如图所示
启动服务后,启动Service的onCreate和onStart事件,并且在onCreate里启动了定时线程,由于第一次是采用handler.post方法,所以不延时直接启动线程,就是上图第三行的TimerOnTimer输出;
随后以接近1秒钟的时间间隔启动线程,就是上图中后面的几个TimerOnTimer输出;
最后我停止了Service服务,所以触发Service的onDestroy事件,在onDestroy用handler.removeCallbacks里取消了线程的下次执行;
说明:关于定时1秒,由于线程里有一些操作,这种写法间隔一般都大于1000毫秒,但这并不影响“定时器”的使用效果;
3. 此方法的不足
我发现在timerThread里执行较长时间的耗时循环时,程序界面会出现“假死”现象,时间如果太长,会导致程序出现异常。
3.1、给程序添加一个按钮和文本显示框,activity_main.xml文件的修改内容如下:
<Button
android:id="@+id/btnClickMe"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="ClickMe"
android:text="点我"/>
<TextView
android:id="@+id/textView1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="计数:0"/>
3.2、修改MainActivity.java文件
1)、在类里定义一个类内的公共变量:
int count = 0;
2)、定义一个按钮的点击函数:
public void ClickMe(View view){
TextViewtextview = (TextView)findViewById(R.id.textView1);
count = count + 1;
textview.setText("计数:"+count);
}
//当每次点击一下按钮,计数自动加1,病显示在文本显示框里
3)、为了便于测试,重写onCreate和onDestroy方法
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intentintent = newIntent(this,TimerService.class);
startService(intent);
}
// 说明:在onCreate里加入启动服务的操作,这样当程序启动时,就可以自动启动服务了;
@Override
protected void onDestroy() {
Intentintent = newIntent(this,TimerService.class);
stopService(intent);
super.onDestroy();
}
// 说明:在onDestroy里加入停止服务的操作,这样当程序退出时,就可以自动停止服务了;
4)、测试一下
模拟器里运行程序,从LogCat里可以看到服务启动正常,并1秒钟执行一次线程,点击“点我”按钮,看到计数加1变化,且界面运行速度正常。
3.3、修改TimerService.java
1)、在Service类里写一个延时函数
private void delay(int intVal){
int i = 0;
int j = 0;
int k = 0;
while (i < intVal){
while (j < intVal){
while (k < intVal){
k= k + 1;
}
k= 0;
j= j + 1;
}
j= 0;
i= i + 1;
}
i= 0;
}
2)、在runnable里调用延时函数
RunnabletimerThread= newRunnable(){
public void run(){
Log.d(TAG,"TimerOnTimer");
delay(500); // 此处调用延时函数
handler.postDelayed(timerThread, 1000);
}
};
//说明:delay(500);里面的500是一个延时经验数值,各个系统可能效果不一样,
//这个数值越大,延时越长,可以根据自己电脑来进行调整
//我的系统里,设为500时,大概延时5秒
3)、运行测试
从上图可以看出达到了延时效果,此次狂点“点我”按钮,界面已经出现反应迟钝了,说明当线程里出现耗时较长的任务比如循环时,主界面会假死。
继续加大延时,我的测试里出现了程序崩溃的错误:
4)、这是代码问题吗?
不知道是不是对线程使用的不对?还是在线程里用循环来做测试是否合理?我确实不得而知,希望高手指教。
不过我用在TimerService里套用Thread线程的方法达到了我的项目使用要求,这部分留待下次再整理记录吧。
示例源码:Android学习笔记_02 – Service的测试应用