android service使用详解一

时间:2021-11-23 04:41:08
 2017年终总结中说要在android的坑中滚到底,并且今年要进一步提升Android基础技能,由于最近公司的项目实在很忙,导致博客更新一拖再拖,眼看再这么下去博客更新又要废掉了,赶紧抽空写一写

又是一年跳槽旺季,似乎好多人又开始蠢蠢欲动看机会,这时候又得面对各种面试。

于是你在简历上写:1.精通安卓四大组件;...

面试官:”那就简单说说service,有什么作用?与thread的关系?...“
你:“...”(卧槽,我真的了解service了吗?)
你口难开,第一感觉,耗时操作呗,具体点呢?说明还是不了解嘛,知道startService/stopService就是了解或者熟悉了?如果真正熟悉,一定不会说不上来,至于怎么表达应该没有什么标准答案。来回就是围绕着那些知识点表述而已。

嗯~废话少说,service会分几篇来讲,这是第一篇从最简单的使用开始说起。

  • service 简单上手使用(startService/stopService)
  • service 在manifest配置文件中参数说明
  • service 与Activity(其它组件)通信方式
  • service 两种启动方式关系说明
  • service onStartCommand返回值详解
  • service 与thread的关系

service 简单上手使用(startService/stopService)

service通常叫做后台服务,说明它是在后台(通常情况下)为其他组件提供服务的,没有用户交互页面,启动之后长时间存在,直到被主动停止或者系统内存不足kill掉。

继承Service实现ServiceDemo
public class LocalService extends Service {

    public static final String TAG = "LocalService";

    @Override
    public void onCreate() {
        super.onCreate();
        LogUtil.outE(TAG, "onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtil.outE(TAG, "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        LogUtil.outE(TAG, "onDestroy");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    }
代码很简单,没有任何业务逻辑,重写生命周期,打印log方便调试。service是基本组件,跟activity一样需要在  `manifest` 中注册之后才能正常使用
<service android:name=".service.LocalService" />
这里插入说明,如果没有特殊需求,一般只需要在  `manifest` 注册service就可以了。针对项目中不同的需求,service组件的配置参数如下:
    <service android:enabled=["true" | "false"]
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:isolatedProcess=["true" | "false"]
    android:label="string resource"
    android:name="string"
    android:permission="string"
    android:process="string" >
    . . .
    </service>

跟activity相比也很多相似的声明,例如:label,name,icon,主要讲解一下特殊的声明:

  • android:exported :是否能被其他应用隐式调用,跟activity一样,其他应用可以通过隐式调用唤起activity/service,如果设置为 false 则无法被其他应用调用。设置为true时,根据intent-filter匹配。默认值:如果有intent-filter,默认值为true,否则为false
  •  android:process :是否在单独进程中运行。当设置为android:process=":remote",代表此service在单独的进程中运行,注意" **:** " 很重要,意思是在当前进程名称前附加应用包名,"remote" 和 " :remote " 不是同一个意思。"remote"表示进程名称为 `remote` ; " :remote " 表示进程名称为 `app包名:remote` 
    特殊备注:
    1.使用process会有一个坑,设置之后会导致Application的onCreate调用多次,可以通过当前进程名称判断是否做主进程初始化的逻辑。
    2."remote"就是名称前面没有 ** : ** 的情况,不允许设置单独一个单词,会安装失败,通过测试,发现使用类似`com.remote` 或者  `.remote` 都可以,就是不允许一个单词 `remote` 
  • android:permission :权限声明
  • android:enabled :是否可以被系统实例化,默认为 true。父标签 也有 enable 属性,必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活
starService()/stopService
启动服务,很简单,构建Intent,调用  `startService`
     Intent intent = new Intent(MainActivity.this, LocalService.class);
     startService(intent);

启动服务之后,service就一直运行在主线程中,如果没有逻辑操作,什么变化都没有,也没有返回值。

android service使用详解一

通过log可以看到,start之后service就一直运行着,即使activity已经销毁了,service一样运行在后台。

> 第一次调用 startService 调用 onCreate -> onStartCommand 
> 没有stop情况下,多次调用 startService 不再调用 onCreate 但是会多次调用 onStartCommand。
> 例如连续调用三次 startService
> onCreate -> onStartCommand 
>  onStartCommand 
>  onStartCommand

如果想要让service停止运行,需要调用stopService,或者在service处理完逻辑操作调用stopSelf函数才能让service停止运行
        Intent intent = new Intent(MainActivity.this, LocalService.class);
        stopService(intent);
在activity的  `onDestroy` 中调用 stopService,查看日志,发现service停止运行,并回调了  `onDestroy`

android service使用详解一

嗯?就这样?我去,service简单的一比呀,我会了,关掉网页,打一把游戏先。

如果候面试官问了一个问题:service与activity之间怎么进行数据交互?有几种方式?

你:这个。。。service在项目中很少用,所以不是很清楚
(所以说还是你不了解,很少使用?这就是你不会的借口?)

这时候面试官提示你:bindService知道吗?

你心里窃喜:知道,知道,绑定service嘛,第二种启动srevice的方式。

面试官:那你讲讲bindService怎么使用?为什么要用bindService?使用bindService的同时startService的情况下如何停止service??

你:...请问电梯是出门左拐吗?我先回去了~~~

OK,接下来就详细讲讲service与其它组件之间的通信方式,以及bindSercice相关的知识,希望学完之后不会再出现上面所说的打脸场景

service 与Activity(其它组件)通信方式

  • 通过Binder对象
这里我们学习bindService的使用,activity通过绑定service返回Binder得到service的实例,通过service的实例去调用service中的方法。
其实这个方式思想还是很简单的,因为service这种带生命周期的组件,不适合通过 new 创建对象,所以通过Binder得到service的实例,然后就可以用这个实例访问service的方法和成员了
        private LocalService localService;

        ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            LogUtil.outE(TAG, "onServiceConnected " + componentName);
            localService = ((LocalService.MyBinder) iBinder).getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            LogUtil.outE(TAG, "onServiceDisconnected " + componentName);
        }
    };
