Android第一行代码学习笔记七----后台服务

服务是Android实现程序后台运行的方案,适合执行不需和用户交互且长期运行的任务。服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。

1、Android多线程

1、线程的基本用法
与java多线程编程类似。
有三种方式
1、继承
新建类继承Thread,重写父类run()方法,在里面处理耗时逻辑。
示例代码如下:
class MyThread extends Thread {
@Override
public void run() {
// 处理具体的逻辑
}
}
启动该线程代码如下:
new MyThread().start();
2、接口
由于继承的耦合性较高,更多是选择使用实现Runable接口的方式来定义线程
class MyThread implements Runnable {
@Override
public void run() {
// 处理具体的逻辑
}
}
启动该线程代码如下:
MyThread myThread = new MyThread();
new Thread(myThread).start();
说明:Thread构造函数接收Runable参数,myThread正是一个Runable接口对象。接着调用start方法即可执行run方法。
3、匿名类
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
}
}).start();
2、子线程更新UI
Android的UI更新必须在主线程中进行,否则会出现异常。示例代码如下:
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/change_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Change Text" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Hello world"
第一行代码——Android
346
android:textSize="20sp" />
</RelativeLayout>
说明:主要包含两个空间,一个显示字符串,一个按钮用于更新字符串内容。
MainActivity示例代码:
public class MainActivity extends Activity implements OnClickListener {
public static final int UPDATE_TEXT = 1;
private TextView text;
private Button changeText;
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
// 在这里可以进行UI操作
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
……
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message); // 将Message对象发送出去
}
}).start();
break;
default:
说明:UPDATA_TEXT表示更新UI这个动作。新增handler对象,并重写父类handleMessage()方法处理具体逻辑。在子线程中,创建Message对象,设置what字段,调用handler的sendMessage方法发送。此时handler会受到这条message,并对其进行处理。此时,就是在主线程中运行了。
3、异步消息处理机制
Android异步消息处理主要由Message、Handler、MessageQueue和Looper四个部分组成。
1、Message
是在线程之间传递的消息,可在内部携带少量信息,用于在不同线程之间交换数据。 Message 的 what 字段携带字符串、 arg1 和 arg2 字段来携带整型数据,使用 obj 字段携带一个 Object 对象。
2、Handler
处理者,主要用于发送和处理消息。发送消息使用 Handler 的 sendMessage()方法,传递到 Handler 的 handleMessage()方法。
3、MessageQueue
消息队列,主要用于存放所有通过 Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个 MessageQueue对象。
4、Looper
Looper 是每个线程中的 MessageQueue 的管家,调用 Looper 的 loop()方法后,就会进入到一个无限循环当中,然后每当发现 MessageQueue 中存在一条消息,就会将它取出, 并传递到 Handler 的 handleMessage()方法中。 每个线程中也只会有一个 Looper 对象。
4、AsyncTask
AsyncTask基于异步消息处理机制。为了更好的在子线程中对UI进行操作。它是一个抽象类,要使用必须创建一个类去继承它,继承时可以指定三个泛型参数。三个参数如下:
1、Params
在执行AsyncTask时需要传入的的参数。
2、Progress
后台任务执行时显示进度。
3、Result
当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
示例如下:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
……
}
第一个参数指定为void,不需传参,第二个为Integer,表示以整型显示进度,第三个指定为Boolean,表示使用布尔型数据返回结果。
接着,我们需要重写以下4个方法
1、onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
2. doInBackground(Params...)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过 return 语句来将任务的执行结果返回,如果 AsyncTask 的第三个泛型参数指定的是 Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行 UI 操作。UI更新可以调用 publishProgress(Progress...)方法来完成。
3. onProgressUpdate(Progress...)
当在后台任务中调用了 publishProgress(Progress...)方法后,这个方法就会很快被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对 UI 进行操作,利用参数中的数值就可以对界面元素进行相应地更新。
4. onPostExecute(Result)
当后台任务执行完毕并通过 return 语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些 UI 操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
示例代码如下:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show(); // 显示进度对话框
}
@Override
protected Boolean doInBackground(Void... params) {
try {
while (true) {
int downloadPercent = doDownload(); // 这是一个虚构的方法
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
// 在这里更新下载进度
progressDialog.setMessage("Downloaded " + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss(); // 关闭进度对话框
// 在这里提示下载结果
if (result) {
Toast.makeText(context, "Download succeeded",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, " Download failed",
Toast.LENGTH_SHORT).show();
}
}
}
说明;:在doInBackgroud中执行具体下载任务,。这个方法中的代码都是在子线程中运行的,doDownload()用于计算当前下载进度并返回,如果需要更新UI,则直接调用publishProgress方法,参数为当前下载进度。总的来说。在 doInBackground()方法中去执行具体的耗时任务,在 onProgressUpdate()方法中进行 UI 操作,在 onPostExecute()方法中执行一些任务的收尾工作。
启动该任务代码:
new DownloadTask().execute();

2、服务

1、定义一个服务
新建类Myservice
public class MyService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
说明:onBind()是Service中唯一一个抽象方法,必须在子类中实现。onCreate()、onStartCommand()、onDestroy()分别在服务创建、启动和销毁时候调用。
服务需要在配置文件中注册才能生效,配置如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicetest"
android:versionCode="1"
android:versionName="1.0" >
……
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
……
<service android:name=".MyService" >
</service>
</application>
</manifest>
启动和停止服务
启动和停止服务主要使用Intent来实现。
MainActivity()示例代码如下:
public class MainActivity extends Activity implements OnClickListener {
private Button startService;
private Button stopService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService = (Button) findViewById(R.id.start_service);
stopService = (Button) findViewById(R.id.stop_service);
startService.setOnClickListener(this);
stopService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.start_service:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 启动服务
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服务
break;
default:
break;
}
}
}
说明:构建Intent对象,调用Context类中的startService和stopService来操作服务。如果不停止服务,那么服务就会一直运行,也可以在MyService中任何位置调用stopSelf()方法使自己停止。onCreate()方法和 onStartCommand()区别就是,第一次点击启动服务时,两个方法都会执行,之后会发现只有onStartCommand()方法执行。
活动与服务通信
活动借助Binder对象来与服务通信。例如在MyService实现下载功能,活动中可以随时查看下载进度。
MyService()示例代码如下:
public class MyService extends Service {
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder {
public void startDownload() {
Log.d("MyService", "startDownload executed");
}
public int getProgress() {
Log.d("MyService", "getProgress executed");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
……
}
说明:新建DownloadBinder类继承Binder,在其内部提供开始下载以及查看下载进度的方法。接着,在MyService中创建DownloadBinder实例,在onBind()方法里返回这个实例。
当一个活动和服务绑定之后,就可以调用该服务里的Binder提供的方法了。
MainActivity示例代码如下:
public class MainActivity extends Activity implements OnClickListener {
private Button startService;
private Button stopService;
private Button bindService;
private Button unbindService;
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
……
bindService = (Button) findViewById(R.id.bind_service);
unbindService = (Button) findViewById(R.id.unbind_service);
bindService.setOnClickListener(this);
unbindService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
……
case R.id.bind_service:
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE); // 绑定服务
break;
case R.id.unbind_service:
unbindService(connection); // 解绑服务
break;
default:
break;
}
}
}
说明:创建ServiceConnection匿名类,重写onServiceConnected()和onServiceDisconnected()。这两个方法分别会在活动和服务绑定及解除时候调用。在onServiceConnected中,通过向下转型得到DownloadBinder的实例,现在就可以在活动中调用DownloadBinder的任何public方法。
构建Intent对象,调用bindService()将MainActivity与MyService绑定。bindService接收三个参数,第一是构建的intent对象,第二是ServiceConnection实例,第三是标志位,传入BIND_AUTO_CREATE表示在活动和服务进行绑定后自动创建服务。这会执行MyService中的onCreate()方法,但不执行onStartCommand()方法。
解除绑定调用unbindService即可。
注意:任何一个服务可以跟整个程序中任何一个活动绑定,且绑定后可以获取到相同的downloadBinder实例。

