欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android基础 - Service实例深入理解

程序员文章站 2022-07-15 12:13:15
...

Service生命周期
上篇学习了Service一些基础知识,知道了在Service做耗时任务(网络请求、IO等等)需开启一个线程,否则会导致ANR,接下来通过实例加深Service的使用理解。

Service种类及特点

Android基础 - Service实例深入理解

Service几种典型实例

下面以模拟下载文件为例子

1、不可交互的后台服务

不可交互的后台服务即是普通的Service,是通过startService方法启动,其生命周期顺序是:onCreate -> onStartCommand -> onDestroy

/**
 * author:hzw on 2018/7/4
 * desc: 模拟下载 - 不可交互
 */

public class DownloadService extends Service {

    private static final String TAG = "DownloadService";

    public static final String FILE_SIZE="file_size";
     private Thread mDownloadThread;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: ");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int fileSize = intent.getIntExtra(FILE_SIZE, 0);
        Log.e(TAG, "onStartCommand: "+fileSize);
        //创建线程,模拟下载
        DownloadRunnable download = new DownloadRunnable(fileSize);
        mDownloadThread = new Thread(download);
        mDownloadThread.start();
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 定义一个下载线程
     */
    private class DownloadRunnable implements Runnable{
        //文件总大小
        private int fileSize;
        //下载进度大小
        private int downloadSize=0;
         DownloadRunnable(int fileSize) {
            this.fileSize=fileSize;
        }

        @Override
        public void run() {

            try {
                //模拟下载
                while (downloadSize<fileSize){
                    downloadSize +=100;
                    Log.d(TAG, "文件总大小:"+fileSize+"  已下载了:"+downloadSize);
                    Thread.sleep(1000);

                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    }
}

在AndroidMainfest文件中声明Service

<service android:name=".service.DownloadService"/>

注:
Service若没有在Mainfest文件中声明,并不会像Activity报崩溃异常,
对于显式Intent启动的Service,在mainfest中未声明,会提示”Unable to start service Intent”错误信息,
所以在开发中使用时需记住这点,少走弯路。
启动与停止Service

public void onClick(View view) {
        Intent intent = new Intent(this, DownloadService.class);
        intent.putExtra(DownloadService.FILE_SIZE,1000);
        switch (view.getId()){
            case R.id.start_service:
                startService(intent);
                break;
            case R.id.stop_service:
                stopService(intent);
                break;
        }
    }

最后输出结果:
Android基础 - Service实例深入理解

注:
在Service关闭销毁中,如果没有在onDestroy中停止关闭线程,线程执行操作会一直执行下去,也就是下载任务会一直存在,
因此在我们使用了Service启动了子线程执行一些耗时任务时,在服务结束onDestroy中必须停止线程关闭任务的执行。
是否需要在销毁停止线程,具体还是要根据业务需求而定,如下:

@Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
        if (!mDownloadThread.isInterrupted())mDownloadThread.interrupt();
    }

我们在使用onStartCommand方法时,它有一个返回参数,那么这个返回值究竟有什么作用呢,接下来看看源码:

  public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
        onStart(intent, startId);
        return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
    }

其中有三个参数

  1. intent:显式启动意图
  2. flags:默认情况下是0,对应的常量名为START_STICKY_COMPATIBILITY
  3. startId:startService请求标识,在多次调用startService方法情况下,startId的值会呈现递增(1,2,3…)

mStartCompatibility默认是false,说明onStartCommand默认返回值是START_STICKY,onStartCommand返回值有三种情况:

  • START_STICKY:当系统内存不足时导致进程杀死,接下来未来的某个时间内,Service会尝试重建,此时在onStartCommand方法中的Intent会为空,比较适合没有参数的服务。
  • START_NOT_STICKY:当Service因为内存不足而被系统进程杀死后,接下来未来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。
  • START_REDELIVER_INTENT:与START_STICKY类似,唯一不同是当重建Service时,onStartCommand方法中的Intent是非空的,比较适合一些紧急恢复数据的场景。
  • -

2、可交互的后台服务

所谓可交互的后台服务就是使用bindService启动服务,然后通过ServiceConnection获取IBinder代理对象,通过代理对象就可以得到Service的执行结果。其生命周期:onCreate -> onBind -> onUnbind ->onDestroy ,其中ServiceConnection的onServiceConnected方法会在onBind之后被调用。

Service代码:

**
 * author:hzw on 2018/7/4
 * desc: 模拟下载 - 可交互的服务
 */

public class DownloadService2 extends Service {

    private static final String TAG = "DownloadService";

