Android学习之服务初体验

•概念

  Service(服务)是一个长期运行在后台,没有用户界面的应用组件,即使切换到另一个应用程序或者后台,服务也可以正常运行;

  因此,服务适合执行一些不需要显示界面的后台耗时操作,比如下载网络数据,播放音乐等。

•定义一个服务

  新建一个 ServiceTest 项目,然后右击  com.example.servicetest->New->Service->Service ;

  会弹出如下图所示的窗口:

  可以看到,这里我们将服务命名为 MyService(由于我之前创建过,所以左下角提示名字重复):

  • exported :表示是否允许除了当前程序之外的其他程序访问这个服务

    • 如果设置为 true,则能够被调用或交互,通常如果一个服务需要跨进程使用需要这么设置,否则不能
    • 设置为 false 时,只有同一个应用程序的组件或带有相同用户 ID 的应用程序才能启动或绑定该服务
  • enabled :指该服务是否能够被实例化

    • 如果设置为 true,则能够被实例化,否则不能被实例化,默认值是 true
    • 一般情况下,我们都会需要实例化,所以也可以选择不设置

  需要注意的是,每一个服务都需要在 AndroidManifest.xml 文件中进行注册,

  因为我们是通过 Android Studio New 的 Service ,

  所以 Android Studio 默认帮我们在清单文件中添加对该 Service 的注册;

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.servicetest">

    <application
        ......>

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true" />
            

        <activity android:name=".MainActivity">
            ......
        </activity>
        
    </application>

</manifest>

  现在观察 MyService 中的代码,如下所示:

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

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

  可以看到,MyService 是继承自 Service 类的,说明这是一个服务;

  目前,MyService 中可以算是空空如也,但有一个  onBind()  方法特别醒目,

  这个方法是 Service 中唯一的一个抽象方法,所以必须要在子类里实现,后面会提及该方法的用法;

  既然是定义一个服务,自然应该在服务中去处理一些事情,这时就要重写 Service 中的另外一些方法了;

MyService.java

public class MyService extends Service {
    
    public final static String TAG = "MyService";
    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();
        Log.d(TAG, "onCreate: ");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }
}

  可以看到,在该代码中重写了  onCreate()onStartCommand() , onDestroy()  方法,并分别在它们的方法体中打印Log日志;

•启动和停止服务

  启动和停止服务主要是借助 Intent 来实现的,下面就让我们在 ServiceTest 项目中尝试去启动以及停止 MyService;

  首先修改 activity_main.xml 中的代码;

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp">

    <Button
        android:id="@+id/start_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Service"
        android:textAllCaps="false"
        />

    <Button
        android:id="@+id/stop_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Stop Service"
        android:textAllCaps="false"
        />

</LinearLayout>

  在该代码中,仅仅添加了两个按钮,分别是用于启动服务和停止服务的;

  然后,修改 MainActivity.java 中的代码;

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private Button startService;
    private Button stopService;

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

        startService = findViewById(R.id.start_service);
        startService.setOnClickListener(this);

        stopService = findViewById(R.id.stop_service);
        stopService.setOnClickListener(this);

    }

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

  可以看到,这里在  onCreate() 方法中分别获取到了 startService 按钮 和 stopService 按钮 的实例,

  并给他们注册了点击事件;

  然后在点击事件里构建出了一个 Intent 对象,并通过调用  startService(intent) 方法来启动 MyService 服务,

  调用  stopService(intent) 来停止 MyService 服务;

   startService() 和 stopService() 方法都是定义在 Context 类中的,所以我们在活动里可以直接调用这两个方法。

运行效果

  通过观察日志文件可以看到:

  •  onCreate() :服务第一次被创建的时候调用,只执行一次
  •  onStartCommand() :每次服务启动的时候调用
    • 在第二次点击 Start Service 按钮的时候,只打印了 onStartCommand: 语句
    • 如果我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在 onStartCommand() 中
  •  onDestory() :服务被销毁的时候调用,只执行一次

  需要注意的是,如果在活动中只点击了 startService 按钮,而没有点击 stopService 按钮,那么服务会一直处于运行状态,

  如果想要 MyService 服务停止运行,除了点击 stopService 按钮外,

  还可以通过在 MyService.java 中任何一个方法中调用  stopSelf()  方法,即可将该服务停止;

  例如,我在  onStartCommand() 中调用  stopSelf() 方法,观察 Logcat 的打印语句;

运行效果

  果不其然,打印完  onStartCommand: 语句后紧跟着打印了 onDestroy: 语句。

•活动和服务进行通信

  在上面的介绍中,我们只是实现了在 Activity 中开启和关闭一个服务,

  然而服务开启后就和这个 Activity 没什么联系了;

  其实二者是可以继续保持联系的,还记得前面提到的一个  onBind() 方法吧,其实它就是 Service 与 Activity 之间建立通信的桥梁;

  比如说,我们希望 MyService 里提供一个下载功能,然后在活动中可以决定何时开始下载,以及随时查看下载进度;

  实现这个功能的思路是创建一个专门的 Binder 对象来对下载功能进行管理;

  修改 MyService.java 中的代码;