服务的生命周期

服务有自己的生命周期。onCreate()、onStartCommand()、onBind()和 onDestroy()是在生命周期内可能回调的方法。
Context的startService()方法被调用后,响应的服务就会启动,并回调onStartCommand()。如果服务未被创建过,则先执行onCreate()方法再执行onStartCommand()。服务启动了之后会一直保持运行状态,直到 stopService()或stopSelf()方法被调用。服务只存在一个实例,不管调用多少次startService(),只需一次stopService或stopSelf(),服务就会停止。
通过调用Context的bindService()获取一个服务的持久连接,这时会回调服务的onBind()方法。如果服务之前未被创建过,同样先执行onCreate()方法。之后,调用方可以获取到onBind方法返回的IBinder对象,就可以自由的和服务进行通信。
调用了 startService()方法则调用 stopService()方法销毁服务,执行onDestroy()。调用bindService()方法再调用unbindService()方法,onDestroy()方法也会执行。启动服务和绑定服务都调用,则调用stopService()和unbindService()才能停止服务。

服务更多技巧

前台服务
系统内存不足时,可能会回收后台服务。这时考虑使用前台服务。前台服务类似于通知效果(如天气)。
修改MyService代码如下:
public class MyService extends Service {
……
@Override
public void onCreate() {
super.onCreate();
Notification notification = new Notification(R.drawable.ic_launcher,
"Notification comes", System. currentTimeMillis());
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
notification.setLatestEventInfo(this, "This is title", "This is
content", pendingIntent);
startForeground(1, notification);
Log.d("MyService", "onCreate executed");
}
……
}
说明:修改了onCreate中的方法,创建通知,调用startForeground()代替之前的NotificationManager显示通知,startForeground()方法接收两个参数,第一个是通知的id,第二个是notification对象。该方法会让MyService变成一个前台服务,并在系统状态栏显示。
使用IntentService
服务中的代码是默认在主线程运行,因此处理耗时逻辑易出现ANR,因此应该在服务的每个具体方法中开启子线程。
示例代码如下:
public class MyService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
}
说明:此时,服务一旦启动,就会一直运行,必须调用stopService()或者stopSelf。
改善示例如下:
public class MyService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
}
除了以上方法,Android提供了IntentService类来实现以上功能。
新建MyIntentService类继承IntentService,示例代码如下:
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService"); // 调用父类的有参构造函数
}
@Override
protected void onHandleIntent(Intent intent) {
// 打印当前线程的id
Log.d("MyIntentService", "Thread id is " + Thread.currentThread().
getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy executed");
}
}
说明:子类中实现onHandlerIntent()方法处理具体逻辑。这个方法已经在子线程中运行,同时,运行完成之后,该服务会自动停止。
主类中调用代码:
Intent intentService = new Intent(this, MyIntentService.class);
startService(intentService);
同时需要在配置文件中注册,示例如下:
<service android:name=".MyIntentService"></service>
IntentService是集开启线程和自动停止于一声的服务。

