android 四大组件之Service

文章参考自:
1.郭神博客:http://blog.csdn.net/guolin_b...
2.Android开发文档:
https://developer.android.com...
https://developer.android.com...html


最近趁着有时间,将之前的知识整理下,要否则总容易遗忘,今天就来说解下Service的用法。
做为Android的四大组件之一,其重要性可想而知。在应用中咱们主要是用来进行一些后台操做,不需与应用UI进行交互,执行耗时任务等。java

官方文档中这样说:android

Service 是一个能够在后台执行长时间运行操做而不提供用户界面的应用组件。服务可由其余应用组件启动,并且即便用户切换到其余应用,服务仍将在后台继续运行。
此外,组件能够绑定到服务,以与之进行交互,甚至是执行进程间通讯 (IPC)。 例如,服务能够处理网络事务、播放音乐,执行文件 I/O
或与内容提供程序交互,而全部这一切都可在后台进行。网络

Service的用途:

1.在后台执行耗时操做,但不须要与用户进行交互。
2.一个应用暴露出来的一些供其余应用使用的功能。app

这里须要声明一点,Service是运行在主线程中,于是若是须要进行耗时操做或者访问网络等操做,须要在Service中再开启一个线程来执行(使用IntentService的话则不须要在本身手动开启线程)。ide

启动Service

启动一个Service有两种方式:ui

Context.startService()
Context.bindService()

clipboard.png

(图片截取自官方文档:https://developer.android.com...this

startService()方式启动Service,咱们启动以后是没有办法再对Service进行控制的,并且启动以后该Service是一直在后台运行的,即便它里面的一些代码执行完毕,咱们要想终止该Service,就须要在他的代码里面调用stopSelf()方法或者直接调用stopService() 方法。而经过bindService()方法启动的Service,客户端将得到一个到Service的持久链接,客户端会获取到一个由Service的onBind(Intent)方法返回来的IBinder对象,用来供客户端回调Service中的回调方法。spa

咱们不管使用那种方法,都须要定义一个类,让它继承Service类,并重写其中的几个方法,若是咱们是采用startService()方式启动的话,只须要重写onCreate() 、onStartCommand(Intent intent, int flags, int startId)、onDestroy()方法便可(其实咱们也能够重写),而若是采用的是bindService()方法启动的话,咱们就须要重写onCreate() 、onBind(Intent intent)、 onUnbind(Intent intent)方法.注意,做为四大组件之一,Service使用以前要在清单文件中进行配置。.net

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

Context.startService()

MyService.java的代码:

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();

        Log.i("test","onCrete executed !");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.i("test","onStartComand executed !");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("test","onDestroy executed !");
    }
}

MainActivity.java的代码以下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

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

        btnStart = (Button) findViewById(R.id.btn_start);
        btnStop = (Button) findViewById(R.id.btn_stop);
        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
    }


    @Override
    public void onClick(View view) {

        Intent mIntent = new Intent(MainActivity.this,MyService.class);

        switch (view.getId()){
            case R.id.btn_start:
                startService(mIntent);
                break;
            case R.id.btn_stop:
                stopService(mIntent);
                break;
        }
    }
}

主界面就两个按钮,一个用来启动Service,一个用来中止Service:

clipboard.png

下面咱们先点击START按钮,Log信息以下:

clipboard.png
能够看出,onCreate()方法先执行,而后onStartCommand()方法紧接着执行,那么若是咱们再次点击启动按钮呢?结果以下图:

clipboard.png
咱们能够看到,此次onCreate()方法没有再执行,而是直接执行了onStartCommand()方法,这是由于Service只在第一次建立的时候才执行onCreate()方法,若是已经建立了,那以后再次调用startService()启动该Service的时候,只会去执行onStartCommand()方法方法,而不会再执行onCreate()方法。
接下来咱们点击中止按钮,能够看到,onDestroy()方法被执行了:

clipboard.png

注意,若是咱们不点击中止按钮手动中止该Service的话,该Service会一直在后台运行,即便它的onStartCommand()方法中的代码已经执行完毕,在下图中咱们能够看到:

这时候咱们的这个Service是一直在后台执行的,即便它的onStartCommand()方法中的代码已经执行完了。若是咱们想要它自动中止的话,能够将onStartCommand()方法中的代码修改以下:

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.i("test","onStartComand() executed !");
        stopSelf();
        return super.onStartCommand(intent, flags, startId);
    }

