Android 后台服务简要概述

本篇文章主要讲述android servivce相关知识,其中会穿插一些其他的知识点,作为初学者的教程。老鸟绕路

本文会讲述如下内容:

- 为什么要用Service

- Service及其继承者IntentService

- 一个后台计数器的例子来讲述Service

- Service如何与UI组件通信


为什么要用Service

我们接触android的时候,大部分时候是在和activity打交道,但是有些比如网络下载、大文件读取、解析等耗时却又不需要界面对象的操作。一旦退出界面,那么可能就会变得不可控(比如界面退出后,线程通知UI显示进度,但是由于View已经被销毁导致报错,或者界面退出后下载中断,就算你写得非常完美,什么异常状态都考虑到了,还是保证不了系统由于内存紧张把你这个后台的activity给干掉,依附于于它的下载线程也中断。)

这时候Service就有它的用武之地了,不依赖界面,消耗资源少,优先级比后台activity高,不会轻易被系统干掉(就算被干掉,也有标志位设置可以让它自动重启,这也是一些流氓软件牛皮鲜的招数)、

Service及其继承者IntentService

service的生命周期

service的生命周期相对activity要简单不少。

可以看出service有两条生命线,一条是调用startService,一条是调用bindService

,两条生命线相互独立。本文只讲startService。

一道选择题,解释service生命周期的所有问题:

android通过startService的方式开启服务,关于service生命周期的onCreate()和onStart() 说法正确的是哪两项

A.当第一次启动的时候先后调用 onCreate()和 onStart()方法

B.当第一次启动的时候只会调用 onCreate()方法

C.如果 service 已经启动,将先后调用 onCreate()和 onStart()方法

D.如果 service 已经启动,只会执行 onStart()方法,不在执行 onCreate()方法

答案自己想下,结尾公布

IntentService

一些容易被忽略的基础知识:Service运行的代码是在主线程上的,也就是说,直接在上面运行会卡住UI,这时就Service的继承者(继承于Service的子类)IntentService就应运而生。android studio的新建里面直接就有IntentService的模板,足见其应用之广。

那么Service与IntentService的区别在哪呢?

详见这里 Android之Service与IntentService的比较

简单来说就是

  • IntentService内部有个工作线程(Worker Thread),会将startService传入的intent通过Handler-Message机制传入工作线程,开发者通过重载onHandleIntent进行服务的具体实现。
  • IntentService在跑完onHandleIntent后,如果Handler队列里没有其他消息,就会自动结束服务,有点像Thread中run函数一样,跑完run函数之后,线程就结束了。而service需要自己去停止。

一个后台计数器的例子来讲述Service

实战环节,本文通过一个计数器的例子模拟下载文件的耗时操作。

public void startService(View view){
    Intent intent = new Intent(this,BackgroundService.class);
    intent.setAction("com.example.administrator.servicestudy.action.counter");
    intent.putExtra("duration",10);
    intent.putExtra("interval",1.0f);
    startService(intent);
}

上述代码就是一个启动service的例子,action相当于做什么操作(适用于一个service处理多种请求的情况。),extra就是参数。参数中duration代表总时间10秒,interval代码每隔一秒。

private static final String ACTION_COUNTER = "com.example.administrator.servicestudy.action.counter";

@Override
protected void onHandleIntent(Intent intent) {
    if (intent != null) {
        final String action = intent.getAction();
        if (ACTION_COUNTER.equals(action)) {
            final int duration = intent.getIntExtra(EXTRA_DURATION,0);
            final float interval = intent.getFloatExtra(EXTRA_INTERVAL,0);
            handleActionCounter(duration, interval);
        }
    }
}

private void handleActionCounter(int duration, float interval) {
    for(int i=0; i<duration; i++){
        updateUI(i,duration);
        try {
            Thread.sleep((long) (interval*1000));
        } catch (InterruptedException ignored) {
        }
    }
    updateUI(duration,duration);
}

可以看到重载onHandleIntent处理事件,handleActionCounter表示具体服务。根据传入的参数决定循环时间和sleep间隔。

当然别忘了在manifest文件中声明该Service

<service
  android:name=".BackgroundService"
  android:exported="false" />

以上就是最基本的IntentService的用法了,不过为了代码独立性更好,可以将代码写成这样。

Activity

public void startService(View view){
     BackgroundService.startCounterService(this,1,10);
}

Service

public static void startCounterService(@NonNull Context context, int interval, int duration) {
        Intent intent = new Intent(context, BackgroundService.class);
        intent.setAction(ACTION_COUNTER);
        intent.putExtra(EXTRA_DURATION, duration);
        intent.putExtra(EXTRA_INTERVAL, interval);
        context.startService(intent);
    }

