Android四大组件Service使用介绍

Android四大组件之Service

简述

  Service 是Android中的组件之一,与Activity、Broadcast Receiver、Content Provider共称为四大组件。从名字也可以看出Service主要是提供服务的,它运行在后台,甚至可以不依赖组件而单独存在。

生命周期

下面是官方文档中的生命周期图:
Service生命周期
  从上图可以看出Service有两种启动方式,不同的启动方式下其生命周期是不同的。比较可以看出两种启动方式下onCreate和onDestroy是一致的,不同之处是中间服务执行的部分。
  对于startService方式启动的服务,它执行的是onStartCommond,由于它启动后就与组件没有了关联,因此只涉及到这一个方法。这样的服务不手动关闭的话会一直运行在后台。对于onBind方式启动的服务,它与启动的组将通过IBinder进行互动,因此它的生命周期中执行的是onBind和onUnbind方法,对应着绑定与解绑的操作。

使用

  Service作为四大组件之一,使用服务的时候也需要在AndroidManifest中进行注册(四大组件都需要注册)。

<service android:description="string resource"
     android:directBootAware=["true" | "false"]
     android:enabled=["true" | "false"]
     android:exported=["true" | "false"]
     android:icon="drawable resource"
     android:isolatedProcess=["true" | "false"]
     android:label="string resource"
     android:name="string"
     android:permission="string"
     android:process="string" >
    ...
</service>

  description:对服务的描述信息,不是必要的。该属性必须使用引用方式,不能直接添加描述。
  derestBootAware:服务是否可以运行在用户未解锁前,默认false
  enabled:当前服务是否可被实例化,默认true。false下服务无法启动
  export:是否可以被其他应用程序启动,默认false。
  isolatedProcess:true则运行在其他进程中,与系统进程独立并且没有权限,仅能通过绑定方式与其他组件通信
  label:展示给用户的服务的名字
  name:对应的Service实现类的类名,必须声明
  permission:权限
  process:服务运行的进程名字,默认情况下所有的组件都是运行在主进程中,主进程名则是应用包名。设置时使用冒号(:)可以在进程前加上当前包名。例如包名为com.example.test,设置进程为 android:process=":new",则服务运行在com.example.test:new 进程。若是不加冒号,则运行在new进程。

创建服务对象

  创建服务只需要继承Service类,然后实现onBind方法即可,并且在AndroidManifest中声明即可。

public class MyService extends Service {
    private static final String TAG = "MyService"; 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate: Service创建");
        super.onCreate();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: Service启动");
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy: Service销毁");
        super.onDestroy();
    }
}

  这里创建了一个空的服务,并且onBind方法直接返回了一个null。这个方法在Service中是必须实现的,但是在非绑定方式启动的服务中,onBind方法是不会起作用的,因此可以直接返回null,但是在绑定方式启动的服务中,则必须要返回一个对应的IBinder实例了。

直接启动:

  直接启动是由startService进行启动,然后通过stopService停止,该方式下,生命周期为onCreate->onStartCommond->onDestroy。
  一个Service在系统中只会存在一个实例,因此当第一次调用startService的时候,服务会先调用onCreate再调用onStartCommond,之后再启动通一个Service则只会调用onStartCommond方法而不会重新创建onCreate。

public class MainActivity extends AppCompatActivity {

    private Button mButtonStart;
    private Button mButtonStop;
    private Intent mIntent;

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

        mButtonStart = findViewById(R.id.btn_start);
        mButtonStop = findViewById(R.id.btn_stop);
        mIntent = new Intent(this,MyService.class);

        mButtonStart.setOnClickListener(v->{
            startService(mIntent);
        });

        mButtonStop.setOnClickListener(v->{
            stopService(mIntent);
        });
    }
}

  多次启动,最后停止的打印日志如下:

/com.example.servicedemo I/MyService: onCreate: Service创建
/com.example.servicedemo I/MyService: onStartCommand: Service启动
/com.example.servicedemo I/MyService: onStartCommand: Service启动
/com.example.servicedemo I/MyService: onStartCommand: Service启动
/com.example.servicedemo I/MyService: onDestroy: Service销毁

  值得注意的是,采用startService方式启动的服务将一直运行在后台,而不会主动销毁。因此,当服务的操作结束后,要使用stopService(Intent)来将该服务进行关闭,或者在服务内部通过stopSelf()来进行关闭。

int onStartCommand(Intent intent, int flags, int startId)

该方法是startService方式启动的关键方法,主要是用于执行服务具体内容的方法。
参数:
  intent :这是启动服务时(startService)所携带的intent,可以用来传递数据。在系统杀死该服务并重启后可能为null。
  flags: 值为0或者START_FLAG_REDELIVERY或者 START_FLAG_RETRY
  startId:唯一ID,用于stopSelf
  返回值:
    START_STICKY:如果服务因为内存问题被系统杀死,则后面会重建该服务并调用onStartCommond,但这种情况下intent的值为null。
    START_STICKY_COMPATIBILITY:兼容START_STICKY,但是并不保证一定能够重建服务成功
    START_NOT_STICKY:如果服务因为内存问题被系统杀死,则不重建该服务,因此这种情况下不会接收到值为null的intent。
   START_REDELIVER_INTENT:如果服务因为内存问题被系统杀死,则重建服务并且传递最后一个intent,这种情况下intent也不为空。

Bind启动

  Bind启动方式是通过bindService进行绑定的,这种方式要求Service的onBind方法必须返回一个IBinder对象,这样在其他组件中就能够获得该对象,从而进行交互。该方式下生命周期为onCreate->onBind->onUnBind->onDestroy

public class MyService extends Service {

    private static final String TAG = "MyService";

    class MyBinder extends Binder {
        public MyService getService() {
            return MyService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: 绑定服务");
        return new MyBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "onUnbind: 解除绑定");
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate: Service创建");
        super.onCreate();
    }

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

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy: Service销毁");
        super.onDestroy();
    }
}

  重新修改了MyService,在其内部定义了一个内部类,该内部类是继承自Binder的,并且定义了一个方法用于获取Service。这样,在通过bindServce获取的IBinder后就可以进而获取Service对象,从而实现组件与服务通信。
  另外,绑定方式下启动的服务是依赖于启动组件的,当组件销毁的时候,若是并未显式解绑服务,则会自动调用onUnbind和onDestroy进行解绑。这与startService方式是有明显不同的,startService方式下启动的服务将一直运行在后台,即使启动它的组件已被销,所以一定要记住手动停止服务。

private MyService mService;

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

    mButtonBind = findViewById(R.id.btn_bind);
    mButtonUnBind = findViewById(R.id.btn_unbind);
    mIntent = new Intent(this, MyService.class);

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = ((MyService.MyBinder) service).getService();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    mButtonBind.setOnClickListener(v -> {
        bindService(mIntent, connection, BIND_AUTO_CREATE);
    });

    mButtonUnBind.setOnClickListener(v -> {
        unbindService(connection);
    });
}

  通过Bind方式启动Service还需要一个ServiceConnection对象,在连接到Service的时候,该对象的onServiceConnected方法就可以获得该Service中onBind返回的IBinder,从而进一步获得Service对象。

/com.example.servicedemo I/MyService: onCreate: Service创建
/com.example.servicedemo I/MyService: onBind: 绑定服务
/com.example.servicedemo I/MyService: onUnbind: 解除绑定
/com.example.servicedemo I/MyService: onDestroy: Service销毁
两种方式同时启动

  同一个服务是只存在一份实例的,也就是说只有第一次创建服务才会调用onCreate方法,后面多次启动只会调用onStartCommond或者onBind方法。并且,当同一个服务既被startService方式启动又被bindService启动的时候,只有同时调用停止和解绑两个方法(stopService和unBindService的顺序无关),服务才会被销毁。
  注意:服务可以同时由多个组件多次以startService方式启动,并且由其他组件进行关闭;但是绑定方式(bindService)同时只能有一个组件进行绑定,并且只有绑定服务的组件才能进行解绑。