通过 ServiceConnection 得到 IBinder ,调用 getService() (自己写的方法)得到 service 实例。

     Intent intent = new Intent(MainActivity.this, LocalService.class);
     bindService(intent, connection, BIND_AUTO_CREATE);
bindService 也很简单,再看一下service中代码,修改 onBind
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }
MyBinder 继承 Binder ,添加一个获取service实例的方法

    public class MyBinder extends Binder {
        public LocalService getService() {
            return LocalService.this;
        }
    }
> bindService 除了调用方式跟 startService 有所区别。其他逻辑还是一样的,生命周期执行也是首次绑定时调用 onCreate-> onStartCommand
> 之后调用都只调用 onStartCommand

现在模拟一个后台下载文件的demo详细看看通过Binder通信的方式,先看看service实现

    public class DownloadService extends Service {

    //最大值
    public static final int MAX_PROGRESS = 100;
    //是否正在下载
    private boolean isRunning = false;
    //进度
    private int progress = 0;

    //获取进度
    public int getProgress() {
        return progress;
    }

    /**
     * 模拟下载任务
     */
    public void startDownload() {
        if (!isRunning) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    progress = 0;
                    isRunning = true;

                    while (isRunning) {
                        if (progress < MAX_PROGRESS) {
                            progress += 5;
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        } else {
                            isRunning = false;
                        }
                    }
                }
            }).start();
        }
    }

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

    public class MyBinder extends Binder {
        public DownloadService getService() {
            return DownloadService.this;
        }
    }

    }
这代码再简单不过了,应该都能看懂,启动线程,每秒更新进度, `startDwonload` 和  `getProgress` 提供给外部调用,下载方法中做一个简单的判断,当前是否正在下载中,避免用户多次调用导致逻辑错误。

再看一下activity代码
    public class MainActivity extends AppCompatActivity {

    private DownloadService mService;
    private ProgressBar mProgressBar;
    private int progress = 0;
    private boolean isRunning = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        //绑定
        Intent intent = new Intent(MainActivity.this, DownloadService.class);
        bindService(intent, connection, BIND_AUTO_CREATE);
        //下载
        findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mService.startDownload();
                listenProgress();
            }
        });
    }

    /**
     * 监听进度,每秒钟获取调用DownloadService的getProgress()方法来获取进度,更新UI
     */
    public void listenProgress() {
        if (!isRunning) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    progress = 0;
                    isRunning = true;

                    while (isRunning) {
                        if (progress < DownloadService.MAX_PROGRESS) {
                            progress = mService.getProgress();//调用service方法回去进度
                            mProgressBar.setProgress(progress);
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        } else {
                            isRunning = false;
                        }
                    }
                }
            }).start();
        }
    }

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mService = ((DownloadService.MyBinder) iBinder).getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
    }
activity的代码也不难,这里先不管代码风格和编程方式是否规范,为了让逻辑直观和简介,一切从简,只为更好理解。这里需要注意的一点是,更新UI是要在主线程中操作,ProgressBar 这个控件估计是内部做了处理,这里先不管了

OK,到这里,我们使用了bindService模拟了下载任务,实现了service和activity之间的数据交互。但是!!!但是不觉得这个业务使用这种主动获取数据的方式很麻烦吗?需要activity主动询问service数据。还有什么方式?