后台定时任务

两种实现方式,一是Java API提供的Timer类,二是Android的Alarm机制。在Android中,如果手机长时间不操作,CPU就会进入睡眠状态,此时Timer中的定时任务无法正常运行,而Alarm有唤醒CPU功能。
Alarm借助AlarmManager类实现,类似NotificationManager,通过调用Contextd getSystemService获取示例。传入的参数是Context.ALARM_SERVICE。示例如下:
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
调用set()方法设置定时任务,如设定10秒后执行,示例如下:
long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent);
说明:set()方法需要传入三个参数
第一个是整型参数,用于指定AlarmManager的工作类型,有四种值可选,分别是ELAPSED_REALTIME、ELAPSED_REALTIME_WAKEUP、RTC 和 RTC_WAKEUP。其中 ELAPSED_REALTIME 表示让定时任务的触发时间从系统开机开始算起,但不会唤醒 CPU。ELAPSED_REALTIME_WAKEUP 表示会唤醒 CPU。RTC 表示从 1970 年 1月 1 日 0 点开始算起,不唤醒CPU。RTC_WAKEUP 表示会唤醒 CPU。使用 SystemClock.elapsedRealtime()方法可以获取到系统开机至今所经历时间的毫秒数,使用 System.currentTimeMillis()方法可以获取到 1970 年 1 月 1 日 0 点至今所经历时间的毫秒数。
第二个参数是定时任务触发时间。与第一个参数相关。如果是开机时间,则获取开始时间再加上延迟时间,其他同理。
第三个参数是PendingIntent。
创建一个长期在后台执行定时任务的服务实例代码如下:
public class LongRunningService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
Log.d("LongRunningService", "executed at " + new Date().
toString());
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 60 * 60 * 1000; // 这是一小时的毫秒数
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent i = new Intent(this, AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
return super.onStartCommand(intent, flags, startId);
}
}
说明:在onStartCommand中开启子线程,子线程中只是打印了日志。接着获取AlarmManager实例,定义任务触发时间为1小时,使用PendingIntent指定处理定时任务的广播接收器为AlarmReceiver,然后调动set()方法完成设定。
新建AlarmReceiver继承BroadcastReceiver,示例代码如下:
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, LongRunningService.class);
context.startService(i);
}
}
说明:onReceiver中只是启动了LongRunningService服务,一旦服务被启动,就会定时调用AlarmReceiver中的onReceive犯法,然后又启动LongRunningService,这样就保证了LongRunningService 可以每隔一小时就会启动一次,一个长期在后台定时运行的服务自然也就完成了。接下来只要在打开程序时启动一次LongRunningService即可。
示例代码如下:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, LongRunningService.class);
startService(intent);
}
}
服务和广播接收器都需要在配置文件注册:
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.servicebestpractice.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>
<service android:name=".LongRunningService" >
</service>
<receiver android:name=".AlarmReceiver" >
</receiver>
</application>
注意:在Android4.4版本开始,Alarm任务的触发时间会变得不准确,这是因为系统在耗电方面进行了优化。如果要求时间必须准确无误,可以使用setExact()代替set()方法。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值