美文网首页
Android四大组件---Service详解

Android四大组件---Service详解

作者: 善笃有余劫 | 来源:发表于2018-05-24 08:17 被阅读27次

    Service详解

    参考文章:https://blog.csdn.net/zxw136511485/article/details/53537993

    service(服务)是安卓中的四大组件之一,它通常用作在后台处理耗时的逻辑,与Activity一样,它存在自己的生命周期,也需要在清单文件中配置相关信息。

    Service的使用和启动方式

    继承Service,实现onBind方法

    (在生命周期里面打上日志)

    public class TestService extends Service {
        private static final String TAG = "TestService";
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            Log.e(TAG, "--------->onCreate: ");
        }
    
        @Override
        public void onStart(Intent intent, int startId) {
            super.onStart(intent, startId);
            Log.e(TAG, "--------->onStart: ");
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.e(TAG, "--------->onStartCommand: ");
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onDestroy() {
            Log.e(TAG, "--------->onDestroy: ");
            super.onDestroy();
        }
    }
    

    AndroidManifest.xml注册一下Service

    <service android:name=".TestService">
                <intent-filter>
                    <action android:name="com.baiheng.androidcomponent.service" />
                </intent-filter>
            </service>
    

    其中name节点将用于Server的启动匹配

    Server的两种启动方式startServicebindService

    startService方式启动和停止

    ps:关于Service的Intent

    //5.0之后不能z这样
            //Intent intent = new Intent("com.baiheng.androidcomponent.service");
      intent = new Intent(MainActivity.this,TestService.class);
    

    所以需要修改成下面这种方式
    Intent intent = new Intent(MainActivity.this,TestService.class);

    btStartService.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent("com.baiheng.androidcomponent.service");
                    startService(intent);
                }
            });
    
            btStartStop.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent("com.baiheng.androidcomponent.service");
                    stopService(intent);
                }
            });
    

    查看一下生命周期

    启动生命周期:

     E/TestService: --------->onCreate:
        --------->onStartCommand:
        --------->onStart:
    

    如果不停止,直接再次点击启动:

    E/TestService: --------->onStartCommand:
        --------->onStart:
    

    停止生命周期:

    E/TestService: --------->onDestroy:
    
    • 1.可以看出,如果不停止直接startService 少掉一个onCreate的生命周期。也就是说如果Service没有被销毁,onCreate只会一次

    • 2.onStartCommand()和onStart()的有什么关系。

      在API 2.0之前,只有onStart()方法,而2.0之后,推荐使用onStartCommand()方法,其实onStartCommand()内部也是调用了onStart()方法,所以我们在开发中只重写onStartCommand()就可以了,而onStartCommand()方法的作用是告诉系统如何重启服务,如判断是否异常终止后重新启动,在何种情况下异常终止 等等。

    在Service搞出一个ANR异常:

    ANR触发条件:

    1.只有主线程才会产生ANR,主线程就是UI线程;![![screenshot.png](https://upload-
    2.必须发生某些输入事件或特定操作,比如按键或触屏等输入事件,在BroadcastReceiver或Service的各个生命周期调用函数;
    3.上述事件响应超时,不同的context规定的上限时间不同

    • a.主线程对输入事件5秒内没有处理完毕
    • b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕
    • c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。

    onCreate搞一个50秒的延时操作

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "--------->onCreate: ");
        try {
            Thread.sleep(50*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    

    ANR直接界面无响应了

    05-23 15:44:11.783 452-470/system_process E/ActivityManager: ANR in com.baiheng.androidcomponent
        PID: 15898
        Reason: Executing service com.baiheng.androidcomponent/.TestService
    

    这说明如果Service在主线程中,如果有耗时任务必须使用子线程。

    通过bindService()启动Service

    使用bindService()启动Service,这种模式是客户端-服务端模式(client-server),即服务端是Service,客户端是调用者,例如是某个Activity,客户端-服务端是可以相互通信的。

    方法如下:

    bindService(Intent service, ServiceConnection conn,int flags)
    
    • 第一个参数:Intent指示对应的Service对象;
    • 第二个参数:实现了 ServiceConnection接口的对象,conn是一个代表与service连接状态的类,当我们连接service成功或失败时,会主动触发其内部的onServiceConnected或onServiceDisconnected方法。如果我们想要访问service中的数据,可以在onServiceConnected()方法中进行实现;
    • 第三个参数:Flags,在进行服务绑定时,其标志位可以为BIND_AUTO_CREATE、BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND等。其中BIND_AUTO_CREATE表示当收到绑定请求时,如果服务尚未创建,则即刻创建,在系统内存不足,需要先销毁优先级组件来释放内存,且只有驻留该服务的进程成为被销毁对象时,服务才可被销毁;BIND_DEBUG_UNBIND通常用于调试场景中判断绑定的服务是否正确,但其会引起内存泄漏,因此非调试目的不建议使用;BIND_NOT_FOREGROUND表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行,该标志位在Froyo中引入;

    如果需要通信,需要在Service中添加Binder

     private MyBinder binder = new MyBinder();
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            Log.e(TAG, "--------->onBind: ");
            return binder;
        }
    
        @Override
        public boolean onUnbind(Intent intent) {
            Log.e(TAG, "--------->onUnbind: ");
            return super.onUnbind(intent);
        }
    
        /**
         * 得到Service
         */
        class MyBinder extends Binder {
            public TestService getService() {
                return TestService.this;
            }
        }`
    

    添加两个生命周期的监听: onBind和unBind 分别对应绑定和解绑
    MyBinder继承Binder,并返回Server对象函数
    最后在onCread中 返回了 binder 对象用于通信

    好的,来onBind一下:

     btServiceOnbind.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent("com.baiheng.androidcomponent.service");
                    bindService(intent, MainActivity.this, Context.BIND_AUTO_CREATE);
                }
            });
    //这里时解绑
    btServiceUnbind.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    unbindService(MainActivity.this);
                }
            });
    
    @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "-------------->onServiceConnected");
            testService = ((TestService.MyBinder) service).getService();
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "-------------->onServiceDisconnected");
            testService = null;
        }
    

    具体参数已解释,值得注意的是,在ServiceConnection接口中两个方法:

    onServiceConnected:连接时调用,用于得到Service对象

    onServiceDisconnected:onServiceDisconnected()方法在正常情况下是不被调用的,它的调用时机是当Service服务被异外销毁时,例如内存的资源不足时这个方法才被自动调用。

    点击一下:生命周期

    E/TestService: --------->onCreate:
        --------->onBind:
    E/MainActivity: -------------->onServiceConnected
    

    再点一次:

    E/MainActivity: -------------->onServiceConnected
    

    可以看到只会onServiceConnected,而不会再次onCreate和onBind

    点击解绑:

     E/TestService: --------->onUnbind:
        --------->onDestroy:
    
    

    值得注意的是:如果在没有绑定Service的情况下调用unbind,则会崩溃

    java.lang.IllegalArgumentException: Service not registered: com.baiheng.androidcomponent.MainActivity@422fca80
    

    提示说Service没有注册。因此,我们在解除绑定(调用unbindService())时,需要多注意。

    可以添加一个isbind参数 是否已经bind

    btServiceOnbind.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent("com.baiheng.androidcomponent.service");
                    bindService(intent, MainActivity.this, Context.BIND_AUTO_CREATE);
                    isbind=true;
                }
            });
    
            btServiceUnbind.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(isbind) {
                        unbindService(MainActivity.this);
                        isbind=false;
                    }
                }
            });
    

    如果直接bind后,不unbind销毁当前的Activity,则会出现错误:

    E/ActivityThread: Activity com.baiheng.androidcomponent.MainActivity has leaked ServiceConnection com.baiheng.androidcomponent.MainActivity@422f95b8 that was originally bound here
    android.app.ServiceConnectionLeaked: Activity com.baiheng.androidcomponent.MainActivity has leaked ServiceConnection com.baiheng.androidcomponent.MainActivity@422f95b8 that was originally bound here
    

    所以在onDestroy记得解绑:

     @Override
        protected void onDestroy() {
            super.onDestroy();
            if(isbind) {
                unbindService(MainActivity.this);
                isbind=false;
            }
        }
    

    通过startService()和bindService()同时启动Service

    启动函数

    btServiceOnbindStart.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent("com.baiheng.androidcomponent.service");
                    startService(intent);
                    bindService(intent, MainActivity.this, Context.BIND_AUTO_CREATE);
                    isbind=true;
                }
            });
    

    生命周期,基本上就是合起来了:

    E/TestService: --------->onCreate:
        --------->onStartCommand:
        --------->onStart:
        --------->onBind:
    E/MainActivity: -------------->onServiceConnected
    

    点击一下stopService:
    发现没有任何日志信息,说明无法使用stopService函数停止Service了。已经和启动者绑定了,如果要停止该Service,不仅要调用 stopService(intent)方法,还需要调用 unbindService(serviceConnection)方法

    使用unbindService,只有unbind 没有销毁,可以继续运行,无法交互

    E/TestService: --------->onUnbind: 
    

    然后使用stopService,才真正的销毁

    E/TestService: --------->onDestroy: 
    

    通过上面的实例,我们可以看出,如果我们在启动一个Service时,同时调用了startService()和bindService(),那么在停止该Service时,也要同时调用 stopService(intent)和 unbindService(serviceConnection);否则,Service可能会未正常停止。

    Service的启动方式总结

    startService()和bindService()启动Service的区别

    1.生命周期不同 如图

    image

    onCreate()和onDestory()是一对的

    startService(): onStartCommand
    bindService(): onBind unBind 一对 绑定和解绑

    startService() onCreate 只会一次除非stopService
    bindService() onCreate onbind 都只会一次

    2.两者和启动者的关系。

    startService()启动的Service和启动者的生命周期无关,必须要显示调用stopService(intent)方法,才能停止该Service;

    bindService()启动的Service和启动者的生命周期有关,当启动者销毁时,会自动销毁该Service。

    3.两者的具体使用场景。

    startService(),常用于启动一个后台服务,例如推送服务、心跳服务,保持一个长久的链接;

    bindService(),常用于调用一个远程连接服务,例如AIDL,使用远程服务暴露的功能。

    特殊情况,如果你想搞一个长连接又需要和当前的Content交互,就需要startService和bindService一起使用了。

    实战阶段

    实战两种启动方式:

    长链接:startService 与当前界面没有数据交互

    这里写一个子线程假装发送心跳包:

    @Override
        public void onCreate() {
            super.onCreate();
            Log.e(TAG, "--------->onCreate: ");
           new Thread(new Runnable() {
               @Override
               public void run() {
                   handler.sendEmptyMessage(0);
               }
           }).start();
    
        }
    
    Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                //暂停两秒
                Log.e("TestLongCommService",count+"");
                count++;
                handler.sendEmptyMessageDelayed(0,2000);
            }
        };
    

    startService启动服务之后:

    05-24 08:55:04.757 4205-4205/com.baiheng.androidcomponent E/TestLongCommService: --------->onCreate: 
        --------->onStartCommand: 
        --------->onStart: 
    05-24 08:55:04.777 4205-4205/com.baiheng.androidcomponent E/TestLongCommService: 0
    05-24 08:55:06.777 4205-4205/com.baiheng.androidcomponent E/TestLongCommService: 1
    05-24 08:55:08.777 4205-4205/com.baiheng.androidcomponent E/TestLongCommService: 2
    05-24 08:55:10.787 4205-4205/com.baiheng.androidcomponent E/TestLongCommService: 3
    05-24 08:55:12.787 
    

    测试即使退出Activity或者回到桌面都不会停止,只有强制清理后台的时候才会退出。

    类似的下载服务也可以写成长连接的服务,因为他和界面无关。只需要一个通知栏的进度提示即可,与当前界面无关。不会因为退出当前界面而停止下载。一般这种下载叫做全局下载。

    当然也有基于当前页面的下载,如果页面关闭则关闭下载。所以需要使用onBind方式

    OnBind局部下载

    使用onbind下载当前页面的东西,并使用binder通信

    TestDownloadService

    public static final int MAX_PROGRESS = 100;
    private int progress = 0;
    private DownProcess downProcess;
    
    public interface DownProcess {
        void update(int i);
    }
    
    class MyBinder extends Binder {
            public TestDownloadService getService() {
                return TestDownloadService.this;
            }
        }
    
    
        /**
         * 模拟下载任务,每秒钟更新一次
         */
    public void startDownLoad() {
        new Thread(new Runnable() {
    
                @Override
                public void run() {
                    while (progress < MAX_PROGRESS) {
                        progress += 1;
                        if(downProcess!=null) {
                            downProcess.update(progress);
                        }
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                    }
                }
        }).start();
    }
    
    
    public void setDownProcess(DownProcess downProcess) {
            this.downProcess = downProcess;
    }
    

    代码很简单,通过binder传递service对象,通过接口传出去数值

     button4.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(TestServiceActivity.this, TestDownloadService.class);
                    bindService(intent, conn2, Context.BIND_AUTO_CREATE);
                }
    });
    
     ServiceConnection conn2 = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                testDownloadService = ((TestDownloadService.MyBinder) service).getService();
                //开始下载
                testDownloadService.startDownLoad();
                testDownloadService.setDownProcess(new TestDownloadService.DownProcess() {
                    @Override
                    public void update(int i) {
                        progressBar.setProgress(i);
                    }
                });
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                testDownloadService = null;
            }
        };
    
    screenshot.png

    很多人不明白,为什么在当前页面下载也需要开启一个服务去下载,直接在当前页面开线cheng下载不行吗。当然可以,但是谷歌官方建议下载这种操作应该放到服务里面去,因为他不需要直接的用户交互而且是长时间操作,为了避免用户误操作。需要放到后台服务器执行。

    最后的解绑操作:

     void doUnbindService() {
            unbindService(conn2);
        }
    

    相关文章

      网友评论

          本文标题:Android四大组件---Service详解

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