其实前辈们一直强调说基础要打扎实,只有基础扎实了才能将知识融会贯通,使用更优雅的方式解决问题。一般多线程之间的数据交互方式有哪些呢?回调?广播?...
  • 回调函数
新增一个回调接口
     public interface OnProgressListener {
        void onProgress(int progress);
    }
  service业务逻辑中,进度变化时设置进度
    public class DownloadService extends Service {

    //最大值
    public static final int MAX_PROGRESS = 100;
    //是否正在下载
    private boolean isRunning = false;
    //进度
    private int progress = 0;

    //获取进度
    public int getProgress() {
        return progress;
    }

    /**
     * 模拟下载任务
     */
    public void startDownload() {
        if (!isRunning) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    progress = 0;
                    isRunning = true;

                    while (isRunning) {
                        if (progress < MAX_PROGRESS) {
                            progress += 5;

                            //进度设置
                            if (mListener != null) {
                                mListener.onProgress(progress);
                            }

                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        } else {
                            isRunning = false;
                        }
                    }
                }
            }).start();
        }
    }

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

    public class MyBinder extends Binder {
        public DownloadService getService() {
            return DownloadService.this;
        }
    }

    /**
     * 定义回调接口
     */
    public interface OnProgressListener {
        void onProgress(int progress);
    }

    //回调接口
    private OnProgressListener mListener;

    /**
     * 设置监听
     *
     * @param listener
     */
    public void setOnProgressListener(OnProgressListener listener) {
        this.mListener = listener;
    }
    }

然后在activity中修改为监听数据,不用再主动获取

    public class MainActivity extends AppCompatActivity {

    private DownloadService mService;
    private ProgressBar mProgressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        //绑定
        Intent intent = new Intent(MainActivity.this, DownloadService.class);
        bindService(intent, connection, BIND_AUTO_CREATE);
        //下载
        findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mService.startDownload();
            }
        });
    }

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mService = ((DownloadService.MyBinder) iBinder).getService();
            /**
             * 设置监听
             */
            mService.setOnProgressListener(new DownloadService.OnProgressListener() {
                @Override
                public void onProgress(int progress) {
                    mProgressBar.setProgress(progress);
                }
            });
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
    }
舒服,不用定时去查询获取数据,只要设置一个监听,当service有数据更新时就会回调,activity只需要收到数据更新UI就可以了。等一下,什么是回调函数?这可能需要你去查查资料哦,如果到现在为止你还不知道什么是回调函数,,那你的路就还很长哦,赶紧去补!

可能很多小伙伴听到回调函数的时候就想到另外一种方式了:广播。也很相似的思路,service的数据发生改变的时候主动发送广播,在activity中设置广播监听,收到数据更新UI
  • 广播
其实这个真的不应该再一一贴代码出来了,广播都会用的啦,想了想,还是贴吧。手把手。完整代码全给了,如果这都还不懂,,,,,goHome,please!
service代码
    public class DownloadService extends Service {

    //最大值
    public static final int MAX_PROGRESS = 100;
    //是否正在下载
    private boolean isRunning = false;
    //进度
    private int progress = 0;

    //广播intent
    private Intent intent = new Intent("com.ruffian.download.action");

    /**
     * 模拟下载任务
     */
    public void startDownload() {
        if (!isRunning) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    progress = 0;
                    isRunning = true;

                    while (isRunning) {
                        if (progress < MAX_PROGRESS) {
                            progress += 5;

                            //发送广播
                            intent.putExtra("progress", progress);
                            sendBroadcast(intent);

                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        } else {
                            isRunning = false;
                        }
                    }
                }
            }).start();
        }
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startDownload();
        return super.onStartCommand(intent, flags, startId);
    }
    }
activity中添加广播监听,别忘了注册和注销广播,说明一下:使用广播的情况不一定要 bindService 的方式,startService 也是可以的
   public class MainActivity extends AppCompatActivity {

    private ProgressBar mProgressBar;
    private MyReceiver myReceiver;
    private Intent mIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        //start
        mIntent = new Intent(MainActivity.this, DownloadService.class);
        //注册广播
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.ruffian.download.action");
        myReceiver = new MyReceiver();
        registerReceiver(myReceiver, intentFilter);

        //下载
        findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startService(mIntent);
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除绑定
        stopService(mIntent);
        //注销广播
        unregisterReceiver(myReceiver);
    }

    /**
     * 广播接收器
     */
    public class MyReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            //拿到进度,更新UI
            int progress = intent.getIntExtra("progress", 0);
            mProgressBar.setProgress(progress);
        }
    }
    }
> service与activity(或者其它组件)的通信方式有以上3种,根据业务需要去选择最合适的一种,比如下载这种业务使用回调会是不错的选择;少量数据的获取也可使用bindservice得到service实例主动获取;对于多个activity都需要获取数据的业务使用广播则更方便一点。

 service 两种启动方式关系说明

