首页 > 技术文章 > Service组件应用实例

huolongluo 2022-01-06 18:23 原文

这是通过Service实现的一个计数器服务功能实例。

直接先上代码:

ICounterCallback.java

1 package com.example.counter;
2 
3 /**
4  * 火龙裸
5  * */
6 public interface ICounterCallback {
7     void count(int val);
8 }

  这个文件定义了一个计数器回调接口ICounterCallback,它只有一个成员函数count,用来将CounterService服务组件的当前“计数结果”,实时地更新到对应的CounterActivity组件上,进行用户界面显示。

 

ICounterService.java

1 package com.example.counter;
2 
3 /**
4  * 火龙裸
5  * */
6 public interface ICounterService {
7     void strtCounter(int initVal, ICounterCallback callback);
8     void stopCounter();
9 }

  这个文件定义了一个计数器接口ICounterService,它有两个成员函数startCounter和stopCounter;其中,前者用来启动计数器,后者用来停止计数器。在启动计数器时,可以指定计数器的初始值,以及更新用户界面的回调接口。

 

CounterService.java

 1 package com.example.counter;
 2 
 3 import android.app.Service;
 4 import android.content.Intent;
 5 import android.os.AsyncTask;
 6 import android.os.Binder;
 7 import android.os.IBinder;
 8 import android.util.Log;
 9 
10 import androidx.annotation.Nullable;
11 
12 /**
13  * 火龙裸
14  * */
15 public class CounterService extends Service implements ICounterService {
16     private static final String TAG = "CounterService";
17 
18     private boolean stop = false;
19     private ICounterCallback counterCallback = null;
20 
21     private final CounterBinder binder = new CounterBinder();
22 
23     public class CounterBinder extends Binder{
24         public CounterService getService() {
25             return CounterService.this;
26         }
27     }
28 
29     @Override
30     public void onCreate() {
31         super.onCreate();
32         Log.e(TAG, "onCreate: =============");
33     }
34 
35     @Nullable
36     @Override
37     public IBinder onBind(Intent intent) {
38         return binder;
39     }
40 
41 
42 
43     @Override
44     public void strtCounter(int initVal, ICounterCallback callback) {
45         counterCallback = callback;
46 
47         AsyncTask<Integer, Integer, Integer> task = new AsyncTask<Integer, Integer, Integer>() {
48             @Override
49             protected Integer doInBackground(Integer... integers) {
50                 Integer initCounter = integers[0];
51 
52                 stop = false;
53 
54                 while (!stop) {
55                     publishProgress(initCounter);
56 
57                     try {
58                         Thread.sleep(1000);
59                     } catch (InterruptedException e) {
60                         e.printStackTrace();
61                     }
62 
63                     initCounter++;
64                 }
65 
66                 return initCounter;
67             }
68 
69             @Override
70             protected void onProgressUpdate(Integer... values) {
71                 super.onProgressUpdate(values);
72 
73                 int val = values[0];
74                 counterCallback.count(val);
75             }
76 
77             @Override
78             protected void onPostExecute(Integer val) {
79                 counterCallback.count(val);
80             }
81         };
82 
83         task.execute(initVal);
84         Log.e(TAG, "strtCounter: 启动AsyncTask");
85     }
86 
87     @Override
88     public void stopCounter() {
89         stop = true;
90         Log.e(TAG, "stopCounter: 停止AsyncTask 任务标志位");
91     }
92 }

  CounterService是一个Service组件,因此,它必须要从Service类继承下来。CounterService组件同时实现了计数器接口ICounterService,用来提供计数器服务。在CounterService组件启动时,它的成员函数onCreate就会被调用;而在CounterService组件被绑定时,它的成员函数onBind就会被调用。

注意:CounterService组件有一个成员变量binder,它指向一个类型为CounterBinder的Binder本地对象。当CounterService组件被绑定时,它就会将内部的Binder本地对象binder返回给绑定着,以便绑定者可以通过这个Binder本地对象来获得它的一个计数器访问接口。

这里之所以要返回一个Binder本地对象给绑定者,是因为绑定着与CounterService组件可能是运行在两个不同的应用程序进程中的。不过本例中,启动CounterService组件的“CounterActivity组件”和“CounterService”组件是运行在同一个应用程序进程中的。

  CounterService组件的成员函数startCounter和stopCounter分别用来启动和停止内部的计数器;其中,成员函数startCounter使用一个异步任务task来启动一个计数器,成员函数stopCounter通过将CounterService组件的成员变量stop设置为true来停止这个计数器。

当计数器被停止执行时,异步任务task的成员函数onPostExecute就会被调用,它主要是使用CounterService组件内部的一个ICounterCallback接口将计数器的最终技术结果更新到用户界面上显示出来。

注意:在异步任务task的三个成员函数中,只有成员函数doInBackground是在后台线程中执行的,其余两个成员函数onProgressUpdate和onPostExecute都是在应用程序的主线程中执行的,因此,它们可以被用来更新用户界面。

 

CounterActivity.java

 1 package com.example.counter;
 2 
 3 import androidx.appcompat.app.AppCompatActivity;
 4 
 5 import android.content.ComponentName;
 6 import android.content.Intent;
 7 import android.content.ServiceConnection;
 8 import android.os.Bundle;
 9 import android.os.IBinder;
