Android四大组件之Service

2019-03-17  本文已影响0人  总是一点点

Android学习的第一步从四大组件开始


在四大组件中ActivityService应该算的上是一对亲兄弟了,两者的使用都比较频繁而且在使用上亦有很多相似之处(毕竟都是有Context的人)。但是“亲兄弟也要明算账”这对兄弟一个负责在外面“抛头露面”,一个负责在后面“默默无闻”。
“抛头露面”的那位已经介绍完了,现在来看看久居后台的这位。

Service 是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。
服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。

1.注册

既然进行注册那肯定少不了AndroidManifest.xml文件,毕竟package中所有暴露的组件都需要在这里声明。与Activity相同,Service的注册就是在 <application/> 节点下添加一个<service/>节点:

<manifest ... > 
    ... 
    <application ... >     
        <service android:name=".TestService" />    
        ...  
    </application>
</manifest>

在注册时只有android:name属性是必须的属性,其他属性可以根据自己的情况进行添加,这里不额外进行介绍了,推荐在开发者文档进行查看。

注意一点点:
为了确保应用的安全性,请始终使用显式 Intent 启动或绑定 Service,且不要为服务声明 Intent 过滤器。

2.生命周期的前一章(启动/绑定状态)

因为service的生命周期和使用service的处理方式密切相关,所以这里插一段说明一下。
对于service的处理方式,有两种态度,一种是 "放养" :任务给你了,你去干吧,其他的我就不管了,但是让你停下时必须停下;另一种是 "放风筝" :任务给你了,但是你要跟着绳子走,如果绳子断了,你就会掉下来。

3.生命周期

不一样的选择会有不一样的人生,不同的状态自然有不同的生命周期。下面我们看一下Service这位默默无闻的人的“人生路线”。

Service的生命周期
使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。(就是在这里写遗书,分配一下“死”后的“财产”)

然后就是Service正式“参与工作”(有效生命周期):

  1. 第一种是通过 调用startService() 开启服务:
  1. 第二种是通过调用bindService() 开启服务:

注意一点点:
无论是启动状态还是绑定状态,onBind() 方法必须重写,但在启动状态的情况下直接返回 null
在绑定状态下,无需实现onStartCommand() 方法

4.Service启动状态

从上面生命周期的图中,我们可以看到启动服务是通过startService()启动,这会调用服务的onCreate()(仅第一次)和onStartCommand()方法,直到stopService()停止服务。直到了基本流程了,我们来写一个启动状态的Service:

package com.test.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

/**
 * Service 启动状态
 *
 * @author ydd
 */
public class StartServiceTest extends Service{

    private static final String TAG = "StartServiceTest";

    /**
     * 服务绑定时调用,且必须要实现该方法(abstract函数)
     * @param intent 接收 bindService()的传入
     * @return 返回IBinder接口的实现,在绑定状态使用,本处为空
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG,"onBind()");
        return null;
    }

    /**
     * 首次创建服务时调用,且super中无任何实现
     */
    @Override
    public void onCreate() {
        Log.d(TAG,"onCreate()");
        super.onCreate();
    }

    /**
     * 每次通过startService()方法启动Service时都会被回调。
     * @param intent 启动时startService()传输过来的数据包
     * @param flags 表示启动请求时是否有额外数据
     * @param startId 指定当前服务的唯一Id
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG,"onStartCommand()");
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 销毁服务时调用
     */
    @Override
    public void onDestroy() {
        Log.d(TAG,"onDestroy()");
        super.onDestroy();
    }
}

服务写起来很简单Service中的abstract方法只有onBind(),所以这里必须实现onBind,为了看一下完整的生命周期运行,这里在几个关键的回调函数中打印Log,看一下运行情况。下面再实现一下操作界面:

package com.test.servicetest;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