在Service里写个静态方法,只将参数传入,剩余的全都在Service内实现。虽然代码写的位置变了,但是代码运行的位置没变(静态方法依然还是运行在activity端),这样做将EXTRA_DURATION、EXTRA_INTERVAL等参数也不暴露给外部。做到更好的封装性和模块化,推荐这种做法。

Service如何与UI组件通信

那么Service在后台努力干活的时候,如何将当前进度通知给用户呢,因为Service不依赖任何界面,所以自身没办法操作界面(除非用Toast)。所以Service就要与其他组件进行通信(主要就是activity和通知栏了,但不限于上述两者)。

android组件间的通信(还记得android四大组件是哪四个不?)。 大部分通过android四大组件之一的Broadcast来通信。

那么简要说下Broadcast

Broadcast

生命周期:

就这么简单,一旦处理完广播就被销毁,没有onCreate,也没有onDestory

最重要的一点就是receiver里不能处理耗时操作,超过5秒(好像是)系统就会报错

Service

 private void updateUI(int current,int total){
    Intent intent = new Intent(BROADCAST_UPDATE_UI);
    intent.putExtra(EXTRA_CURRENT,current);
    intent.putExtra(EXTRA_TOTAL,total);

    sendBroadcast(intent);
}

可以看到,发个广播就这么简单,把参数填入intent,自定义一个action,send!好了。

Activity

@Override
protected void onResume() {
    super.onResume();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BackgroundService.BROADCAST_UPDATE_UI);
    registerReceiver(mBackgroundServiceReceiver,intentFilter);
}

@Override
protected void onPause() {
    super.onPause();
    unregisterReceiver(mBackgroundServiceReceiver);
}

private BroadcastReceiver mBackgroundServiceReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG,"receive:"+intent.getAction());
        if(intent.getAction() == BackgroundService.BROADCAST_UPDATE_UI){
            int current = intent.getIntExtra(BackgroundService.EXTRA_CURRENT,0);
            int total = intent.getIntExtra(BackgroundService.EXTRA_TOTAL,0);
            mHint.setText(current+"/"+total);
        }
    }
};

Activity在resume的时候注册一个广播接收器,pasue的时候注销掉。在receiver里处理更新UI的操作。就这么简单

同样的,为了代码更具有封装性。在Activity中将recevier去掉。放在Service中,看代码:

<receiver android:name=".BackgroundService$BackgroundServiceReceiver">
   <intent-filter>
       <action android:name="com.example.administrator.servicestudy.action.update_ui" />
   </intent-filter>
</receiver>
public static class BackgroundServiceReceiver extends BroadcastReceiver {
    private static List<UIHandler> mHandlers = new ArrayList<>();

    @Override
    public void onReceive(Context context, Intent intent) {
        if(intent.getAction().equals(BROADCAST_UPDATE_UI)){
            int current = intent.getIntExtra(BackgroundService.EXTRA_CURRENT,0);
            int total = intent.getIntExtra(BackgroundService.EXTRA_TOTAL,0);
            for (UIHandler handler : mHandlers) {
                handler.onUpdateUI(current,total);
            }
        }
    }
}

public interface UIHandler {
    void onUpdateUI(int current,int total);
}

public static void registerUIHandler(UIHandler handler){
    if(handler != null){
        BackgroundServiceReceiver.mHandlers.add(handler);
    }

}

public static void unregisterUIHandler(UIHandler handler){
    BackgroundServiceReceiver.mHandlers.remove(handler);
}

这里代码有点多,一点一点说,

  1. 首先在manifest里注册一个静态广播接收器,静态就是表示一直都会接收的,不需要手动register和unregister。一般的receiver都是单独一个文件,这里为了更好地封装性,写在Service里作为静态内部类。所以在manifest里的注册名字也写成了.BackgroundService$BackgroundServiceReceiver,注意中间一个美元符号,那就是表示公共静态内部类的标志。
  2. 在Service内部实现一个Receiver,具体和Activity里面的一样。
  3. 然后写一个interface,代表具体的UI处理
  4. 写一个注册函数和反注册函数,用以界面组件注册UI更新事件。
  5. 由于该Service可能不止只更新一个界面组件,所以注册的Handler是一个列表。在收到广播后,将所有注册过的组件都通知更新一遍。

然后在Activity中注册一下。替换掉注册广播的地方。

@Override
protected void onResume() {
    super.onResume();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BackgroundService.BROADCAST_UPDATE_UI);
