Android安卓四大组件之Service

Posted woodwhale

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android安卓四大组件之Service相关的知识,希望对你有一定的参考价值。

android】安卓四大组件之Service

1、Service的介绍

1.1 什么是service

Service即服务,用大白话讲就是——长期运行在后台的程序,如果我们说的官方一点,那么就是用于执行长期运行的任务,并且没有与用户交互的功能。

每一个service都和activity一样,需要在manifest.xml中配置,赋予其生命。使用<service>标签即可配置

在activity类中,可以使用Context.startService()方法来开启服务,使用Context.stopService()方法来关闭服务。同时,我们还可以通过绑定的方式来开启服务,通过解除绑定的方式来关闭服务。

1.2 为什么需要使用service

第一个原因:服务适用于执行长期后台运行的操作,有时候我们没有用户交互的界面,但是该程序仍然需要执行。

最常见的例子有如下几种:

  • 音乐APP,我们播放歌曲的时候,可以进行看小说、浏览网页等等操作。
  • 后台下载,我们在后台下载的时候,并没有明显的交互,但是仍然可以挂在后台下载。

在了解第二个原因之前,我们需要给几个概念:

  1. 前台进程:最顶部直接跟用户交互的进程。比如说我们操作的Activity界面.
  2. 可见进程:可见但不操作的进程,比如说我们在一个Activity的顶部弹出一个Dialog,这个Dialog就是前台进程,但是这个Activity则是可见进程。
  3. 服务进程:服忙碌的后台进程,虽然是在后台,但是它在运行。
  4. 后台进程:后台进程就是退隐到后台,不做事的进程。
  5. 空进程:没有任何东西在上面跑着的进程,仅作缓存作用。

如果内存不够了,首先杀的是空进程,要是还不够就杀后台进程,要是还不够,那么就杀服务,但是服务被杀死以后,等内存够用了,服务又会自动跑起来了。

如果我们需要长期后台操作的任务,使用Service就可以了

2、Service的生命周期方法

和activity一样,service也有它的生命周期

2.1 最基本的生命周期

我们这里使用最基本的三个生命周期方法:

  • onCreate(),创建服务时调用
  • onStartCommand(Intent intent, int flags, int startId),开启服务时调用
  • onDestroy(),销毁服务时调用

我们来模拟服务开启和停止:

  • 开启service的方法是:startService()
  • 停止service的方法是:stopService()

首先创建一个带有两个button的页面,这两个button分别绑定开启服务和停止服务的方法。

记得要去manifest.xml中注册service噢,我这里创建了一个FirstService类extends了Service

<service android:name=".study.test.services.FirstService"/>

前端xml页面代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".study.test.ServiceActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点我开启服务"
        android:id="@+id/bt_startService"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点我关闭服务"
        android:id="@+id/bt_stopService"/>

</LinearLayout>

然后是activity代码

public class ServiceActivity extends AppCompatActivity implements OnClickListener 

    private static final String TAG = "ServiceActivity";
    private Button stop;
    private Button start;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_service);
        initView();
    

    public void initView() 
        start = this.findViewById(R.id.bt_startService);
        stop = this.findViewById(R.id.bt_stopService);
        start.setOnClickListener(this);
        stop.setOnClickListener(this);
    


    @Override
    public void onClick(View v) 
        if (v == start) 
            Log.d(TAG,"114514");
            Intent serviceIntent = new Intent();
            serviceIntent.setClass(this, FirstService.class);
            this.startService(serviceIntent);
         else if (v == stop) 
            Log.d(TAG,"1919810");
            Intent serviceIntent = new Intent();
            serviceIntent.setClass(this, FirstService.class);
            this.stopService(serviceIntent);
        
    


最后是service的代码:

// 继承service
public class FirstService extends Service 
    private static final String TAG = "FirstService";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        return null;
    

    // create的周期方法
    @Override
    public void onCreate() 
        Log.d(TAG,"create !");
        super.onCreate();
    

    // start的周期方法
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) 
        Log.d(TAG,"start !");
        return super.onStartCommand(intent, flags, startId);
    

    // destroy的周期方法
    @Override
    public void onDestroy() 
        Log.d(TAG,"destroy !");
        super.onDestroy();
    


我们来测试一下,效果如下:(注意控制台的log输出)