前台服务

  前台服务是借助Notification来在通知栏显示的服务,这种方式下的服务的优先级较高,当系统内部不足时会销毁后台服务而几乎不会销毁前台服务。
  使用前台服务可以与用户进行交互,例如下载文件、后台播放音乐等,都可以使用前台服务。前台服务是在Service中调用startForeground方式,可以在onStartCommand也可以在onBind中调用,代表着两种启动方式都可以成为前台服务。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.i(TAG, "onStartCommand: 启动服务");
    Intent notificationIntent = new Intent(this, MainActivity.class);
    PendingIntent pendingIntent =
            PendingIntent.getActivity(this, 0, notificationIntent, 0);

    NotificationCompat.Builder builder;
    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    // android 8.0以上需要配置Channel
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        String channelId = "notification_channel_id";
        NotificationChannel channel = new NotificationChannel(channelId, "channel_name", NotificationManager.IMPORTANCE_HIGH);
        // 设置默认声音
        channel.enableLights(true);
        channel.enableVibration(true);
        manager.createNotificationChannel(channel);
        builder = new NotificationCompat.Builder(getBaseContext(), channelId);
    } else {
        builder = new NotificationCompat.Builder(getBaseContext(),null);
    }

        builder.setContentTitle("服务标题")
            .setContentText("服务内容")
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentIntent(pendingIntent)
            .setWhen(System.currentTimeMillis());
    startForeground(1, builder.build());
    return super.onStartCommand(intent, flags, startId);
}
IntentService

  在服务中,所有的操作都是运行在主线程中的。因此,在服务中若是有耗时的操作的时候,都必须创建子线程,然后在子线程中进行执行,以避免ANR错误。
  而Service的子类IntentService,是系统为我们封装好的一个服务,就是为了解决耗时操作的情况的。其内部创建了一个线程,通过Handler来处理请求。使用它只需要重写onHandleIntent方法即可,另外构造方法接收一个String参数,这个参数是用来命名当前服务所运行的线程的线程名的。

public class MyIntentService extends IntentService {
    private static final String TAG = "MyService";
    public MyIntentService() {
        super("intent");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.i(TAG, "onHandleIntent: 处理服务   "+Thread.currentThread().getName());
    }
}

  使用IntentService不用担心关闭的问题,因为IntentService每次被启动的时候,都会将onHandleIntent中的操作封装后分发到消息队列中依次执行,当所有的任务都执行完毕的时候就会销毁服务。由于这种服务是依次执行任务的,是串行的,不适用于大量的并发任务。
  优点是使用简单,另外,若是要重写onCreate、onStartCommond、onDestroy,则必须调用其父类实现,以保证IntentService的正常运行。

总结

  Serivice分为前台服务和后台服务。后台服务是运行在后台并且没有界面的,用户是无法感知的;前台服务通过Notification方式,可以在通知栏显示,用户也可以与之交互,它是在Service的内部通过startForeground方法实现的。
  Service的启动方式分为直接启动和绑定启动。直接启动方式下的服务将不依赖启动组件,并且启动后可以一直运行在后台,直到手动停止。停止的方式有两种,组件通过stopService停止服务或者服务内部调用stopSelf进行停止。绑定方式启动的服务依赖于启动组件,当组件销毁的时候,服务也会自动销毁,即使没有调用解绑方法(unBindService)。
  直接启动的方法可以启动多次,但只有第一次启动的时候执行onCreate方法,后面只会执行onStartCommond,并且可以由任意组件停止服务。绑定启动的服务只能被一个组件绑定,其他组件无法绑定无法解绑,只能由绑定的组件进行解绑。
  Service是运行在主线程的,是无法进行耗时操作的。因此若是执行耗时操作需要在服务中开启子线程进行执行任务,或者使用IntentService。IntentService将操作都放到子线程中进行执行,内部采用Handler方式。因此任务的执行是串行的,不适用于大量并发任务。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值