美文网首页
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