Context.bindService()

采用该方法的代码就稍微比之前的多了,由于咱们须要在客户端对Service进行控制,于是会在MainActivity中建立一个匿名内部类ServiceConnection,而后会在bindService()方法和unbindService()方法中将其传入。
MyService.java 中的代码以下:

public class MyService extends Service {
    private MyBinder myBinder = new MyBinder();

    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("test","onCreate() executed !");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("test","onDestroy() executed !");
    }

    @Override
    public boolean onUnbind(Intent intent) {

        Log.i("test","onUnbind executed !");
        return super.onUnbind(intent);
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i("test","onBind() executed !");
        return myBinder;
    }

    class MyBinder extends Binder{
        public void startDownload(){

            Log.i("test", "MyBinder中的startDownload() executed !");
            // 执行具体的下载任务,需开启一个子线程,在其中执行具体代码
        }
    }
}

MainActivity.java 的代码以下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    Button btnBind,btnUnBind;
    MyService.MyBinder myBinder ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnBind = (Button) findViewById(R.id.bind);
        btnUnBind = (Button) findViewById(R.id.btn_unBind);
        btnBind.setOnClickListener(this);
        btnUnBind.setOnClickListener(this);

    }

    ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            // 将IBinder向下转型为咱们的内部类MyBinder
            myBinder = (MyService.MyBinder) iBinder;
            // 执行下载任务
            myBinder.startDownload();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    public void onClick(View view) {

        Intent mIntent = new Intent(MainActivity.this,MyService.class);

        switch (view.getId()){
            case R.id.bind:
                // 绑定Service
                bindService(mIntent,mServiceConnection,BIND_AUTO_CREATE);
                break;
            case R.id.btn_unBind:
                // 取消绑定Service
                unbindService(mServiceConnection);
                break;
        }
    }
}

点击绑定按钮;

clipboard.png
点击取消绑定按钮:

clipboard.png
注意,若是咱们没有先点击绑定,而是直接点击的取消绑定,程序会直接crash,报如下错误:

java.lang.IllegalArgumentException: Service not registered: com.qc.admin.myserializableparceabledemo.MainActivity$1@8860e28
                                                                                              at android.app.LoadedApk.forgetServiceDispatcher(LoadedApk.java:1120)
                                                                                              at android.app.ContextImpl.unbindService(ContextImpl.java:1494)
                                                                                              at android.content.ContextWrapper.unbindService(ContextWrapper.java:616)
                                                                                              at com.qc.admin.myserializableparceabledemo.MainActivity.onClick(MainActivity.java:71)

clipboard.png

细心的你也许早就发现了,Log中并无打印"onServiceDisconnected executed !"这句,也就是说没有调用onServiceDisconnected()方法?从字面理解,onServiceConnected()方法是在Service创建链接的时候调用的,onServiceDisconnected()不就应该是在Service断开链接的时候调用的吗?其实否则,咱们查看该方法的文档就知道了:

Called when a connection to the Service has been lost. This typically happens when the process hosting the service has crashed or been killed. This does not remove the ServiceConnection itself -- this binding to the service will remain active, and you will receive a call to onServiceConnected(ComponentName, IBinder) when the Service is next running.

意思就是:当绑定到该Service的链接丢失的时候,该方法会被调用,典型的状况就是持有该Service的进程crash掉了,或者被杀死了。可是这并不会移除ServiceConnection 自身--它仍然是保持活跃状态,当Service下次被执行的时候,onServiceConnected(ComponentName, IBinder) 方法仍然会被调用。

可是要注意,若是咱们按照刚才说的,不是先点击 bindService()方法,而是直接点击unbindService()方法,程序虽然也是crash掉了,但onServiceDisconnected()方法并不会被调用,这个很容易理解,毕竟都没有创建链接呢,谈何断开链接啊。可是若是咱们已经绑定了Service,而后在后台直接终止该Service呢?结果会怎样?答案是onServiceDisconnected()方法仍然不会调用。这里我以为应该是只有在乎外的状况下进程结束,是由系统自动调用的,而非咱们手动中止的。咱们能够查看该方法内部的注释:

This is called when the connection with the service has been
unexpectedly disconnected -- that is, its process crashed.Because it
is running in our same process, we should never see this happen.