/**
 * 主界面
 *
 * @author ydd
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener{


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startServiceBtn = (Button) findViewById(R.id.btnStartService);
        Button stopServiceBtn = (Button) findViewById(R.id.btnStopService);
        startServiceBtn.setOnClickListener(this);
        stopServiceBtn.setOnClickListener(this);
    }


    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this , StartServiceTest.class);
        switch (v.getId()){
            case R.id.btnStartService:
                startService(intent);
                break;
            case R.id.btnStopService:
                stopService(intent);
                break;
            default:
                break;
        }
    }
}

界面只添加了两个按钮一个用于启动服务(调用startService())一个用于关闭服务(调用stopService()).代码都准备好了,下面我们开始进行测试。
说明一下:本次操作为连续2次启动服务,然后停止服务。再次开启服务并停止服务。

    ——————————第一次开启————————————
    com.test.servicetest D/StartServiceTest: onCreate()
    com.test.servicetest D/StartServiceTest: onStartCommand()
    ——————————第二次开启————————————
    com.test.servicetest D/StartServiceTest: onStartCommand()
    ——————————关闭服务—————————————
    com.test.servicetest D/StartServiceTest: onDestroy()
    ——————————重新开启服务———————————
    com.test.servicetest D/StartServiceTest: onCreate()
    com.test.servicetest D/StartServiceTest: onStartCommand()
    ——————————关闭服务—————————————
    com.test.servicetest D/StartServiceTest: onDestroy()

第一次启动时从Log中我们可以清晰的看出第一次启动时调用了onCreate()onStartCommand()方法,之后在开启时,因为 Service 已经存在了,就只调用onStartCommand()方法。关闭服务调用onDestroy()方法,当服务关闭后,如果再次启动就会开始新的生命周期。
下面来点好玩的东西:
从上面可以看出onStartCommand()是整个开启服务的一个“有趣”点,这里简单介绍一下这个函数。
我们先看传入的参数:

上面出现了几个陌生的面孔:START_STICKY_COMPATIBILITY、START_STICKY、START_REDELIVER_INTENT
这几个参数对应了onStartCommand()的函数返回值:

有点乱,这里梳理一下思路:使用startService()启动服务后,在Service中onStartCommand()被调用(如果是第一次,会首先调用onCreate())。我们在onStartCommand()方法中根据接收传入的参数,并根据flags和startId确认当前开启服务的状态,开始处理事务,最后根据当前的需求设置不同的返回值,以便程序更好的对当前Service进行控制。

5.Service绑定状态

不同于启动状态的服务,绑定状态的Service不能单独存在,需要与其他的组件之间建立联系。从生命周期上看,只需要通过onBind()函数返回一个IBinder接口的实现类,用以提供客户端用来与服务进行交互的编程接口,再通过onUnbind()接触绑定结束工作。想起来总是比实际简单,根据情况的不同实际可以通过三种方法定义接口:

5.1 扩展 Binder 类

本方法无法跨进程工作,所以只适用于仅提供本应用服务的 Service。通过实现自有的 IBinder ,让客户端通过该接口直接访问服务中的公共方法。
扩展 Binder 类的使用方法大致分为三步:

  1. 在您的服务中,创建一个可满足下列任一要求的IBinder 实例
    • 包含客户端可调用的公共方法
    • 返回当前 Service 实例,其中包含客户端可调用的公共方法
    • 或返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法
  2. onBind() 回调方法返回此 Binder 实例
  3. 在客户端中,从 onServiceConnected() 回调方法接收 Binder实例,并使用提供的方法调用绑定服务。

有启动状态的铺垫,这里直接上代码:

package com.test.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

/**
 * Service 绑定状态
 *
 * @author ydd
 */
public class BindServiceTest extends Service{

    private static final String TAG = "BindServiceTest";
    private final IBinder mBinder = new TestBinder();
    
    private int count;
    private boolean stop;

    /**
     * 服务绑定时调用,且必须要实现该方法(abstract函数)
     * @param intent 接收 bindService()的传入
     * @return 返回IBinder接口的实现
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG,"onBind()");
        return mBinder;
    }

    /**
     * 首次创建服务时调用,且super中无任何实现
     */
    @Override
    public void onCreate() {
        Log.d(TAG,"onCreate()");
        super.onCreate();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!stop) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count++;
                }
            }
        }).start();
    }

    /**
     * 解除Service绑定
     * @param intent
     * @return
     */
    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG,"onUnbind()");
        return super.onUnbind(intent);
    }

    /**
     * 销毁服务时调用
     */
    @Override
    public void onDestroy() {
        Log.d(TAG,"onDestroy()");
        stop = true;
        super.onDestroy();
    }
    
    /**
     * 获取服务时长
     */
    public int getCount(){
        return count;
    }

    /**
     * 定制需要传出的Binder
     */
    public class TestBinder extends Binder {
        BindServiceTest getService(){
            return BindServiceTest.this;
        }
    }
}