//        registerReceiver(mBackgroundServiceReceiver,intentFilter);
    BackgroundService.registerUIHandler(mServiceUIHandler);
}

@Override
protected void onPause() {
    super.onPause();
    BackgroundService.unregisterUIHandler(mServiceUIHandler);
//        unregisterReceiver(mBackgroundServiceReceiver);
}

private BackgroundService.UIHandler mServiceUIHandler = new BackgroundService.UIHandler() {
    @Override
    public void onUpdateUI(int current, int total) {
        Log.d(TAG,"receive: service broadcast");
        mHint.setText(current+"/"+total);
    }
};

这样就完成了一个Service的封装,简化Activity的代码,我的思想一直都是Activity中,应该只处理和界面有关的代码。就像C语言的main函数一样,你不可能把所有代码都写在main函数里吧。或者把所有的函数写在同一个文件里吧。

这里需要注意的是,由于之前提过IntentService内部其实是一个Worker Thread,所以多按几次start,其实是多发了几次消息,导致会计数完成后,重新计数。这个自己感受下就知道了。

那么我们加一个stop Service的函数吧。

Service

public static void stopCounterService(@NonNull Context context){
    Intent intent = new Intent(context, BackgroundService.class);
    intent.setAction(ACTION_COUNTER);
    context.stopService(intent);
}

Activity

public void stopService(View view){
//        Intent intent = new Intent(this,BackgroundService.class);
//        intent.setAction("com.example.administrator.servicestudy.action.counter");
//        stopService(intent);
    BackgroundService.stopCounterService(this);
}

IntentService是以Message为单位来停止的,也就是说,一定要等到当前消息处理完才能完全stop掉,为此我们可以加一个标志位,一旦Service停止,强制循环退出。

Service

@Override
public void onCreate() {
    super.onCreate();
    Log.d(TAG,"onCreate");
    mServiceFinished = false;
}

@Override
public void onDestroy() {
    super.onDestroy();
    Log.d(TAG,"onDestroy");
    mServiceFinished = true;
}

private void handleActionCounter(int duration, float interval) {
   for(int i=0; i<duration; i++){
       if(mServiceFinished){
           break;
       }
       updateUI(i,duration);
       try {
           Thread.sleep((long) (interval*1000));
       } catch (InterruptedException ignored) {
       }
   }
   updateUI(duration,duration);
}

Service与通知栏的通信

至此我们已经完成了Service与Activity的通信,Service与Activity之间通过广播进行通信。Service负责逻辑处理,Activity负责更新界面显示。但是到这边还没发现Service的独特之处,就是这个这些代码完全也可以写在Activity里面的,写在Service里面无非就是结构更好看点,如果你那么认为就错了。你可以在Activity中退出再进入,可以发现计数器并没有因为Activity的退出而终止或者暂停。依然跟着时间走。这点是写在Activity中完全做不到的。当然你也可以通过一些小技巧来达到同样的效果,不过我们这个例子是为了模拟后台下载用的。所以不扯这些了。

下面进入真正的后台下载。Service与通知栏的通信。

我们这样设计一个程序,当Activity退出后,通知栏继续显示计数器进度,点击通知或者再次进入Activity,通知栏取消显示进度(为了不重复显示,也为了演示代码)。

为此我们新建一个新的Service,并在Activity添加如下代码

NotificationService

public class NotificationService extends Service {
    private static final String TAG = NotificationService.class.getSimpleName();

    public NotificationService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        BackgroundService.registerUIHandler(mUIHandler);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy");
        BackgroundService.unregisterUIHandler(mUIHandler);
    }
    ......
}

这里我们新建的是一个普通的service,而不是IntentService,因为这边我们不需要耗时操作,我们甚至连onStartCommand都没有重载,因为我们只需要在启动服务的时候注册一个UI更新的回调就可以了,然后在销毁服务的时候注销掉。

Activity

@Override
protected void onResume() {
    super.onResume();
    ...
    stopService(new Intent(this,NotificationService.class));
}

@Override
protected void onPause() {
    super.onPause();
    ...
    startService(new Intent(this,NotificationService.class));
}

我们在Activity Resume的时候关闭通知栏通知服务,在Pause的时候开启该服务,这样就能做到我们的设计初衷。

接下来就是通知栏的UI更新操作了,都是通知栏的接口,听说2.3和4.0以上的接口很不一样,我们这边用的是4.0以上的接口。