通过观察可以发现,第一次开启服务会调用onCreate()方法和onStartCommand()方法,第二次开启,仅仅调用onStartCommand()方法,因为该服务已经被create过了。点击停止服务,会调用onDestroy()方法,第二次暂停没有反映,因为服务已经被销毁过了!

2.2 Service绑定的生命周期

我们可以发现,在实现Service的类中,会默认重写一个onBind()的方法:

  • 这个方法的作用就是,用来绑定服务,接下来的一小节我们会讲。
  • 我们只需要知道,这个方法是activity类中使用bindService()方法后的回调方法,会让我们activity中的ServiceConnection类调用onServiceConnected()方法,从而获取一个实现IBinder接口的service对象。
public IBinder onBind(Intent intent) 
    return null;

还有一个onUnBinde()的方法,就如同字面意思一样,就是解除绑定的回调方法:

  • 这个方法就是在activity类中调用unbindService()方法后的一个回调方法
public boolean onUnbind(Intent intent) 
    Log.d(TAG, "开始解除绑定!onUnbind...");
    return super.onUnbind(intent);

3、Service的绑定

前面的开启服务方式,我们会发现——无法进行各个类之间的交互,这就是一个缺点。

所以,我们可以是用绑定服务的方式来进行交互:

  • 使用bindService(Intent service, ServiceConnection conn, int flags)方法来进行绑定
  • 使用unbindService(ServiceConnection conn)来解除绑定

我们来讲一下ServiceConnection这个类,这就是一个服务连接类,如果new一个对象,需要重写两个方法:

  • onServiceConnected(ComponentName name, IBinder service)方法

    1. 该方法是service绑定成功的回调方法,会给一个实现IBinder接口的service对象
    2. 还会返回一个ComponentName对象,可以获取service的name信息
  • onServiceDisconnected(ComponentName name)方法

    1. 该方法时service非正常解除绑定的回调方法,在连接正常关闭的情况下是不会被调用的。
  1. 该方法只在Service 被破坏了或者被杀死的时候调用。 例如, 系统资源不足, 要关闭一些Services, 刚好连接绑定的 Service 是被关闭者之一, 这个时候onServiceDisconnected() 就会被调用。

绑定服务的方式和开启服务的方式的区别:

  • bindService开启的服务,在系统里是看不到服务在运行的:
  • startService启动的服务,则会在设置-应用里看到

最后注意一点,使用绑定服务的方式,在Context(Activity)被销毁之前,一定要解除绑定,否则会泄露!

3.1 尝试绑定一个Service类中的内部类?

我们来写一个例子,来实现调用服务类内部的一个private方法

首先还是xml前端页面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".study.test.ServiceActivity">


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点我开启服务"
        android:id="@+id/bt_startService"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点我关闭服务"
        android:id="@+id/bt_stopService"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点我绑定服务"
        android:id="@+id/bt_bindService"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点我解绑服务"
        android:id="@+id/bt_unbindService"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="调用服务内部方法"
        android:id="@+id/bt_serviceMethod"/>


</LinearLayout>

然后是我们的service类:

  • 重点是我们创建了一个内部类CommunicateBinder继承了Binder,然后写了一个callInnerMethod()方法调用内部private的innerMethod()方法
  • 我们的目的是在activity启动服务后可以获取到这个内部类CommunicateBinder的实例化对象,从而调用callInnerMethod()方法来实现调用FirstService中的private的innerMethod()方法
  • 需要注意的是我们在onBind()触发方法中return了一个new CommunicateBinder()——一个实例化对象
public class FirstService extends Service 
    private static final String TAG = "FirstService";

    public class CommunicateBinder extends Binder 
        public void callInnerMethod()
            innerMethod();
        
    

    private void innerMethod() 
        Log.d(TAG,"内部方法!innerMethod...");
        Toast.makeText(this, "inner method!", Toast.LENGTH_SHORT).show();
    

    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        Log.d(TAG,"开始绑定!onBind...");
        return new CommunicateBinder();
    

    // create的周期方法
    @Override
    public void onCreate() 
        Log.d(TAG,"服务创建!onCreate...");
        super.onCreate();
    

    // start的周期方法
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) 
        Log.d(TAG,"服务开启!onStart...");
        return super.onStartCommand(intent, flags, startId);
    

    // destroy的周期方法
    @Override
    public void onDestroy() 
        Log.d(TAG,"服务销毁!destroy...");
        super.onDestroy();
    

    @Override
    public boolean onUnbind(Intent intent) 
        Log.d(TAG, "开始解除绑定!onUnbind...");
        return super.onUnbind(intent);
    