BindService的创建和StartService的最大的区别是增加了Binder的实现以及,在onBind()函数中不再返回空值,更改为一个Binder对象。另外既然是可以互相通信的,我们就在Service中记录开启的时间长短,并在开启者组件中获取一下这个数据看一下。下面看一下控制界面的代码。

package com.test.servicetest;

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

/**
 * 主界面
 *
 * @author ydd
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "BindServiceTest";
    private ServiceConnection connection;
    private BindServiceTest mService;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startServiceBtn = (Button) findViewById(R.id.btnStartService);
        Button stopServiceBtn = (Button) findViewById(R.id.btnStopService);
        Button getDataBtn = (Button) findViewById(R.id.btnGetData);
        startServiceBtn.setOnClickListener(this);
        stopServiceBtn.setOnClickListener(this);
        getDataBtn.setOnClickListener(this);
        connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.d(TAG,"——onServiceConnected");
                mService = ((BindServiceTest.TestBinder) service).getService();

            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.d(TAG,"——onServiceDisconnected");
                mService = null;
            }
        };
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this, BindServiceTest.class);
        switch (v.getId()) {
            case R.id.btnStartService:
                bindService(intent, connection, Service.BIND_AUTO_CREATE);
                break;
            case R.id.btnStopService:
                unbindService(connection);
                break;
            case R.id.btnGetData:
                if (mService != null) {
                    Log.d(TAG, "服务当前已经开启:" + mService.getCount() + "秒");
                }else {
                    Log.d(TAG, "服务当前未开启!");
                }
                break;
            default:
                break;
        }
    }
}

根据启动模式的Service的讲解和两种状态的生命周期,我们应该可以知道两者的开启和关闭调用的方法是不同的。在绑定状态下,我们使用的是bindService()unbindService()函数来控制Service。另外这里使用了ServiceConnected,并实现了两个函数:

ComponentName是一个封装了组件(Activity, Service, BroadcastReceiver, or ContentProvider)信息的类,如包名,组件描述等信息,较少使用该参数。

看完ServiceConnected我们再重新看一下函数bindService(),在参数中处理Intent和刚刚提到的ServiceConnected还有一个int flags,这个参数有时负责控制什么的?
简单的说就是对绑定进行控制操作(因为我只是知道简单的),例如上面使用到的参数:

补充一点点

  1. 当 flags 不等于 BIND_AUTO_CREATE 时,bindService是不会自动启动service的。
  2. 与启动模式与绑定模式的参数不同:
    bindService(Intent service, ServiceConnection conn, int flags)
    unbindService(ServiceConnection conn)

5.2 Messenger

终于到Messenger了,一种可以跨进程通讯的方式,用起来稍微比上面的方式复杂一点,不过毕竟功能更强(可以跨进程通讯),稍微难一点也是可以理解的。这里想大致介绍一下使用方法:

  1. 创建一个handler(Service)对象,并实现hanlemessage方法,用于接收来自客户端的消息,并作处理;
  2. 创建一个messenger(Service),,封装handler(Service);
  3. 用messenger(Service)的getBinder()方法获取一个IBinder对象,通过onBind返回给客户端;
  4. 等待客户端发来的消息;
  1. 在activity中绑定服务;
  2. 创建一个handler(Client)对象,并实现hanlemessage方法,用于接收Service返回的消息,并作处理;
  3. 创建一个messenger(Client),,封装handler(Client);
  4. 创建ServiceConnection并在其中使用 IBinder 将 messenger(Service)实例化(这里就是Service中的创建一个messenger,通过IBinder的方式来到了客户端) ;
  5. 创建Message并在其中加载数据,注意为了能接收Service的回复信息,这里将Message.replyTo设置为messenger(Client);
  6. 使用Messenger(Service)向服务端发送消息;
  1. 接收到Messager(Service)带回来的消息,进行处理;
  2. 创建一个新的Message,装载处理结果;
  3. 使用Message.replyTo调用Messenger(Client)向客户端发送消息;
  1. 接收到Messager(Client)带回来的消息,进行处理进行相关显示操作;
  2. 整体操作完成,解除绑定;

补充一点点:
Messenger个人感觉,理解为收信人比较好。如果A需要向B发消息,A中就需要有一个B的收信人B在,A将Message交给收信人B,并由收信人B带给(send)B。这时如果A想知道B的回复,就派自己的收信人A,跟着(replyTo)收信人B一起去B,等B的处理结果,由收信人A带回A。

说起来有点绕,大家看一下代码,应该会更好理解:
服务端

package com.test.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.annotation.Nullable;

/**
 * 绑定服务使用Messenger
 *
 * @author ydd
 */