private BackgroundService.UIHandler mUIHandler = new BackgroundService.UIHandler() {
    @Override
    public void onUpdateUI(int current, int total) {
        Log.d(TAG,"Notification onUpdateUI");
        //点击通知后,启动Activity,最后的FLAG_ONE_SHOT,表示只执行一次,具体自行百度。
        PendingIntent pendingIntent = PendingIntent.getActivity(NotificationService.this,
                0,
                new Intent(NotificationService.this,MainActivity.class),
                PendingIntent.FLAG_ONE_SHOT);

        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        Notification.Builder builder = new Notification.Builder(getApplicationContext());
        Notification notification = builder.setContentTitle("Background Service")
                .setTicker("Counting...")//状态栏上滚动的字符串
                .setContentText("Ongoing")//设置通知的正文
                .setProgress(total, current, false)//设置通知栏的进度条,android真贴心,终于可以不用自定义进度条了。
                .setOngoing(true)//设置可不可以取消该通知
                .setContentIntent(pendingIntent)//点击该通知后的操作。
                .setDefaults(Notification.DEFAULT_ALL)//通知的音效、震动、呼吸灯全都随系统设置,当然你也可以自定义
                .setAutoCancel(true)//是不是点击之后自动取消,否则的话,可能你需要手动调用接口来取消
                .setOnlyAlertOnce(true)//音效震动呼吸灯是否只提醒一下,专门给进度条之类,频繁更新的通知用的,不设置这个,你可以试试,那鬼畜的音效
                .setSmallIcon(R.mipmap.ic_launcher)//这个不解释了
                .build();
        //第一个参数为ID,APP内全局唯一,相同的ID表示相同的通知,不会在通知栏新增一条通知,不同的话,则在通知栏插入一条新的通知。第二个参数就是刚才配置的通知。
        nm.notify(1234,notification);
    }
};

最后提醒一句,通知不配置PendingIntent是不会显示的哦

为了完美模拟后台下载,我们在下载完成后(服务被销毁后),发送一个结束广播,通知UI层。

Service

public interface UIHandler {
    void onUpdateUI(int current,int total);
    void onFinish();
}

新增一个结束时的回调

@Override
public void onDestroy() {
    ....
    Intent intent = new Intent(BROADCAST_FINISH);
    sendBroadcast(intent);
}

在被销毁时发送广播

@Override
public void onReceive(Context context, Intent intent) {
    if(intent.getAction().equals(BROADCAST_UPDATE_UI)){
       ....
    }else if(intent.getAction().equals(BROADCAST_FINISH)){
        for (UIHandler handler : mHandlers) {
            handler.onFinish();
        }
    }
}

在onReceive中发送onFinish的回调

<receiver android:name=".BackgroundService$BackgroundServiceReceiver">
  <intent-filter>
      <action android:name="com.example.administrator.servicestudy.action.update_ui" />
      <action android:name="com.example.administrator.servicestudy.action.finish" />
  </intent-filter>
</receiver>

最重要的是别忘了在manifest中声明这个广播,因为Service中的是静态广播接收器

而在Activity和Notification中就简单多了,只要实现相应的onFinish回调就可以了

@Override
public void onFinish() {
    Log.d(TAG,"receive: service finish");
    mHint.setText("Finished");
}
@Override
public void onFinish() {
    Log.d(TAG,"Notification onFinish");

    NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    Notification.Builder builder = new Notification.Builder(getApplicationContext());
    Notification notification = builder.setContentTitle("Background Service")
            .setContentText("Finished")
            .setOngoing(false)
            .setContentIntent(null)//这里PendingIntent设置为null,只是为了演示代码,这样这个通知点上去就不会有反应
            .setDefaults(Notification.DEFAULT_ALL)
            .setAutoCancel(true)
            .setOnlyAlertOnce(true)
            .setSmallIcon(R.mipmap.ic_launcher)
            .build();

    //设置两个不同的notification ID,为了演示两个不同通知,并且演示如何取消一个通知
    nm.notify(1232,notification);
    nm.cancel(1234);
}

教程到此结束。谢谢

最后公布,文中一道问题的答案,A和D。很简单吧

源码点这里下载

时间: 2024-10-09 20:29:27

Android 后台服务简要概述的相关文章

android 后台服务定时通知

最近有个项目的要求是在程序退出之后,任然可以每天定时发通知,我们可以想下,其实就是后台开一个服务,然后时间到了就发下通知. 1.首先我们需要用到Service类. 先上代码在慢慢解释 1 package com.example.androidnotification; 2 3 import java.util.Timer; 4 import java.util.TimerTask; 5 import android.app.Notification; 6 import android.app.N

Android后台服务拍照的解决方式