学会了 startService 和 bindService 。是不是又想急着去打游戏了呢?如果面试官再问你:如果使用 startService 之后再调用 bindService ,要如何停止服务呢?bind 之后再 start 的情况呢? 

卧槽,,,要不要问这么细?我已经做的很好了!还问! 巧了,真就有面试官会这么干,看你到底掌握了多少,深度多深~~

莫慌,给一句话: **只要调用了 start 就必须用 stop 才能停止服务。如果单一 bind 那么 unBind 就可以停止服务了**

service onStartCommand返回值详解

到现在为止,基本上把service的基础知识过了一遍,回过头来看看service的类,onCreate ,onDestory 没什么可讲的,但是这个  `onStartCommand` 好像还没仔细研究一下,是不是之前还见过 onStart 呢?

卧槽,现在是不是又想问 onStart 跟 onStartCommand 的关系?

没错,我就是想问,你砍我呀。

这个比较简单,onStart 是 <= API5之前的回调,在 >API5 之后这个方法不直接回调了,而是放在 onStartCommand 中, 看一下源码
     public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
     }
所以实现service时候直接重新 onStartCommand 就可以了。这里的intent 是启动组件传递过来的Intent。实际上 onStartCommand 的返回值 int 类型才是最最值得注意的,它有4种可选值
START_STICKY
START_NOT_STICKY
START_REDELIVER_INTENT
START_STICKY_COMPATIBILITY

具体说明一下:

  • START_STICKY :当service因内存不足被系统kill掉后,一段时间后当内存再次空闲时,会重启service ,回调 onStartCommand 方法,START_STICKY状态下,startId 改变,重启时 intent==null
  • START_NOT_STICKY : 当service因内存不足被系统kill掉后,一段时间后当内存再次空闲时,系统不再自动重启service
  • START_REDELIVER_INTENT :当service因内存不足被系统kill掉后,一段时间后当内存再次空闲时,会重启service ,回调 onStartCommand 方法,START_REDELIVER_INTENT 状态下,startId 不变,重启时 intent == 后一次调用startService中的intent
  • START_STICKY_COMPATIBILITY :当service因内存不足被系统kill掉后,一段时间后当内存再次空闲时,会重启service,但是只调用 oncreate 方法,没有调用 onStartCommand 

由于每次启动服务(调用startService)时,onStartCommand方法都会被调用,因此我们可以通过该方法使用Intent给Service传递所需要的参数,然后在onStartCommand方法中处理的事件,最后根据需求选择不同的Flag返回值,以达到对程序更友好的控制。

service 与thread的关系

前面的例子中我们都看到了,在service中需要创建thread线程做业务逻辑,耗时操作。所以之前一直说用service来做耗时操作的说法可能是错误的喔~
其实service运行在主线程(UI线程)
那么问题来了,说好的在service中做耗时操作呢?在主线程中运行,那么超过10s不是ANR了?
我觉得:这里需要把service当做一个对象来理解,他运行在主线程中,start之后就一直运行,但是所谓的耗时操作是需要创建Thread来实现,一个thread的逻辑处理完了,thread结束了,但是service还是运行着的,直到调用stop或者被系统kill掉。

所以开口就说用service做耗时操作,估计就是在网上看了一下面试答案就来了。应该是在service的工作线程中才能做耗时操作,如果你质疑我,自己去尝试,直接在 `onCreate()` 中休眠20s,必定ANR

    @Override
    public void onCreate() {
        super.onCreate();
        LogUtil.outE(TAG, "onCreate");
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
所以现在要搞明白了,service确实运行在主线程,所谓的耗时操作实在service的工作线程中进行的。

那么service 与thread 之间有什么关系呢? 这个可以给肯定答案: 没有半毛钱关系!

thread: 是程序执行的最小单元,它是分配CPU的基本单位,android UI线程也是thread的一种,thread常用来做异步耗时的操作。

service: android的一个组件,运行在主线程中,由系统托管,并不能用来执行耗时操作,常说的Service后台任务只不过是指没有UI的组件罢了


这么说service要做耗时任务都是要创建thread咯?不是的,体统提供了IntentService,他是service的一个实现类,就是为了解决在service中做耗时操作处理而诞生的,原理还是创建thread,只不过开发者不需要关心这部分逻辑,由于篇幅太长,这部分知识放到下一篇博客中将。

看客们可以持续关注,只要你坚持不懈,我就可以一步一步带你到饿死的地步~~~总是有一些什么小程序,快应用什么的想来抢饭碗,想让安卓开发者饿死,然而呢?不存在的,只要我们够吊~~~