public class MessengerService extends Service {

    private static final int SIGN = 1;
    private final Messenger mMessenger = new Messenger(new CustomHandler());

    private class CustomHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Message message = new Message();
            switch (msg.what) {
                case SIGN:
                    message.arg1 = msg.arg1 + msg.arg2;
                    message.what = SIGN;
                    try {
                        msg.replyTo.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    break;
            }
            super.handleMessage(msg);
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

客户端

package com.test.servicetest;

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
/**
 * 主界面
 *
 * @author ydd
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    public static final int SIGN = 1;
    private ServiceConnection connection;
    private boolean mBound;
    private Messenger mService;

    private Messenger mMessenger = new Messenger(new MainHandler());

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startServiceBtn = (Button) findViewById(R.id.btnStartService);
        Button stopServiceBtn = (Button) findViewById(R.id.btnStopService);
        Button setDataBtn = (Button) findViewById(R.id.btnSetData);
        startServiceBtn.setOnClickListener(this);
        stopServiceBtn.setOnClickListener(this);
        setDataBtn.setOnClickListener(this);
        connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mService = new Messenger(service);
                mBound = true;
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                mService = null;
                mBound = false;
            }
        };
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this, MessengerService.class);
        switch (v.getId()) {
            case R.id.btnStartService:
                bindService(intent, connection, Service.BIND_AUTO_CREATE);
                break;
            case R.id.btnStopService:
                unbindService(connection);
                break;
            case R.id.btnSetData:
                if (mBound) {
                    try {
                        Message message = new Message();
                        message.arg1 = 3;
                        message.arg2 = 4;
                        message.what = SIGN;
                        message.replyTo = mMessenger;
                        mService.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else {
                    Toast.makeText(this,
                            "当前服务未打开,请开启服务!", Toast.LENGTH_LONG).show();
                }
                break;
            default:
                break;
        }
    }

    private class MainHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SIGN:
                    Toast.makeText(MainActivity.this,
                            "服务器说等于:" + msg.arg1, Toast.LENGTH_LONG).show();
                    break;
                default:
                    break;
            }
            super.handleMessage(msg);
        }
    }

}

客户端连接到服务器后,向服务器发送两个值“3”“4”,然后服务器求和计算回复“7”,服务器接收到回复消息,并显示在界面。

补充一点点
想深入了解Messager的话推荐《Android 基于Message的进程间通信 Messenger完全解析》鸿洋大神的文章

6.注意事项

6.1同时启动和绑定

前面介绍了Service的启动状态和绑定状态,应该还记得在Service中onBind()是必须实现的方法,那是不是就意味着,在启动状态的Service中,onBind()不返回null,就可以启动绑定模式?同理,在绑定状态的Service中,是不是也可以进入启动状态?
答案是肯定的。
需要注意一点的时,只要Service进入过启动状态(调用startService()),服务将在后台运行,直到有Context调用了stopService()或是服务本身调用了stopSelf()方法抑或内存不足时才会销毁服务。

6.2生命周期控制

因为Service本身没有界面空着,为了把控Service的生命周期,通常情况下需要在客户端(例如Activity)生命周期中于合适的时间开启(绑定)和关闭(解绑)Service。需要注意的是切勿在 Activity 的 onResume() 和 onPause() 期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,这样反复绑定与解绑是不合理的。

6.3使用服务还是线程?

简单地说,服务是一种即使用户未与应用交互也可在后台运行的组件。 因此,您应仅在必要时才创建服务。
如需在主线程外部执行工作,不过只是在用户正在与应用交互时才有此需要,则应创建新线程而非服务。 例如,如果您只是想在 Activity 运行的同时播放一些音乐,则可在 onCreate() 中创建线程,在 onStart() 中启动线程,然后在 onStop() 中停止线程。您还可以考虑使用 AsyncTask 或 HandlerThread,而非传统的 Thread 类。如需了解有关线程的详细信息,请参阅进程和线程文档。
请记住,如果您确实要使用服务,则默认情况下,它仍会在应用的主线程中运行,因此,如果服务执行的是密集型或阻止性操作,则您仍应在服务内创建新线程。——《Android Developers Doc》

上一篇 下一篇

猜你喜欢

热点阅读