Android Service(二) Service使用

      上篇文章,我们只是翻译了Android官方文档,具体详情请看,Android Service(一) 初识。今天就通过实例来演示Service的启动以及生命周期等特征。

一、实战演示。

1.新建工程,创建项目。

2.新建一个类,继承自Service,实现它的onBind()方法,顺便重写它的几个方法,

public class MyService extends Service {
    private static final String TAG = "MyService";
    @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();
    }
}

在几个方法中添加了打印log,接着需要在AndroidManifest.xml中注册,如需访问网络,还需要添加网络请求权限。

...
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".MyService">
            <intent-filter>
                <action android:name="cn.xinxing.service" />
            </intent-filter>
        </service>
    </application>
...

在主界面中添加一个启动服务按钮和停止服务按钮,点击启动服务按钮,启动一个Service,

public class MainActivity extends AppCompatActivity {

    Intent intent = new Intent("cn.xinxing.service");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(intent);

            }
        });
        findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(intent);
            }
        });
    }
}

代码都比较简单,就不多解释了。好了,下面就可以运行该程序了,


运行后,点击启动服务按钮,可以看到输出了日志,分别依次调用了onCreate()->onStartCommand()->onStart(),


当我们再点击启动服务按钮,分别依次调用了onStartCommand()->onStart(),再次点击服务启动按钮,依旧还是只调用onStartCommand()->onStart()。


当点击停止服务按钮时,会调用Service的onDestroy()方法,Service就停止了,


此时,我们再次点击启动服务按钮,大家想一想,会调用什么方法呢?


和你想的一样吧!重新启动了一个新的Service。后面不管做什么操作,就和前面所讲的一样了!

小结: 

1.通过startService()启动Service,会依次调用onCreate()->onStartCommand()->onStart(),一旦Service启动成功,再次调用startService()时,只会调用onStartCommand()onStart(),不会再调用onCreate(),也就是说onCreate()只会执行一次(该Service没有销毁时)。通过stopService()方法停止Service,会调用Service的onDestroy()方法。

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

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

下面给出一张完整的log输出截图,


3.下面我们在Service的onCreate()方法中模拟一个耗时操作,看看会发生什么,

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "--------->onCreate: ");
        try {
            Thread.sleep(50*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
开启了一个模拟耗时操作,结果程序最终就崩溃了!下面是日志截图,可以看到ANR了,并且错误日志保存在“/data/anr/traces.txt”中,




可以看到,我们在Service的onCreate()方法中模拟一个耗时操作,结果导致ANR,所以,在Service当中,如果有耗时操作,请开始子线程来执行。

4.通过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中引入;

下面还是看具体实例吧!首先是MyService的代码,

...
    //通过binder实现调用者client与Service之间的通信
    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);
    }

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

...
在MyService中增加了一个内部类MyBinder,为了测试,MyBinder类只是简单实现,返回了MyService自身,并且重写了onBind()和onUnbind()方法,onBind()返回了MyBinder对象。事实上,在实际开发中,我们需要在MyBinder类,去执行一些任务,向客户端暴露一些方法等。如果了解AIDL的话,这一块是很容易理解的。如果想了解AIDL,详情请看 Android IPC之AIDL浅谈

接着看看MainActivity的实现,

...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
            }
        });

        findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(serviceConnection);
            }
        });
    }

    MyService myService;
    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "-------------->onServiceConnected");
            myService = ((MyService.MyBinder) service).getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "-------------->onServiceDisconnected");
            myService = null;
        }
    };
...
运行后,点击启动服务按钮,下面是截图,


调用bindService()后,可以看到,执行了Service的onCreate()->onBind()->onServiceConnected(),调用者和服务端已经绑定成功,建立连接。

此时,如果我们再次点击启动服务按钮,会有什么发生吗?还是上截图,


咦,什么都没有!这个逻辑时这样的,我们在绑定Service的时候,如果service没被创建,那么调用一次onCreate(),然后调用onBind(),多次绑定时,不会多次调用onBind()

我们点击停止服务按钮,会发生什么呢?不多说了,上截图,


调用unbindService()后,可以看到,执行了Service的onUnbind()->onDestroy()。此时说明,Service已经停止了。细心的你,有没有发现有个方法没有调用?你知道是哪个方法吗?是----->onServiceDisconnected()方法,为何我们解除绑定了,该回调方法没有调用呢?

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

如果这个时候,不小心再点一下停止服务按钮,应该发生什么呢?