10 import android.util.Log;
11 import android.view.View;
12 import android.widget.Button;
13 import android.widget.TextView;
14 
15 /**
16  * 火龙裸
17  * */
18 public class CounterActivity extends AppCompatActivity implements View.OnClickListener, ICounterCallback {
19     private static final String TAG = "CounterActivity";
20 
21     private Button btn_start;
22     private Button btn_stop;
23     private TextView tv_count;
24 
25     private ICounterService counterService = null;
26 
27     @Override
28     protected void onCreate(Bundle savedInstanceState) {
29         super.onCreate(savedInstanceState);
30         setContentView(R.layout.activity_counter);
31         btn_start = findViewById(R.id.btn_start);
32         btn_stop = findViewById(R.id.btn_stop);
33         tv_count = findViewById(R.id.tv_count);
34         btn_start.setOnClickListener(this);
35         btn_stop.setOnClickListener(this);
36 
37         Intent serviceIntent = new Intent(CounterActivity.this, CounterService.class);
38         bindService(serviceIntent, serviceConnection, BIND_AUTO_CREATE);
39     }
40 
41     @Override
42     public void onClick(View view) {
43         switch (view.getId()){
44             case R.id.btn_start:
45                 counterService.strtCounter(0, this);
46                 break;
47             case R.id.btn_stop:
48                 counterService.stopCounter();
49                 break;
50         }
51     }
52 
53     /**
54      * 计数器服务的回调
55      * */
56     @Override
57     public void count(int val) {
58         tv_count.setText("计算值为:" + val);
59     }
60 
61     @Override
62     protected void onDestroy() {
63         super.onDestroy();
64         unbindService(serviceConnection);
65     }
66 
67     private ServiceConnection serviceConnection = new ServiceConnection() {
68         @Override
69         public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
70             counterService = ((CounterService.CounterBinder)iBinder).getService();
71             Log.e(TAG, "onServiceConnected: ======================>");
72         }
73 
74         @Override
75         public void onServiceDisconnected(ComponentName componentName) {
76             counterService = null;
77             Log.e(TAG, "onServiceDisconnected: >>>>>>>>>>>>>>>");
78         }
79     };
80 }

  CounterActivity是应用程序的根Activity组件,因此,它必须要从类Activity继承下来。CounterActivity同时实现了计数器回调接口ICounterCallback,以便可以实时地回调更新用户界面上的计数值。

 

  CounterActivity组件被启动时,它的成员函数onCreate就会被调用,这时候它就会调用其父类ContextWrapper的成员函数bindService来启动CounterService组件。当CounterService组件启动起来之后,CounterActivity组件内部的ServiceConnection对象serviceConnection的成员函数onServiceConnected就会被调用,这时候CounterActivity组件就会获得CounterService服务组件中的一个Binder本地对象。由于CounterActivity组件和CounterService服务组件是运行在同一个应用程序进程中的,因此,ServiceConnection对象serviceConnection的成员函数onServiceConnected可以安全地将前面获得的一个Binder本地对象转换为一个类型为CounterBinder的对象,接着再调用它的成员函数getService来获得一个计数器接口。有了这个计数器接口之后,CounterActivity组件就相当于将CounterService服务组件绑定起来了。

  由于CounterActivity组件在启动时绑定了CounterService组件,因此当它销毁时,即它的成员函数onDestrory被调用时,它就必须要调用父类ContextWrapper的成员函数unbindService来解绑CounterService服务组件。解绑成功后,CounterActivity组件内部的ServiceConnection对象serviceConnection的成员函数onServiceDisconnected就会被调用,用来执行清理工作。

 

activity_counter.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:app="http://schemas.android.com/apk/res-auto"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     tools:context=".CounterActivity">
 8 
 9     <Button
10         android:id="@+id/btn_start"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content"
13         android:text="开始"
14         app:layout_constraintBottom_toBottomOf="parent"
15         app:layout_constraintHorizontal_bias="0.498"
16         app:layout_constraintLeft_toLeftOf="parent"
17         app:layout_constraintRight_toRightOf="parent"
18         app:layout_constraintTop_toTopOf="parent"
19         app:layout_constraintVertical_bias="0.196" />
20 
21     <TextView
22         android:id="@+id/tv_count"
23         android:layout_width="wrap_content"
24         android:layout_height="wrap_content"
25         android:text="计算值为:"
26         android:textSize="20sp"
27         app:layout_constraintBottom_toBottomOf="parent"
28         app:layout_constraintHorizontal_bias="0.498"
29         app:layout_constraintLeft_toLeftOf="parent"
30         app:layout_constraintRight_toRightOf="parent"
31         app:layout_constraintTop_toTopOf="parent"
32         app:layout_constraintVertical_bias="0.311" />
33 
34     <Button
35         android:id="@+id/btn_stop"
36         android:layout_width="wrap_content"
37         android:layout_height="wrap_content"
38         android:text="停止"
39         app:layout_constraintBottom_toBottomOf="parent"
40         app:layout_constraintHorizontal_bias="0.498"
41         app:layout_constraintLeft_toLeftOf="parent"
42         app:layout_constraintRight_toRightOf="parent"
43         app:layout_constraintTop_toTopOf="parent"
44         app:layout_constraintVertical_bias="0.409" />
45 
46 </androidx.constraintlayout.widget.ConstraintLayout>

 

AndroidManifest.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 3     package="com.example.counter">
 4 
 5     <application
 6         android:allowBackup="true"
 7         android:icon="@mipmap/ic_launcher"
 8         android:label="@string/app_name"
 9         android:roundIcon="@mipmap/ic_launcher_round"
10         android:supportsRtl="true"
11         android:theme="@style/Theme.Counter">
12         <activity
13             android:name=".CounterActivity"
14             android:exported="true">
15             <intent-filter>
16                 <action android:name="android.intent.action.MAIN" />
17 
18                 <category android:name="android.intent.category.LAUNCHER" />
19             </intent-filter>
20         </activity>
21         <service
22             android:name=".CounterService"
23             android:enabled="true" />
24     </application>
25 
26 </manifest>

 

推荐阅读