    public static final String FILE_SIZE = "file_size";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: ");
    }


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        int fileSize = intent.getIntExtra(FILE_SIZE, 0);
        Log.e(TAG, "onBind: " + fileSize);
        return new DownloadBinder(fileSize);
    }


    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "onUnbind: ");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    }


    /**
     * 用于监听下载
     */
    public  interface OnDownloadListener{

        void onDownloadProgressUpdate(int progress);

        void onDownloadComplete();
    }

    public class DownloadBinder extends Binder {
       private int fileSize;
        private DownloadTask mDownloadTask;

        DownloadBinder(int fileSize) {
          this.fileSize=fileSize;
        }

        public void startDownload(OnDownloadListener onDownloadListener){
            //创建异步任务
             mDownloadTask = new DownloadTask(onDownloadListener);
            //执行任务
            mDownloadTask.execute(fileSize);

        }

        /**
         * 注意在onDestroy中取消异步操作,否则Service任务会一直存在
         */
        public void stopDownload(){
            mDownloadTask.cancel(true);
        }

    }

    private class DownloadTask extends AsyncTask<Integer, Integer, String> {

        private OnDownloadListener mOnDownloadListener;

        public DownloadTask(OnDownloadListener onDownloadListener) {
            this.mOnDownloadListener=onDownloadListener;
        }

        @Override
        protected String doInBackground(Integer... integers) {
            int fileSize = integers[0];
            int downloadSize = 0;
            String progress = "0%";

            try {
                //模拟下载
                while (downloadSize < fileSize) {
                    downloadSize += 200;
                    //发布进度
                    publishProgress(downloadSize);
                    progress = downloadSize / fileSize * 100 + "%";
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return progress;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            if (mOnDownloadListener!=null){
                mOnDownloadListener.onDownloadProgressUpdate(values[0]);
            }
            super.onProgressUpdate(values);

        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            if (mOnDownloadListener!=null){
                mOnDownloadListener.onDownloadComplete();
            }
        }
    }

}

Activity代码:

public class MainActivity extends AppCompatActivity implements DownloadService2.OnDownloadListener {

    private static final String TAG = "DownloadService";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void onClick(View view) {
        Intent intent = new Intent(this, DownloadService2.class);
        intent.putExtra(DownloadService2.FILE_SIZE,1000);
        switch (view.getId()){
            case R.id.bind_service:
                bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
                break;
            case R.id.unbind_service:
                unbindService(mServiceConnection);
                break;
        }
    }

    private ServiceConnection mServiceConnection=new  ServiceConnection(){
        /**
         * 服务绑定成功被调用
         * @param name 已绑定服务的组件名
         * @param service 已绑定的Binder
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            DownloadService2.DownloadBinder binder=(DownloadService2.DownloadBinder) service;
            binder.startDownload(MainActivity.this);

        }

        /**
         * 服务断开连接会调用,一般是进程被杀死的情况下。
         * @param name 已绑定服务的组件名
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "onServiceDisconnected: ");
        }
    };

    @Override
    public void onDownloadProgressUpdate(int progress) {
        Log.i(TAG, "onDownloadProgressUpdate: "+progress);
    }

    @Override
    public void onDownloadComplete() {
        Log.i(TAG, "onDownloadComplete: ");
    }
}

从绑定服务到下载完成,最后结果解绑的过程:
Android基础 - Service实例深入理解

用bindService方式启动服务,其生命周期会依附在Content的启动组件上,就是组件被销毁了,Service也会随之销毁。

3、前台服务

所谓前台服务,就是把进程的优先级提高了,一般是不可见状态变成了可见状态,前台服务会有一个一直运行在状态栏的图标显示,类似消息通知栏的效果。

前台服务的优点就是提高Service进程优先级,我们知道后台服务,在系统内存不足的情况,很容易杀死进程,而前台服务一定程度上可以弥补后台服务的缺陷,在Service中一个是通过startForeground方法来设置前台服务。

startForeground方法有两个参数:

 public final void startForeground(int id, Notification notification) {
        try {
            mActivityManager.setServiceForeground(
                    new ComponentName(this, mClassName), mToken, id,
                    notification, 0);
        } catch (RemoteException ex) {
        }
    }
  • id:通知的标识符
  • notification:显示的通知

还是以上面的例子进行设置,把后台服务提升为前台服务。

  @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: ");
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        //处理在Android8.0不显示问题
        Notification.Builder builder = new Notification.Builder(this);
        Notification mNotification;
        if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
            NotificationChannel channel = new NotificationChannel("notification_channel", "huawei", NotificationManager.IMPORTANCE_LOW);
            assert notificationManager != null;
            notificationManager.createNotificationChannel(channel);
            builder.setChannelId("notification_channel");
            mNotification = builder.setSmallIcon(R.mipmap.ic_launcher_round)
                    .setTicker("hello world")
                    .setWhen(System.currentTimeMillis())
                    .setContentText("模拟下载")
                    .setContentTitle("正在下载中……")
                    .setContentIntent(pendingIntent).build();

        }else {
            mNotification = builder.setSmallIcon(R.mipmap.ic_launcher_round)
                    .setTicker("hello world")
                    .setWhen(System.currentTimeMillis())
                    .setContentText("模拟下载")
                    .setContentTitle("正在下载中……")
                    .setContentIntent(pendingIntent).build();
        }

        startForeground(2, mNotification);


    }

Android基础 - Service实例深入理解

注意:1、使用bindService方式启动服务,如果Content的启动组件被销毁了,状态栏的通知也会消失。
2、可以通过stopsForeground方法取消通知栏,即把前台服务降到后台服务。

感谢

相关标签: Service