Android

Android 四大组件 —— 服务

凡凡 · 8月25日 · 2019年 · 68次已读

1 Service 简介

Service 是 Android 四大组件中与 Activity 最相似的组件,它们都代表可执行程序,Service 与 Activity 的区别在于:Service 一直在后台运行,它没有用户界面,所以它绝不会到前台上来。一旦 Service 被启动起来之后,它就与 Activity 一样,它完全具有自己的生命周期。那二者如何选择呢?

如果某个程序组件需要在运行时向用户呈现某种界面,或者该程序需要与用户交互,就需要使用 Activity,否则就应该考虑使用 Service 了。

开发 Service 的步骤与开发 Activity 的步骤很像,开发 Service 组件需要先开发一个 Service 的子类,然后在 AndroidManifest.xml 文件中配置该 Service,配置时可通过 <intent-filter.../> 元素指定它可被哪些 Intent 启动。

2 创建、配置 Service

开发 Service 需要两个步骤:

  • 定义一个继承 Service 的子类;
  • 在 AndroidManifest.xml 文件中配置该 Service;

Service 与 Activity 还有一点相似之处,它们都是从 Context 类派生出来的,因此它们都可调用 Context 里定义的如 getResources()、getContentResolver() 等方法。

与 Activity 相似的是,Service 中也定义了系列生命周期方法,如下所示:

  • abstract IBinder onBind(Intent intent):该方法是 Service 子类必须实现的方法。该方法返回一个 IBinder 对象,应用程序可通过该对象与 Service 组件通信;
  • void onCreate():当该 Service 第一次被创建后将立即回调该方法;
  • void onDestroy():当该 Service 被关闭之前将会回调该方法;
  • void onStartCommand(Intent intent, int flags, int startId):该方法的早期版本是 void onStart(Intent intent, int startId),每次客户端调用 startService(Intent) 方法启动该 Service 时都会回调该方法。
  • boolean onUnbind(Intent intent):当 Service 上绑定的所有客户端都断开连接时将会回调该方法。

下面举一个简单的例子来说明下:

在 Android Studio 快速创建 Service,如上所示,这样做的好处它会自动在 AndroidManifest.xml 中生成 <Service>...<service />

MyService.java

public class MyService extends Service {

    public MyService() {
    }

    // 必须实现的方法,该方法将返回 IBinder 对象
    // 可用该对象与 Service 组件进行通信
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    // 当该 Service 第一次被创建后将立即回调该方法
    @Override
    public void onCreate() {
        super.onCreate();
        Log.v("-fanfanblog.cn-","Service is Created");
    }

    // Service 被启动时回调该方法
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v("-fanfanblog.cn-","Service is Started");
        return START_STICKY;
    }

    // Service 被关闭之前回调
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.v("-fanfanblog.cn-","Service is Destroyed");
    }

}

上面这个 Service 什么也没干 —— 它只是重写了 Service 组件的 onCreate、onStartCommand、onDestroy、onBind 等方法,重写这些方法只是简单地在 Logcat 中输出一些信息。如果希望 Service 组件做某些事情,那么只要在 onCreate 或 onStartCommand 方法中定义相关业务逻辑代码即可。

定义了上面的 Service 之后,接下来需要在 AndroidManifest.xml 文件中配置该 Service,配置 Service 使用 <service>...</service> 元素,也可以使用 <intent-filter> 子元素,用于说明该 Services 可被哪些 Intent 启动。

AndroidManifest.xml

<!-- 配置一个 Service 组件 -->
<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="cn.fanfanblog.services.MYSERVICE" />
    </intent-filter>
</service>

当 Service 开发完成之后,接下来可在程序中运行该 Service 了,Android 系统中运行 Service 有如下两种方式:

  • 通过 Context 的 startService() 方法:通过该方法启用 Service,访问者与 Service 之间没有关联,即使访问者退出了,Service 仍然运行;
  • 通过 Context 的 bindService() 方法:使用该方法启用 Service,访问者与 Service 绑定在了一起,访问者一旦退出,Service 也就终止;

在界面上添加两个按钮,用于开启服务和关闭服务。

activity_main.xml

<Button
    android:text="启动服务"
    android:id="@+id/start"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    />

<Button
    android:text="关闭服务"
    android:id="@+id/stop"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    />

MainActivity.java

public class MainActivity extends AppCompatActivity {

    Button start, stop;

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

        // 获取程序界面中的 start、stop 两个按钮
        start = (Button) findViewById(R.id.start);
        stop = (Button)findViewById(R.id.stop);

        // 创建启动 Service 的 Intent
        final Intent intent = new Intent();

        // 为 Intent 设置 Action 属性
        intent.setAction("cn.fanfanblog.services.MYSERVICE");

        // 不加这句,Android 5.0 以上会报 Service Intent must be explitict 错误
        intent.setPackage(getPackageName());
        
        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 启动指定 Service
                startService(intent);
            }
        });

        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 停止指定 Service
                stopService(intent);
            }
        });
    }
}

运行之后,在 Logcat 下输出日志如下:

从上图中可以看出,当首次创建 Service 时,如果服务不关闭,再点启动,那么就不会再 Service is Created,也就是说只回调一次 onCreate() 方法,而连续点击启动服务按钮则连续回调 onStart() 方法。

3 startService 方式示例 —— 电话监听器

在第二小节的最后用 startService 方式创建了一个服务,然而什么都没做,这一小节将给出一个小应用 —— 电话监听器。下面来说下它的原理:

  • 在创建服务时获取电话管理器,注册监听器,监听电话状态;
  • 电话状态:闲置、响铃、通话、接听;
  • 监听电话需要权限;

实现步骤:

  • 继承 Service,编写自定义 Service,定义必要的成员变量;
  • 重写 onCreate 方法获取电话管理器,创建监听器;
  • 重写 onDestroy 方法,取消监听;
  • 清单文件中增加 标签;
  • 添加监听电话状态权限;
  • 使用 startService 启动服务;

首先用 Android Studio 快速创建 Service:

MyService.java

public class MyService extends Service {

    private final String TAG = "MyService";
    private TelephonyManager tm;
    private MyPhoneStateListener listener;

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        // 获取电话管理器
        tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
        // 创建电话监听器
        listener = new MyPhoneStateListener();
        // 监听电话的呼叫状态
        tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy");
        super.onDestroy();
        // 取消电话状态接听
        tm.listen(listener, PhoneStateListener.LISTEN_NONE);
    }

    private class MyPhoneStateListener extends PhoneStateListener {

        // 重写 onCallStateChanged 方法,监听电话状态
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            super.onCallStateChanged(state, incomingNumber);
            switch (state) {
                case TelephonyManager.CALL_STATE_IDLE:
                    Log.d(TAG, "电话状态:闲置");
                    break;
                case TelephonyManager.CALL_STATE_RINGING:
                    Log.d(TAG, "电话状态:响铃");
                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK:
                    Log.d(TAG, "电话状态:接听");
                    break;
            }
        }
    }
}

在清单文件中注册 MyService 服务,添加读取电话状态权限。如下:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="cn.fanfanblog.services.MYSERVICE" />
    </intent-filter>
</service>

最后在 Activity 中启动服务,关闭服务:

MainActivity.java

public class MainActivity extends AppCompatActivity {

    Button start, stop;

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

        start = (Button)findViewById(R.id.start);
        stop = (Button)findViewById(R.id.stop);

        final Intent intent = new Intent();

        intent.setAction("cn.fanfanblog.services.MYSERVICE");

        intent.setPackage(getPackageName());

        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(intent);
            }
        });

        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(intent);
            }
        });
    }
}

运行后结果如下:

4 startService 方式实例 —— 计时器

在上一小节给出了一个非常简单 startService 案例,这节也同样给出一个,但这个例子会与之前的广播相结合。下面来说下它的原理:

  • 在创建服务时在 onStartCommand 方法中启动定时器,获取时间,定时发送广播;
  • 在创建 Activity 时注册广播接收者,接收时间广播,更新控件;

实现步骤:

  • 继承 Service,编写自定义 Service,定义必要的成员变量;
  • 重写 onCreate 方法启动定时器,获取时间;
  • 重写 onStartCommand,重置时间;
  • 重写 onDestroy 方法,关闭定时器;
  • 清单文件中增加 标签;
  • 在 Activity 中创建内部类广播接收者,重写 onReceive 方法;
  • 在 Activity onCreate 中动态注册广播接收者;
  • 启动服务;

创建服务:TimerService.java

public class TimerService extends Service {

    private Timer timer = null;
    private int mCount = 0;

    public TimerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        // 创建一个定时器
        timer = new Timer();
        // 创建一个定时器任务
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                mCount++;
                Log.d("-fanfanblog.cn-", "计时:" + mCount);
                Intent intent = new Intent();
                intent.setAction("action.timer");
                intent.putExtra("count", mCount);
                sendBroadcast(intent);

            }
        };
        // 设置定时器任务的触发间隔
        timer.schedule(task, 0, 1000);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 重置定时器
        mCount = 0;
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        timer.cancel();
    }
}

TimerReceiver.java

public class TimerReceiver extends BroadcastReceiver {

    Activity mContext;

    public TimerReceiver(Activity mContext) {
        this.mContext = mContext;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        // 获取 定时器的数据
        int count = intent.getIntExtra("count", 0);
        // 更新到控件上
        Log.d("-fanfanblog.cn-", "当前计时:" + count);
        Activity activity = mContext;
        TextView textView =  activity.findViewById(R.id.tv1);
        textView.setText("当前计时: " + count);
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

        initReceiver();
    }

    private void initReceiver() {
        // 1. 创建广播接收者
        TimerReceiver timerReceiver = new TimerReceiver(this);
        // 2. 指定这个广播接收者的动作
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("action.timer");
        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
        // 3. 注册广播接收者到系统中
        registerReceiver(timerReceiver, intentFilter);
    }