然后是activity类:

  • 我们在类中写入了一个成员变量,是ServiceConnection类的对象conn
  • 在conn的重写的onServiceConnected()方法中,我们可以知道其中的第二个参数service其实就是我们刚刚在Service类中的return的new CommunicateBinder()的一个实例化对象,我们创建一个成员变量private FirstService.CommunicateBinder communicateBinder并给他赋值
  • 我们在绑定服务按钮触发时,启动了bindService(serviceIntent, conn,BIND_AUTO_CREATE);这个方法,用来绑定一个service,这时候就会去调用conn中的onServiceConnected()方法了
  • 我们在解除绑定服务按钮触发的时候,使用unbindService(conn)方法关闭service连接,并且将我们的communicateBinder对象置空
public class ServiceActivity extends Activity implements OnClickListener 

    private static final String TAG = "ServiceActivity";
    private Button stop;
    private Button start;
    private Button bind;
    private Button unbind;
    private Button method;
    private boolean isService = false;
    private FirstService.CommunicateBinder communicateBinder;
    private ServiceConnection conn = new ServiceConnection() 
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) 
            if (service instanceof FirstService.CommunicateBinder) 
                Log.d(TAG,name.toString()+"服务绑定!onServiceConnected...");
                communicateBinder = (FirstService.CommunicateBinder) service;
            
        

        @Override
        public void onServiceDisconnected(ComponentName name) 
            Log.d(TAG,"服务在不知情情况下解绑!onServiceDisconnected...");
            communicateBinder = null;
        
    ;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_service);
        initView();
    

    public void initView() 
        start = this.findViewById(R.id.bt_startService);
        stop = this.findViewById(R.id.bt_stopService);
        bind = this.findViewById(R.id.bt_bindService);
        unbind = this.findViewById(R.id.bt_unbindService);
        method = this.findViewById(R.id.bt_serviceMethod);
        start.setOnClickListener(this);
        stop.setOnClickListener(this);
        bind.setOnClickListener(this);
        unbind.setOnClickListener(this);
        method.setOnClickListener(this);
    


    @Override
    public void onClick(View v) 
        Intent serviceIntent = new Intent();
        serviceIntent.setClass(this, FirstService.class);
        if (v == start) 
            this.startService(serviceIntent);
         else if (v == stop) 
            this.stopService(serviceIntent);
         else if (v == bind) 
            isService = this.bindService(serviceIntent, conn,BIND_AUTO_CREATE);
         else if (v == unbind) 
            if (isService && conn != null) 
                isService = false;
                this.unbindService(conn);
                communicateBinder = null;
            
         else if (v == method) 
            if (communicateBinder != null) 
                communicateBinder.callInnerMethod();
            
        
    


效果如下:(注意看控制台的输出)

  • 我们点击绑定服务后,可以调用服务内部的方法
  • 解除绑定服务后,无法调用服务内部方法

3.2 用接口来减少暴露风险

我们会发现,在上面的代码中,绑定的一个服务内部类是public的,也就是说,我们可以在任何地方new一个内部类出来,这当然是非常不安全的。

如果我们把这个内部类改为一个private的类,让其更安全,那么我们如何调用其中的方法呢?

答案不难想到,就是使用接口

我们写一个接口类:

public interface ICommunication 
    void sayHello();

之后再让我们的内部类implements这个ICommunication接口,并让他成为private的内部类,这样我们进行重写sayHello()方法,用雷调用Service类中的内部方法就可以啦!

private class CommunicateBinder extends Binder implements ICommunication 
    @Override
    public void sayHello()
        innerMethod();
    

完整的代码如下:

Service类

public class FirstService extends Service 
    private static final String TAG = 安卓四大组件之服务服务的生命周期和启动方式

Android安卓四大组件之Activity

四大组件之Service生命周期

安卓基础之 四大基本组件介绍与生命周期

Android安卓进阶技巧之——Android Service 服务

Android四大组件之service