咦,应用报错了!根据log信息“Service not registered: cn.xinxing.service.MainActivity$3@420946f0”,可以得知,提示说Service没有注册。因此,我们在解除绑定(调用unbindService())时,需要多注意,具体改动,详见下文。

如果,我们绑定了,然后直接点击返回键,会出现什么情况呢?看截图,


可以看到log信息输出,说leaked ServiceConnection,并且最终又调用了onUnbind()->onDestroy()方法。因此,如果绑定过Service 记得要解除,虽然应用没有崩溃,但是这是一种好习惯!

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

如果我们同时通过startService()和bindService()启动Service,那么具体的处理逻辑是什么呢?那么,还是照旧,通过代码实例来演示具体的执行过程。

首先我们看看MainActivity的主要代码,

...
  findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService();
            }
        });

        findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService();
            }
        });

        findViewById(R.id.btn_unbind).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                doUnbindService();
            }
        });
...

...
   void startService() {
        startService(intent);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        mIsBound = true;
    }

    void stopService(){
        stopService(intent);
    }


    void doUnbindService() {
        if (mIsBound) {
            // Detach our existing connection.
            unbindService(serviceConnection);
            mIsBound = false;
        }
    }
...
当点击启动按钮时,同时调用了startService()和bindService(),log输入日志截图如下,


可以看到,log输出的内容和我们前面讲解的一致,就是分别调用startService()和bindService()时调用的方法。

(1).那么此时,当点击stop Service按钮时,会出现什么情况呢!我们还是看看log会输入什么内容!什么东东都没有?这是什么情况?为何这样呢?

这是因为,我们此时启动的Service,已经和启动者绑定了,如果要停止该Service,不仅要调用 stopService(intent)方法,还需要调用 unbindService(serviceConnection)方法,所以当我们只调用stopService(intent)方法时,是不会有输出的。那么如果此时我们再点击unbind Service按钮,会是什么情况呢?依旧上截图,


可以看到,Service已经解绑并且已经停止了。

(2).如果我们先点击unbind Service按钮,最后再点击stop Service按钮会发生什么呢?好期待哦!偷笑接着看下文吧!

先点击unbind Service按钮,看截图,


可以看到只是调用了解绑功能,Service还未停止,那么此时,再点击stop Service按钮,


可以看到Service已经停止了。

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

二.startService()和bindService()启动Service的区别。

1.首先两者的生命周期是不一样的,


通过这张图,我们可以很明显的看出二者整个生命周期的过程是不一样的。

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

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

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

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

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

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

ps:

最后展示修改过MainActivity的代码,

public class MainActivity extends AppCompatActivity {

    Intent intent = new Intent("cn.xinxing.service");
    private static final String TAG = "MainActivity";

    private boolean mIsBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                doBindService();
            }
        });

        findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                doUnbindService();
            }
        });
    }

    MyService myService;
    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "-------------->onServiceConnected");
            myService = ((MyService.MyBinder) service).getService();
            mIsBound=true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "-------------->onServiceDisconnected");
            myService = null;
        }
    };

    void doBindService() {
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        mIsBound = true;
    }


    void doUnbindService() {
        if (mIsBound) {
            // Detach our existing connection.
            unbindService(serviceConnection);
            mIsBound = false;
        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        doUnbindService();
    }
}
在绑定过程中加入标志判断,这样在解绑就不会发生异常了!看过上篇文章的话 ,应该对这些代码不会陌生的。这些代码是来自官网。

小结:

1.Context.BIND_AUTO_CREATE表明只要绑定存在,就自动建立Service;同时也告知Android系统,这个Service的重要程度与调用者相同,除非考虑终止调用者,否则不要关闭这个Service;

2.如果service没被创建,那么调用一次onCreate(),然后调用onBind(),多次绑定时,不会多次调用onBind();
3.通过unbindService()函数取消绑定Servcie时,onUnbind()函数将被调用; 
4.如果onUnbind()函数的返回true,则表示在调用者绑定新服务时, onRebind()函数将被调用;
5.取消绑定仅需要使用unbindService()方法,并将ServiceConnnection传递给unbindService()方法需注意的是,unbindService()方法成功后,系统并不会调用onServiceDisconnected(),因为onServiceDisconnected()仅在意外断开绑定时才被调用;
6.当bindService后,不能stopService,需要通过unBindService()来解除绑定。

推荐文章,Android Service(三) IntentService详解

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值