MyService.java

public class MyService extends Service {
    
    public static final String TAG = "MyService";
    private DownloadBinder mBinder = new DownloadBinder();
    
    class DownloadBinder extends Binder {
        
        public void startDownload(){//开始下载
            Log.d(TAG, "startDownload: ");
        }
        
        public int getProgress(){//获取下载进度
            Log.d(TAG, "getProgress: ");
            return 0;
        }
    }
    
    @Override
    public IBinder onBind(Intent intent) {
       return mBinder;
    }
    
    public MyService() {}

    @Override
    public void onCreate() {...}

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {...}
    
    @Override
    public void onDestroy() {...}
}

  可以看到,这里我们新建了一个 DownloadBinder 类,并让他继承自 Binder;

  然后在他的内部提供了开始下载以及查看下载进度的方法,

  当然,这只是两个模拟方法,仅仅是在这两个方法中打印了一行日志;

  接着在 MyService.java 中创建了 DownloadBinder 实例,然后再  onBind() 方法中返回了这个实例;

  这样,MyService 中的工作就全部完成了;

  下面就要看一看,在活动中如何去调用服务里的这些方法;

  首先在布局文件中新增两个按钮;

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp">

    <Button .../>

    <Button .../>

    <Button
        android:id="@+id/bind_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Bind Service"
        android:textAllCaps="false"
        />
    <Button
        android:id="@+id/unbind_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Unbind Service"
        android:textAllCaps="false"
        />

</LinearLayout>

  这两个按钮分别是用于绑定服务和取消绑定服务的;

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    ......
    
    private Button bindService;
    private Button unbindService;
    private MyService.DownloadBinder downloadBinder;
    
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        ......

        bindService = findViewById(R.id.bind_service);
        bindService.setOnClickListener(this);
        unbindService = findViewById(R.id.unbind_service);
        unbindService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this,MyService.class);
        switch(v.getId()){
        
            ......
            
            case R.id.bind_service:
                bindService(intent,connection,BIND_AUTO_CREATE);
                break;
            case R.id.unbind_service:
                unbindService(connection);
                break;
            default:
                break;
        }
    }
}

  可以看到,这里我们首先创建了一个 ServiceConnection 的匿名类,

  在里面重写了 onServiceConnected() 方法和 onServiceDisconnected() 方法,

  这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用;

  在  onServiceConnected() 方法中,我们又通过向下转型得到了 DownloadBinder 实例;

  然后调用了 DownloadBinder 的  startDownload() 和  getProgress() 方法。

  当然,现在活动和服务还没进行绑定呢,这个功能是在 bindService 按钮 的点击事件里完成的;

  在点击事件里,通过调用  bindService() 方法将 MainActivity 和 MyService 进行绑定,

  调用  unbindService() 方法解除绑定;

  其中, bindService(Intent service,ServiceConnection conn,int flags) 传递了三个参数:

  •  Intent :用于指定要启动的 Service

  •  ServiceConnection :用于监听调用者与 Service 之间的连接状态

    • 当调用者与Service连接成功时,将回调该对象的  onServiceConnected()  方法
    • 断开连接时,将回调该对象的  onServiceDisconnected() 方法
  •  flag :指绑定时是否自动创建 Service(如果 Service 还未创建)

    • 可指定为 0,即不自动创建
    • 也可指定为  BIND_AUTO_CREATE  ,即自动创建

  现在,让我们重新运行一下程序;

运行效果

  可以看到,再点击 Bind Service 按钮 的时候,首先是 MyService 的  onCreate()  方法得到了执行,

  然后  startDownload()  和  getProgress()  方法都得到了执行;

  另外需要注意的是,任何一个服务在整个应用程序范围内都是通用的,即 MyService 不仅可以和 MainActivity 绑定,

  还可以和任何一个其他的活动进行绑定,而且在绑定完成后,他们都可以获取到相同的 DownloadBinder 实例。

•服务的生命周期

服务的启动方式

  通过上面的学习,可以看到服务有两种启动方式:

  • 通过  startService(Intent intent)  方式启动服务

    • 通过 startService 方式启动服务,服务会长期在后台运行
    • 并且服务的状态与开启者的状态没有关系,即便启动服务的组件已经被销毁,服务也依旧会运行
  • 通过 bindService 方式启动服务

    • 通过 bindService 方式启动服务,服务会与组件进行绑定
    • 一个被绑定的服务提供一个客户端与服务器接口,允许组件与服务进行交互,发送请求,得到结果
    • 多个组件可以绑定一个服务,当调用  onUnbind()  方法时,这个服务就被销毁了,即调用  onDestory()  方法

图示

文字描述


 ——《第一行代码》

•声明

参考资料

posted @ 2021-03-29 10:54  MElephant  阅读(114)  评论(0编辑  收藏  举报