这段文字清楚的说明了该方法执行的场景:异常状况下致使断开了链接。也就是进程crash掉了。由于它运行在咱们应用程序所在的进程中,于是咱们将永远不但愿看到这种状况发生。

Context.startService()和Context.bindService()同时使用

这两种方式是能够同时使用的,可是要注意,startService()和stopService()方法是对应的,而bindService()和unBind()方法是对应的,也就是说若是咱们先调用startService()以后调用bindService()方法,或者相反,那么咱们若是只调用stopService()或者只调用bindService()都没法中止该Service,只有同时调用才能够。
下面来看下具体代码:
MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    Button btnStart,btnStop,btnBind,btnUnBind;
    MyService.MyBinder myBinder ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnStart = (Button) findViewById(R.id.btn_start);
        btnStop = (Button) findViewById(R.id.btn_stop);
        btnBind = (Button) findViewById(R.id.btn_bind);
        btnUnBind = (Button) findViewById(R.id.btn_unBind);

        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
        btnBind.setOnClickListener(this);
        btnUnBind.setOnClickListener(this);

    }

    ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            // 将IBinder向下转型为咱们的内部类MyBinder
            myBinder = (MyService.MyBinder) iBinder;
            // 执行下载任务
            myBinder.startDownload();

        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

            Log.i("test","onServiceDisconnected executed !");
        }
    };

    @Override
    public void onClick(View view) {

        Intent mIntent = new Intent(MainActivity.this,MyService.class);

        switch (view.getId()){
            case R.id.btn_start:
                // 启动Service
                startService(mIntent);
                break;
            case R.id.btn_stop:
                // 终止Service
                stopService(mIntent);
                break;
            case R.id.btn_bind:
                // 绑定Service
                bindService(mIntent,mServiceConnection,BIND_AUTO_CREATE);
                break;
            case R.id.btn_unBind:
                // 取消绑定Service
                unbindService(mServiceConnection);
                break;
        }
    }
}

MyService.java的代码:

public class MyService extends Service {
    private MyBinder myBinder = new MyBinder();

    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("test","onCreate() executed !");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.i("test","onStartComand() executed !");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("test","onDestroy() executed !");
    }

    @Override
    public boolean onUnbind(Intent intent) {

        Log.i("test","onUnbind executed !");
        return super.onUnbind(intent);
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i("test","onBind() executed !");
        return myBinder;
    }

    class MyBinder extends Binder{
        public void startDownload(){

            Log.i("test", "MyBinder中的startDownload() executed !");
            // 执行具体的下载任务
        }
    }
}

a.下面是依次点击start、bind、stop、unBind 按钮的输出结果:

clipboard.png

b.下面是依次点击start、bind、unbind、stop 按钮时的输出结果:

clipboard.png

在前台运行服务

咱们上面一直说Service通常是用来在后台执行耗时操做,可是要知道,Service也是能够运行在前台的。后台Service的优先级比较低,容在内存不足等状况下被系统杀死,经过将其设置为前台,能够大大下降其被杀死的机会。前台Service会在系统通知栏显示一个图标,咱们能够在这里进行一些操做。前台Service比较常见的场景有音乐播放器和天气预报等:

clipboard.png

那么接下来咱们就直接上代码:

@Override
    public void onCreate() {
        super.onCreate();
        Log.i("test", "onCreate() executed !");

        Intent mIntent = new Intent(this, SecondActivity.class);
        PendingIntent mPendingIntent = PendingIntent.getActivity(this, 0, mIntent, 0);
        Notification mNotification = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("My Notification ")
                .setContentText("Hello World ! ")
                .setContentIntent(mPendingIntent)
                .build();

        // 注意:提供给 startForeground() 的整型 ID 不得为 0。
        // 要从前台移除服务,请调用 stopForeground()。此方法采用一个布尔值,指示是否也移除状态栏通知。
        // 然而stopForeground()不会中止服务。 可是,若是您在服务正在前台运行时将其中止,则通知也会被移除。
        startForeground(1, mNotification);
    }

其实这里的实现很简单,就是将一个Notification经过startForeground(1, mNotification);传进去,从而将Notification与 Service创建起关联。
咱们点击这个通知,就会跳转到第二个Activity(可是该Notification并不会消失),截图以下:

clipboard.png

相关文章
相关标签/搜索