    boolean mIsClick = false;
    public void btnStart(View view) {
        Intent intent = new Intent();
        intent.setClass(this,TimerService.class);
        startService(intent);
        mIsClick = true;
        if(mIsClick){
            Button button = findViewById(R.id.btn1);
            button.setText("重新开始计时");
        }
    }

    public void btnStop(View view) {
        Intent intent = new Intent();
        intent.setClass(this,TimerService.class);
        stopService(intent);

        mIsClick = false;
        Button button = findViewById(R.id.btn1);
        button.setText("开始计时");
    }
}

activity_main.xml

<Button
    android:id="@+id/btn1"
    android:text="开始计时"
    android:onClick="btnStart"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    />

<Button
    android:text="关闭计时"
    android:onClick="btnStop"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" 
    />

<TextView
    android:text="当前计时未开始"
    android:textSize="16dp"
    android:id="@+id/tv1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" 
    />

运行后如下所示:

5 绑定本地 Service 并与之通信

当程序通过 startService() 和 stopService() 启动、关闭 Service 时,Service 与访问者之间基本上不存在太多的关联,因此 Service 和访问者之间也无法进行通信、数据交换。

如果 Service 和访问者之间需要进行方法调用或数据交换,则应该使用 bindService() 和 unbindService() 方法启动、关闭服务。

bindService() 函数原型为 bindService(Intent service, ServiceConnection conn, int flags) 该方法中的三个参数解释如下:

  • servicce:该参数通过 Intent 指定要启动的 Service;
  • conn:该参数是一个 ServiceConnection 对象,该对象用于监听访问者与 Service 之间的连接情况。当访问者与 Service 之间连接成功时将回调该 ServiceConnection 对象的 onServiceConnected() 方法;当访问者与 Service 之间断开连接时将回调该 ServiceConnection对象的 onServiceDisconnected() 方法;
  • flags:指定绑定时是否自动创建 Service(如果 Service 还未创建)。该参数可指定为 0(不自动创建)或 BIND_AUTO_CREATE (自动创建);

还记得那个 onBind() 方法吗?这个方法在开发 Service 类必须实现的一个方法,在绑定本地 Service 的情况下,onBind() 方法所返回的 IBinder 对象将会传给 ServiceConnection 对象里的 onServiceConnected() 方法的 service 参数,这样访问者就可以通过该 IBinder 对象与 Service 进行通信。

实际开发过程时通常会采用继承 Binder(IBinder 的实现类)的方式实现自己的 IBinder 对象。下面将会演示下如何在 Activity 中绑定本地 Service,并获取 Service 的运行状态。

MainActivity.java

public class MainActivity extends AppCompatActivity {

    Button bind, unbind, getServiceStatus;

    // 保持所启动的 Service 的 IBinder 对象
    BindService.MyBinder binder;

    // 定义一个 ServiceConnection 对象
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("-fanfanblog.cn-","--Service Connected--");
            // 获取 Service 的 onBind 方法所返回的 MyBinder 对象
            binder = (BindService.MyBinder)service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("-fanfanblog.cn-","--Service Disconnected--");
        }
    };

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

        bind = (Button)findViewById(R.id.bind);
        unbind = (Button)findViewById(R.id.unbind);
        getServiceStatus = (Button)findViewById(R.id.getServiceStatus);

        final Intent intent = new Intent();

        intent.setAction("cn.fanfanblog.service.BIND_SERVICE");
        intent.setPackage(getPackageName());
        bind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindService(intent, conn, Service.BIND_AUTO_CREATE);
            }
        });

        unbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(conn);
            }
        });

        getServiceStatus.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this,
                        "Service 的 count 值为:" + binder.getCount(),
                        Toast.LENGTH_SHORT).show();
            }
        });

    }
}

BindService.java

public class BindService extends Service {

    private int count;
    private boolean quit;

    // 定义 onBinder 方法所返回的对象
    private MyBinder binder = new MyBinder();

    public class MyBinder extends Binder {
        public int getCount() {
            // 获取 Service 的运行状态:count
            return count;
        }
    }

    public BindService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        Log.d("-fanfanblog.cn-","Service is Binded");
        // 返回 IBinder 对象
        return binder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("-fanfanblog.cn-","Service is Created");
        // 启动一个线程、动态地修改 count 状态值
        new Thread() {
            @Override
            public void run() {
                while(!quit) {
                    try {
                        Thread.sleep(1000);
                    }
                    catch(InterruptedException e) {
                    }
                    count++;
                }
            }
        }.start();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d("-fanfanblog.cn-","Service is Unbinded");
        return true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        this.quit = true;
        Log.d("-fanfanblog.cn-","Service is Destroyed");
    }
}

AndroidManifest.xml

<service
    android:name=".BindService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="cn.fanfanblog.service.BIND_SERVICE" />
    </intent-filter>
</service>

activity_main.xml

<Button
    android:id="@+id/bind"
    android:text="bind"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" 
    />

<Button
    android:id="@+id/unbind"
    android:text="unbind"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" 
    />

<Button
    android:id="@+id/getServiceStatus"
    android:text="getServiceStatus"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" 
    />

运行后结果如下:

6 Service 的生命周期

以一张图来说明,如下:

0 条回应