一.背景介绍 近期在项目中遇到一个需求.实现一个后台拍照的功能. 一開始在网上寻找解决方式.也尝试了非常多种实现方式,都没有惬意的方案.只是确定了难点:即拍照要先预览,然后再调用拍照方法.问题也随之而来.既然是要实现后台拍照,就希望能在Service中或者是异步的线程中进行,这和预览这个步骤有点相矛盾. 那有什么方式可以既能正常的实现预览.拍照,又不让使用者察觉呢?想必大家也会想到一个取巧的办法:隐藏预览界面. 说明一下,这仅仅是我在摸索中想到的一种解决方式.能非常好的解决业务上的需求. 对于像

android后台服务的基本用法

了解了安卓多线程编程的技术之后,作为安卓的四大组件之一,是十分重要的. 定义一个服务 首先看一下如何在项目中定义一个服务, public class MyService extends Service { @Override public void onCreate() { super.onCreate(); Log.d("myservice","oncreate"); } @Override public int onStartCommand(Intent int

Android后台服务拍照的解决方案

一.背景介绍 最近在项目中遇到一个需求,实现一个后台拍照的功能.一开始在网上寻找解决方案,也尝试了很多种实现方式,都没有满意的方案.不过确定了难点:即拍照要先预览,然后再调用拍照方法.问题也随之而来,既然是要实现后台拍照,就希望能在Service中或者是异步的线程中进行,这和预览这个步骤有点相矛盾.那有什么方式能够既能正常的实现预览.拍照,又不让使用者察觉呢?想必大家也会想到一个取巧的办法:隐藏预览界面. 说明一下,这只是我在摸索中想到的一种解决方案,能很好的解决业务上的需求.对于像很多手机厂商

Android后台服务拍照

原文:https://blog.csdn.net/wurensen/article/details/47024961 一.背景介绍最近在项目中遇到一个需求,实现一个后台拍照的功能.一开始在网上寻找解决方案,也尝试了很多种实现方式,都没有满意的方案.不过确定了难点:即拍照要先预览,然后再调用拍照方法.问题也随之而来,既然是要实现后台拍照,就希望能在Service中或者是异步的线程中进行,这和预览这个步骤有点相矛盾.那有什么方式能够既能正常的实现预览.拍照,又不让使用者察觉呢?想必大家也会想到一个取

Android中如何像 360 一样优雅的杀死后台服务而不启动

Android中,虽然有很多方法(API或者shell命令)杀死后台`service`,但是仍然有很多程序几秒内再次启动,导致无法真正的杀死.这里主要着重介绍如何像 360 一样杀死Android后台服务,而不会再次启动. 一.已知的 kill 后台应用程序的方法 android.os.Process.killProcess(pid); activityManager.killBackgroundProcesses(pkgName); kill -9 pid 这三种方法都可以“杀死”后台应用程序

Android Services (后台服务)

一.简介 服务是可以在后台执行长时间运行的应用程序组件,它不提供用户界面. 另一个应用程序组件可以启动一个服务,并且即使用户切换到另一个应用程序,它仍然在后台运行. 另外,组件可以绑定到一个服务来与它进行交互,甚至执行进程间通信(IPC). 例如,服务可以从后台处理网络交易,播放音乐,执行文件I / O或与内容提供商交互. 这些是三种不同类型的服务: Scheduled(计划的服务)--- Android 5.0后可用当在Android 5.0(API级别21)中引入的诸如JobSchedule

android如何做到类似于微信那样后台服务不会被杀死?

问题描述 正在做一款锁屏应用. 做锁屏肯定用到了service,可是我发现每日手动点击自带的内存清理按钮的时候,我的那个service总是会被杀死. 而微信的后台服务却是一直正常的运行,不会被杀掉. 360的话也不会被杀死,但是360会重新启动.而且360的是两个后台服务,我猜有可能会相互作用的,杀死一个的时候另一个接收到广播把其重启. 尝试过用startForeground以及提高service优先级的方式,发现都不行,service都会被杀死. 反编译了一下微信的代码. 请大家帮忙想想办法吧

Android四大组件——Service后台服务、前台服务、IntentService、跨进程服务、无障碍服务、系统服务

Service后台服务.前台服务.IntentService.跨进程服务.无障碍服务.系统服务 本篇文章包括以下内容: 前言 Service的简介 后台服务 不可交互的后台服务 可交互的后台服务 混合性交互的后台服务 前台服务 IntentService AIDL跨进程服务 AccessibilityService无障碍服务 系统服务 部分源码下载 前言 作为四大组件之一的Service类,是面试和笔试的必备关卡,我把我所学到的东西总结了一遍,相信你看了之后你会对Service娓娓道来,在以后遇