Android 学习日报 组件Service再学习

51 阅读7分钟

10.3 Service的基本用法

10.3.1 定义一个Service

package com.example.demo2.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

import androidx.annotation.Nullable;

public class MyService  extends Service {
    // 这个方法是Service中唯一的抽象方法,所以必须在子类里实现
    @Nullable
    @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();
    }
}

这里我们又重写了onCreate()、onStartCommand()和onDestroy()这3个方法,它们是每个Service中最常用到的3个方法了。

其中onCreate()方法会在Service创建的时候调用

onStartCommand()方法会在每次Service启动的时候调用

onDestroy()方法会在Service销毁的时候调用


在AndroidManifest.xml文件中进行注册才能生效

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        tools:targetApi="31">
        <service
            android:name=".service.MyService"
            android:enabled="true"
            android:exported="true" />
    </application>

</manifest>

Exported 属性表示是否将这个Service暴露给外部其他程序访问,Enabled 属性表示是否启用这个Service。

10.3.2 启动和停止Service

启动和停止的方法当然你也不会陌生,主要是借助Intent来实现的。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button startBtn = findViewById(R.id.btn1);
        startBtn.setOnClickListener(v -> {
            Intent intent = new Intent(this, MyService.class);
            startService(intent);
        });

        Button stopBtn = findViewById(R.id.btn1);
        stopBtn.setOnClickListener(v -> {
            Intent intent = new Intent(this, MyService.class);
            stopService(intent);
        });
    }

另外,Service也可以自我停止运行,只需要在Service内部调用stopSelf()方法即可。

启动Service后,可以在Settings→System→Advanced→Developeroptions→Running services中找到它


从Android 8.0系统开始,应用的后台功能被大幅削减。现在只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。

如果你真的非常需要长期在后台执行一些任务,可以使用前台Service或者WorkManager

10.3.3 Activity和Service进行通信

需要借助 onBind() 方法

比如说,目前我们希望在MyService里提供一个下载功能,然后在Activity中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理。

package com.example.demo2.service;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import androidx.annotation.Nullable;

public class MyService extends Service {

    private DownloadBinder mBinder = new DownloadBinder();

    // 这个方法是Service中唯一的抽象方法,所以必须在子类里实现
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public static class DownloadBinder extends Binder {

        public void startDownload() {
            Log.d("MyService", "startDownload executed");
        }

        public int getProgress() {
            Log.d("MyService", "getProgress executed");
            return 0;
        }

    }
}

这里我们新建了一个DownloadBinder类,并让它继承自Binder,然后在它的内部提供了开始下载以及查看下载进度的方法。

当一个Activity和Service绑定了之后,就可以调用该Service里的Binder提供的方法了。

package com.example.demo2;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.widget.Button;

import com.example.demo2.service.MyService;

public class MainActivity2 extends AppCompatActivity {

    private MyService.DownloadBinder downloadBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);

        Button bindServiceBtn = findViewById(R.id.btn3);
        bindServiceBtn.setOnClickListener(v -> {
            Intent intent = new Intent(this, MyService.class);
            bindService(intent, connection, Context.BIND_AUTO_CREATE);// 绑定Service
        });

        Button unbindServiceBtn = findViewById(R.id.btn4);
        unbindServiceBtn.setOnClickListener(v -> {
            unbindService(connection);// 解绑Service
        });
    }
}

这里我们首先创建了一个ServiceConnection的匿名类实现,并在里面重写了onServiceConnected()方法和onServiceDisconnected()方法。

onServiceConnected()方法方法会在Activity与Service成功绑定的时候调用,而onServiceDisconnected()方法只有在Service的创建进程崩溃或者被杀掉的时候才会调用

在onServiceConnected()方法中,我们又通过向下转型得到了DownloadBinder的实例,有了这个实例,Activity和Service之间的关系就变得非常紧密了。现在我们可以在Activity中根据具体的场景来调用DownloadBinder中的任何public方法,即实现了指挥Service干什么Service就去干什么的功能。

当然,现在Activity和Service其实还没进行绑定呢,这个功能是在“Bind Service”按钮的点击 事件里完成的。可以看到,这里我们仍然构建了一个Intent对象,然后调用bindService()方 法将MainActivity和MyService进行绑定。bindService()方法接收3个参数,第一个参数就 是刚刚构建出的Intent对象,第二个参数是前面创建出的ServiceConnection的实例,第三 个参数则是一个标志位,这里传入BIND_AUTO_CREATE表示在Activity和Service进行绑定后 自动创建Service。这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。

10.4 Service的生命周期

一旦在项目的任何位置调用了Context的startService()方法,相应的Service就会启动,并回调onStartCommand()方法。 如果这个Service之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。

Service启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用,或者被系统回收。

注意,虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个Service只会存在一个实例。 所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,Service就会停止。

可以调用Context的bindService()来获取一个Service的持久连接,这时就会回调Service中的onBind()方法。 之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由地和Service进行通信了。 只要调用方和Service之间的连接没有断开,Service就会一直保持运行状态,直到被系统回收。

当调用了startService()方法后,再去调用stopService()方法。这时Service中的onDestroy()方法就会执行,表示Service已经销毁了。

类似地,当调用了bindService()方法后,再去调用unbindService()方法,onDestroy()方法也会执行

对一个Service既调用了startService()方法,又调用了bindService()方法的,在这种情况下该如何让Service销毁呢? 根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service才能被销毁。 所以,这种情况下要同时调用stopService()和 unbindService()方法,onDestroy()方法才会执行。

10.5 Service的更多技巧

10.5.1 使用前台Service

从Android 8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。

而如果你希望Service能够一直保持运行状态,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果

由于状态栏中一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。另外,用户也可以通过下拉状态栏清楚地知道当前什么应用正在运行,因此也不存在某些恶意应用长期在后台偷偷占用手机资源的情况。

package com.example.demo2.service;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;

import androidx.core.app.NotificationCompat;

import com.example.demo2.MainActivity;

public class MyService2 extends Service {
    public MyService2() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        Log.d("MyService", "onCreate executed");
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("my_service", "前台Service通知",
                    NotificationManager.IMPORTANCE_DEFAULT);
            manager.createNotificationChannel(channel);
        }
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
        Notification notification = new NotificationCompat.Builder(this, "my_service2")
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setContentIntent(pi)
                .build();
        startForeground(1, notification);
    }
}

这里调用了startForeground()方法。这个方法接收两个参数:第一个参数是通知的id,类似于notify()方法的第一个参数;第二个参数则是构建的Notification对象。调用startForeground()方法后就会让MyService变成一个前台Service,并在系统状态栏显示出来。

另外,从Android 9.0系统开始,使用前台Service必须在AndroidManifest.xml文件中进行权限声明才行

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> 

10.5.2 使用IntentService

Service中的代码都是默认运行在主线程当中的,如果直接在Service里处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。

所以这个时候就需要用到Android多线程编程的技术了,我们应该在Service的每个具体的方法里开启一个子线程,然后在这里处理那些耗时的逻辑。因此,一个比较标准的Service就可以写成如下形式:

class MyService : Service() { 
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { 
        thread { 
            // 处理具体的逻辑 
            stopSelf()
        } 
        return super.onStartCommand(intent, flags, startId) 
    } 
}