Android《第二章 : Service》

作者: 泅渡者 | 来源:发表于2017-09-20 17:31 被阅读43次

    Service

    Service是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要长期运行的任务。
    但是不要被“后台”所迷惑,Service 并不在子线程运行,同时也不运行在一个独立的进程,它同样在UI线程中执行,所以不要在Service进行耗时操作,除非你在Service中创建了单独的线程来完成耗时操作。
    Service的运行不依赖任何用户界面,即使程序切换到后台或者打开另一个应用Service都会保持正常运行。但是如果整个应用进程被杀掉时所有依赖于该进程的Service也会停止运行。

    Service基本用法

    关于Service最基本的用法自然就是如何启动一个Service了,启动Service的方法和启动Activity很类似,都需要借助Intent来实现。

    注:Service启动方式有两种:显式启动、隐式启动,在同一应用中两种都可用,但在不同应用之间只能用隐式方式。需要注意的是在5.0上采用隐式启动时,会出现java.lang.IllegalArgumentException: Service Intent must be explicit异常,也就是说Service的Intent必须明确, 如下需要指明Package:
    final Intent intent = new Intent();
    intent.setAction("com.bsoft.messengerserver");
    intent.setPackage("com.bsoft.messengerserver");
    bindService(intent, mConnect, Service.BIND_AUTO_CREATE);
    这里用个混淆点:
    service启动方法有两种startService和bindService。
    service启动方式有两种显式和隐式。

    下面我们就通过一个具体的例子来看一下。
    新建一个Android项目,项目名就叫ServiceTest(注:这里的KLog是一个日志打印库)

    compile 'com.github.zhaokaiqiang.klog:library:+'

    /**
     * Created by 泅渡者
     * Created on 2017/9/20.
     */
    
    public class APP extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            KLog.init(true);//日志打印初始化
        }
    }
    
    
    /**
     * Created by 泅渡者
     * Created on 2017/9/20.
     */
    
    public class MyService extends Service {
    
    
        @Override
        public void onCreate() {
            super.onCreate();
            KLog.d("onCreate() 被执行");
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            KLog.d("onStartCommand() 被执行");
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            KLog.d("onDestroy() 被执行");
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            KLog.d("onBind() 被执行");
            return null;
        }
    }
    

    我们在MainActivity中新建布局如下


    image.png

    接下来我们看下如何启动Service:

    /**
     * Created by 泅渡者
     * Created on 2017/9/20.
     */
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private Button btn_start, btn_stop, btn_bind, btn_unbind;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            btn_start = (Button) findViewById(R.id.btn_start);
            btn_stop = (Button) findViewById(R.id.btn_stop);
            btn_bind = (Button) findViewById(R.id.btn_bind);
            btn_unbind = (Button) findViewById(R.id.btn_unbind);
    
            btn_start.setOnClickListener(this);
            btn_stop.setOnClickListener(this);
            btn_bind.setOnClickListener(this);
            btn_unbind.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
    
            switch (v.getId()) {
                case R.id.btn_start:
                    Intent startIntent = new Intent(this, MyService.class);
                    startService(startIntent);
                    break;
                case R.id.btn_stop:
                    Intent stopIntent = new Intent(this, MyService.class);
                    stopService(stopIntent);
                    break;
                case R.id.btn_bind:
                    break;
                case R.id.btn_unbind:
                    break;
            }
        }
    }
    

    别忘了在Manifest.Xml中进行注册。

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

    接下来我们看下运行结果:点击启动Service

    /MyService.java: [ (MyService.java:21)#onCreate ] onCreate() 被执行
    /MyService.java: [ (MyService.java:26)#onStartCommand ] onStartCommand() 被执行
    

    在启动Service时执行了OnCreat()、onStartCommand ()这两个方法,如果我们再次点击启动呢?

    /MyService.java: [ (MyService.java:21)#onCreate ] onCreate() 被执行
    /MyService.java: [ (MyService.java:26)#onStartCommand ] onStartCommand() 被执行
    /MyService.java: [ (MyService.java:26)#onStartCommand ] onStartCommand() 被执行
    

    可见再次点击时只会执行onStartCommand(),那是因为Service已经创建好了实例再次点击只会复用当前实例。
    此时我们去验证下如果把应用切到后台运行,我们的 Service会不会停掉。
    这是我的截图

    image.png

    ServiceTest确实是正在运行的,即使它的内部并没有执行任何的逻辑。
    回到ServiceTest程序,然后点击一下Stop Service按钮就可以将MyService停止掉了。

    Service和Activity通信

    上面的MyService只是简单的演示了创建过程,并没有执行任何事务。也没有和Activity做关联,只是Activity通知了MyService“你可以启动了“的指令。怎么才能将Service 和Activity进行关联呢?
    我们可以看到上面的MyService中有个onBind()我们一直没有用,这个方法其实就是用于和Activity建立关联的,修改MyService中的代码,如下所示:

    /**
     * Created by 泅渡者
     * Created on 2017/9/20.
     */
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private Button btn_start, btn_stop, btn_bind, btn_unbind;
        private MyService.MyBinder myBinder;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            btn_start = (Button) findViewById(R.id.btn_start);
            btn_stop = (Button) findViewById(R.id.btn_stop);
            btn_bind = (Button) findViewById(R.id.btn_bind);
            btn_unbind = (Button) findViewById(R.id.btn_unbind);
    
            btn_start.setOnClickListener(this);
            btn_stop.setOnClickListener(this);
            btn_bind.setOnClickListener(this);
            btn_unbind.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
    
            switch (v.getId()) {
                case R.id.btn_start:
                    Intent startIntent = new Intent(this, MyService.class);
                    startService(startIntent);
                    break;
                case R.id.btn_stop:
                    Intent stopIntent = new Intent(this, MyService.class);
                    stopService(stopIntent);
                    break;
                case R.id.btn_bind:
                    /**
                     * 1).Context.BIND_AUTO_CREATE
                     *说明:表示收到绑定请求的时候,如果服务尚未创建,则即刻创建,在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧毁
                     *2).Context.BIND_DEBUG_UNBIND
                     *说明:通常用于调试场景中判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用
                     *3).Context.BIND_NOT_FOREGROUND
                     *说明:表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行,该标志位位于Froyo中引入。
                     */
                    Intent bindIntent = new Intent(this, MyService.class);
                    bindService(bindIntent, connection, BIND_AUTO_CREATE);
                    break;
                case R.id.btn_unbind:
                    unbindService(connection);
                    break;
            }
    
        }
    
        private ServiceConnection connection = new ServiceConnection() {
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
    
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                myBinder = (MyService.MyBinder) service;
                myBinder.Download();
            }
        };
    
    }
    

    接下来我们运行程序看看我们的效果如何:

    /MyService.java: [ (MyService.java:24)#onCreate ] onCreate() 被执行
    /MyService.java: [ (MyService.java:42)#onBind ] onBind() 被执行
    /MyService.java: [ (MyService.java:49)#Download ] Download() 下载任务被执行
    ##点击解绑后的效果
    /MyService.java: [ (MyService.java:36)#onDestroy ] onDestroy() 被执行
    

    那如果我们先点击启动Service再点击绑定呢?

    ##点击创建后
    /MyService.java: [ (MyService.java:24)#onCreate ] onCreate() 被执行
    /MyService.java: [ (MyService.java:29)#onStartCommand ] onStartCommand() 被执行
    ##点击绑定后
    /MyService.java: [ (MyService.java:42)#onBind ] onBind() 被执行
    /MyService.java: [ (MyService.java:49)#Download ] Download() 下载任务被执行
    

    此时当我们点击 “解除绑定”会发现没有任何效果,但是如果我们再次点击 就会发现报错了!(错误信息是我们的Service没有注册)

     java.lang.IllegalArgumentException: Service not registered: com.bsoft.servicetest.MainActivity$1@7079a45
    

    一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁。(这两个步骤不论谁先谁后均可)

    Sevice.png

    Service和Thread

    大部分人会觉得Service和Thread一样都是运行在后台处理耗时操作的!可真正的答案往往不是如此,Service和Thread完全没有关系,从概念来说Service是Android中实现程序后台运行 的解决方案,它非常适合去执行那些不需要和用户交互而且还要长期运行的任务。Thread是开辟子线程处理耗时操作。但是千万别被Service“后台”所迷惑,Service也是运行在UI线程。

    前台Service

    Service几乎都是在后台(是指与用户没有直接的操作交互)运行的,一直以来它都是默默地做着辛苦的工作,但是Service的系统优先级别还是比较低。当出现内存不足的情况就会回收掉正在后台运行的Service。如果我们想让Service可以一直保持运行,而不会被系统在内存不足时回收掉,那么可以将Service运行在前台。比如墨迹天气在前台运行了一个Service,并且在Service中定时更新通知栏上的天气信息。
    下面我们来创建一个前台Service:

    
    /**
     * Created by 泅渡者
     * Created on 2017/9/21.
     */
    
    public class WeatherService extends Service {
    
        private static final int NOTIFY_KEY = 0x11;
    
        @Override
        public void onCreate() {
            super.onCreate();
            showNotifycation();
            KLog.d("被执行");
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            KLog.d("被执行");
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        /**
         * Into the Senate :
         * Return:
         * 显示通知
         */
        private void showNotifycation() {
            NotificationCompat.Builder mBilder = new NotificationCompat.Builder(this)
                    .setSmallIcon(R.mipmap.icon_weather)
                    .setContentTitle(new Date().toString())
                    .setContentText("今天天气晴");
            Intent resultIntent = new Intent(this, MainActivity.class);
    
            TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
            stackBuilder.addParentStack(MainActivity.class);
            stackBuilder.addNextIntent(resultIntent);
    
            PendingIntent requestPending = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
    
            mBilder.setContentIntent(requestPending);
    
            NotificationManager mNotify = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            final Notification notify = mBilder.build();
    
            mNotify.notify(NOTIFY_KEY, notify);
    
            //启动前台服务
            startForeground(NOTIFY_KEY, notify);
            KLog.d("被执行");
    
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            KLog.d("被执行");
        }
    }
    
    

    在Activity中我们修改如下

                Intent startIntent = new Intent(this, WeatherService.class);
                startService(startIntent);
    

    运行效果如下:

    前台服务.png

    下一章我会介绍AIDL 和Messenger ,这两个可是很有必要去看看的。

    所有代码上传至Git:https://gitee.com/13102169005/Android_Projects.git

    相关文章

      网友评论

        本文标题:Android《第二章 : Service》

        本文链接:https://www.haomeiwen.com/subject/uevnsxtx.html