Android体系复习--Service

一、Service简介

四大组件之一,是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。
一般分为:前台服务、后台服务、绑定服务

  • 前台服务:前台服务执行一些用户能注意到的操作。例如,音频应用会使用前台服务来播放音频曲目。前台服务必须显示通知即使用户停止与应用的交互,前台服务仍会继续运行

  • 后台服务:后台服务执行用户不会直接注意到的操作。例如,如果应用使用某个服务来压缩其存储空间,则此服务通常是后台服务

  • 绑定服务:当应用组件通过调用 bindService()绑定到服务时,服务即处于绑定状态。绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接收结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

二、生命周期

服务启动方式有两种:启动服务和绑定服务,下图是两种启动方式的生命周期


service_lifecycle.png

onStartCommand:

当另一个组件(如 Activity)请求启动服务时,系统会通过调用 startService() 来调用此方法。执行此方法时,服务即会启动并可在后台无限期运行。如果您实现此方法,则在服务工作完成后,您需负责通过调用 stopSelf() 或 stopService() 来停止服务。(如果您只想提供绑定,则无需实现此方法。)

onBind:

当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用 bindService() 来调用此方法。在此方法的实现中,您必须通过返回 IBinder 提供一个接口,以供客户端用来与服务进行通信。请务必实现此方法;但是,如果您并不希望允许绑定,则应返回 null。

onCreate:

首次创建服务时,系统会(在调用 onStartCommand() 或 onBind() 之前)调用此方法来执行一次性设置程序。如果服务已在运行,则不会调用此方法。

onDestroy:

当不再使用服务且准备将其销毁时,系统会调用此方法。服务应通过实现此方法来清理任何资源,如线程、注册的侦听器、接收器等。这是服务接收的最后一个调用。

三、onStartCommand的返回值介绍

START_STICKY:

在Service启动后,如果该Service被系统杀死了,系统会重新创建该Service,onStartCommand方法会被调用,但是传给Service的intent为null

START_NOT_STICKY:

在Service启动后,如果该Service被系统杀死了,系统不会重新创建该Service。除非开发者明确地使用startService的方式

START_REDELIVER_INTENT:

在Service启动后,如果该Service被系统杀死了,系统会重新创建该Service,onStartCommand方法会调用,并且将上次的Intent通过onStartCommand方法传递进来。除非开发者明确使用stopSelf方法停止该Service,否则其不会停止。

START_STICKY_COMPATIBILITY

其功能和START_STICKY一样,是START_STICKY的兼容版本,但是不保证onStartCommand方法会被调用。

四、开启服务的两种方式

1. startService

startService 第一次启动时会触发OnCreate--> onStartCommond方法, 如果Service已经存在,再次启动时则只处罚OnStartCommond方法,且第三个参数startId + 1,停止任务时,我们可以直接stopSelf,也可以通过stopSelf(startId)来停止服务
stopSelf(startId) 这种方式停止时,如果startService了多次,需要将所有startId关闭后才会触发OnDestroy回调


class LearnService : Service() {
    override fun onBind(intent: Intent?): IBinder? {
        "onBind".log()
        return null
    }

    override fun onCreate() {
        super.onCreate()
        "onCreate".log()
    }

    override fun onDestroy() {
        super.onDestroy()
        "onDestroy".log()
    }


    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        "onStartCommand".log()
        val mThread = object : Thread() {
            override fun run() {
                super.run()
                var count: Int = 0
                while (!isInterrupted) {
                    "任务执行中:$startId   执行次数:${count++}".log()
                    if (count > 5) {
                        interrupt()
                        stopSelf(startId)
                    }
                    SystemClock.sleep(1000)
                }
            }
        }
        mThread.start()
        return super.onStartCommand(intent, flags, startId)
    }
}

间隔两秒后开启两次 打印结果如下:

2020-11-12 15:48:50.839 3337-3337/com.czy.systemlearn D/YYYYYY: onCreate
2020-11-12 15:48:50.841 3337-3337/com.czy.systemlearn D/YYYYYY: onStartCommand
2020-11-12 15:48:50.842 3337-4173/com.czy.systemlearn D/YYYYYY: 任务执行中:1   执行次数:0
2020-11-12 15:48:51.848 3337-4173/com.czy.systemlearn D/YYYYYY: 任务执行中:1   执行次数:1
2020-11-12 15:48:52.856 3337-4173/com.czy.systemlearn D/YYYYYY: 任务执行中:1   执行次数:2
2020-11-12 15:48:53.005 3337-3337/com.czy.systemlearn D/YYYYYY: onStartCommand
2020-11-12 15:48:53.008 3337-4189/com.czy.systemlearn D/YYYYYY: 任务执行中:2   执行次数:0
2020-11-12 15:48:53.864 3337-4173/com.czy.systemlearn D/YYYYYY: 任务执行中:1   执行次数:3
2020-11-12 15:48:54.016 3337-4189/com.czy.systemlearn D/YYYYYY: 任务执行中:2   执行次数:1
2020-11-12 15:48:54.870 3337-4173/com.czy.systemlearn D/YYYYYY: 任务执行中:1   执行次数:4
2020-11-12 15:48:55.024 3337-4189/com.czy.systemlearn D/YYYYYY: 任务执行中:2   执行次数:2
2020-11-12 15:48:55.876 3337-4173/com.czy.systemlearn D/YYYYYY: 任务执行中:1   执行次数:5
2020-11-12 15:48:56.033 3337-4189/com.czy.systemlearn D/YYYYYY: 任务执行中:2   执行次数:3
2020-11-12 15:48:57.040 3337-4189/com.czy.systemlearn D/YYYYYY: 任务执行中:2   执行次数:4
2020-11-12 15:48:58.048 3337-4189/com.czy.systemlearn D/YYYYYY: 任务执行中:2   执行次数:5
2020-11-12 15:48:58.059 3337-3337/com.czy.systemlearn D/YYYYYY: onDestroy

2. bindService

同一个 Service 可以被多个组件绑定,只有所有绑定它的组件都进行了 unBind 操作,这个 Service 才会被销毁。
绑定服务 bindService, 其生命周期是 onCreate -> onBind ,绑定成功后再次调用bindService不会触发生命周期。启动方法参数如下:

 bindService(Intent service, ServiceConnection conn,int flags)
  • Intent : 要开启的服务
  • ServiceConnection:一个接口,包含两个回调onServiceConnected(绑定成功后触发)和onServiceDisconnected(服务端意外挂掉触发)
  • flags:一般选用BIND_AUTO_CREATE 当bindService时,该服务如果不存在则自动创建该服务

ServiceConnection的onServiceConnected会返回一个IBinder对象, Service 也有一个onBind方法,返回一个IBinder对象,他们是同一个对象,我们可以通过该对象来实现 Activity 调用Service方法通信
代码示例


class LearnService : Service() {
    override fun onBind(intent: Intent?): IBinder? {
        "onBind".log()
        return MyBind()
    }

    override fun onUnbind(intent: Intent?): Boolean {
        "onUnbind".log()
        return super.onUnbind(intent)
    }
    
    override fun onCreate() {
        super.onCreate()
        "onCreate".log()
    }

    override fun onDestroy() {
        super.onDestroy()
        "onDestroy".log()
    }


    // 暴露给 Activity调用的方法
    fun serviceInnerMethods(msg: String) {
        msg.log()
    }

    open inner class MyBind : Binder() {
        val service: LearnService
            get() = this@LearnService
    }
}

调用方法

class MainActivity : AppCompatActivity() {
    lateinit var connection: ServerConnect
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        connection = ServerConnect()
  
        btnBindService.setOnClickListener {
            //绑定服务
            bindService(Intent(MainActivity@ this, LearnService::class.java), connection, BIND_AUTO_CREATE)
        }
        btnUnBindService.setOnClickListener {
            //解绑服务
            unbindService(connection)
        }
    }

    class ServerConnect : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
          // 拿到Service对象 调用其内部方法
            val myBind = service as LearnService.MyBind
            myBind.service.serviceInnerMethods("Activity 已经和你建立了连接")

        }

        override fun onServiceDisconnected(name: ComponentName?) {
            "连接断开".log()
        }
    }
}

最终结果

2020-11-12 17:31:36.607 13974-13974/com.czy.systemlearn D/YYYYYY: 点击绑定服务 ==== 
2020-11-12 17:31:36.623 13974-13974/com.czy.systemlearn D/YYYYYY: onCreate
2020-11-12 17:31:36.624 13974-13974/com.czy.systemlearn D/YYYYYY: onBind
2020-11-12 17:31:36.628 13974-13974/com.czy.systemlearn D/YYYYYY: Activity 已经和你建立了连接
2020-11-12 17:31:39.467 13974-13974/com.czy.systemlearn D/YYYYYY: 点击解绑服务 ==== 
2020-11-12 17:31:39.483 13974-13974/com.czy.systemlearn D/YYYYYY: onUnbind
2020-11-12 17:31:39.486 13974-13974/com.czy.systemlearn D/YYYYYY: onDestroy

上面只是Activity 向Service调用,如果Service获取到最终结果后如何将结果传递回Activity,除了广播外,我们可以在Service设置一个回调监听即可,


    lateinit var dataCallback: DataCallback

    //设置数据回调监听
    fun setDataCallbackListener(dataCallback: DataCallback) {
        this.dataCallback = dataCallback
    }
    
    interface DataCallback {
        fun callBack(result: String)
    }

 // 在合适的时机触发即可
if(this::dataCallback.isInitialized){
            dataCallback.callBack("Service 将结果传递给Activity")
        }

3. 先StartService 在BindService的生命周期

  1. 先startService : onCreate --> onStartCommand
    在BindService: --> onBinde
    然后unBindService: -->onUnbind
    最后stopService: -->onDestroy

  2. 先绑定bindService: onCreate --> onBinde
    在startService: --> onStartCommand
    然后stopService: 不触发生命周期
    最后UNBindService: --> unbind-->onDestroy

其他场景类似,只有stopService 跟unbindservice都执行了,才会走onDestroy生命周期

注意:混合使用时,如果我们绑定服务时flags 用的BIND_AUTO_CREATE,binderService后,在unbindService,如果之前此时Service还在运行,我们再次bindService是不会触发onBinde生命周期回调的

五、IntentService

IntentService是继承于Service的子类,它可以处理异步处理请求,Client使用startService(Intent)来启动Service。在它内部有一个工作线程来处理耗时操作,当它处理完所有请求的时候会停止它自身,不需要我们手动控制。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService 的 onHandleIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。
而且,所有请求都在一个单线程中,不会阻塞应用程序的主线程(UI Thread),同一时间只处理一个请求。

总结下相比Service的优势和特点:

  • 内部有一个工作线程,来处理耗时操作
  • 工作线程已队列形式运行,一个执行完在执行另一个(串行执行。)
  • 不需要开发者手动停止服务

使用场景和示例
IntentService 可多次启动,工作线程内部会顺序执行。另外 IntentService 因为是服务的原因,相比普通线程具有更高的优先级,适用更高优先级的的后台任务,不容易被系统杀死。可以用作后台下载任务。


class MyIntentService() : IntentService("name") {
    override fun onCreate() {
        super.onCreate()
        "onCreate".log()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        "onStartCommand".log()
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {
        "onDestroy".log()
        super.onDestroy()
    }

    override fun onHandleIntent(intent: Intent?) {
        val name = intent?.getStringExtra("name")
        "当前线程: ${Thread.currentThread().name}".log()
        SystemClock.sleep(2000)
        "$name 任务执行完毕".log()
    }
}

使用

 var index = 0
 btnStart.setOnClickListener {
     val intent = Intent(MainActivity@ this, MyIntentService::class.java)
     intent.putExtra("name", index++.toString())
     startService(intent)
}

结果

2020-11-16 17:31:52.588 8466-8466/com.czy.systemlearn D/YYYYYY: onCreate
2020-11-16 17:31:52.589 8466-8466/com.czy.systemlearn D/YYYYYY: onStartCommand
2020-11-16 17:31:52.590 8466-8626/com.czy.systemlearn D/YYYYYY: 当前线程: IntentService[name]
2020-11-16 17:31:53.077 8466-8466/com.czy.systemlearn D/YYYYYY: onStartCommand
2020-11-16 17:31:53.560 8466-8466/com.czy.systemlearn D/YYYYYY: onStartCommand
2020-11-16 17:31:54.594 8466-8626/com.czy.systemlearn D/YYYYYY: 0 任务执行完毕
2020-11-16 17:31:54.597 8466-8626/com.czy.systemlearn D/YYYYYY: 当前线程: IntentService[name]
2020-11-16 17:31:56.600 8466-8626/com.czy.systemlearn D/YYYYYY: 1 任务执行完毕
2020-11-16 17:31:56.603 8466-8626/com.czy.systemlearn D/YYYYYY: 当前线程: IntentService[name]
2020-11-16 17:31:58.606 8466-8626/com.czy.systemlearn D/YYYYYY: 2 任务执行完毕
2020-11-16 17:31:58.613 8466-8466/com.czy.systemlearn D/YYYYYY: onDestroy

源码分析

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;//子线程中创建的Looper对象
    private volatile ServiceHandler mServiceHandler;// 跟子线程中的Looper关联的Hander
    private String mName;//线程名
    private boolean mRedelivery;//决定onStartCommand的返回值(服务是否重启)

    // 处理hander的消息,并通过startId来停止服务
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            //如果服务中所有的startid都被停止了,才会触发onDestroy
            stopSelf(msg.arg1);
        }
    }

    public IntentService(String name) {
        super();
        //name 是线程名称,主要是在调试时方便测试
        mName = name;
    }

  
  //enabled: 如果为true: onStartCommand返回START_REDELIVER_INTENT,否则:START_NOT_STICKY
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //创建一个子线程 里边运行着Looper
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        // 作用:创建Looper 并启动looper循环
        thread.start();
        
        mServiceLooper = thread.getLooper();
        //创建子线程Hander;将hander和HanderThread线程关联起来
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    //在服务的onStartCommand方法被调用
    //作用:通过子线程hander进行消息分发
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }


    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }
    //真正处理需要在子线程运行的业务
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

intentService源码比较简单,流程如下:

  1. 在onCreate的时候创建两个对象:一个 handerThread(一个拥有Looper的线程),另一个:ServiceHandler 对象
    (与子线程looper绑定的Hander),
  2. 在onSTartCommand的时候进行mServiceHandler.sendMessag消息分发
  3. 分发的消息在onHandleIntent((Intent)msg.obj); 处理,处理完后通过StopSelf(startId)停止服务
  4. 因为hander机制 决定了任务是需要队列运行的。

六、android 8.0后台限制

限制
Android 8.0对系统进行了优化,为节省资源不允许后台应用创建后台服务,
处于前台时,应用可以自由创建和运行前台与后台 Service。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用 Service。 在该时间窗结束后,应用将被视为处于空闲状态。 此时,系统将停止应用的后台 Service,就像应用已经调用 Service 的 Service.stopSelf() 方法一样。

我们看下在8.0之上的手机在后台开启服务的运行情况;

Handler().postDelayed({
    startService(Intent(MainActivity@ this, LearnService::class.java))
},10000)

在后台十秒后自动开启服务,生命周期打印日志如下:

2020-11-17 09:55:29.841 32453-32453/com.czy.systemlearn D/YYYYYY: onCreate
2020-11-17 09:55:29.845 32453-32453/com.czy.systemlearn D/YYYYYY: onStartCommand
2020-11-17 09:56:21.499 32453-32453/com.czy.systemlearn D/YYYYYY: onDestroy

可以看出:在55分时候创建服务后,在56分时就自动执行了onDestroy

解决
在 Android 8.0 之前,创建前台 Service 的方式通常是先创建一个后台 Service,然后将该 Service 推到前台。 Android 8.0 有一项复杂功能:系统不允许后台应用创建后台 Service。 因此,Android 8.0 引入了一种全新的方法,即 startForegroundService(),以在前台启动新 Service。 在系统创建 Service 后,应用有五秒的时间来调用该 Service 的 startForeground() 方法以显示新 Service 的用户可见通知。 如果应用在此时间限制内调用 startForeground(),则系统将停止此 Service 并声明此应用为 ANR

调用:

 startForegroundService(Intent(MainActivity@ this, LearnService::class.java))

因为需要在五秒内调用startForeground,我们更改下服务的onCreate代码,添加该方法
服务代码:


class LearnService : Service() {
    override fun onBind(intent: Intent?): IBinder? {
        "onBind".log()
        return MyBind()
    }

    override fun onUnbind(intent: Intent?): Boolean {
        "onUnbind".log()
        return super.onUnbind(intent)
    }

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreate() {
        super.onCreate()
        "onCreate".log()
        val channel = NotificationChannel(
            100.toString(),
            "background",
            NotificationManager.IMPORTANCE_MIN
        )
        channel.enableVibration(false)
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        manager.createNotificationChannel(channel)

        val builder: Notification.Builder = Notification.Builder(
            applicationContext, 100.toString()
        ).setContentTitle("正在后台运行")
            .setSmallIcon(R.mipmap.sym_def_app_icon)
        startForeground(1, builder.build())

    }

    override fun onDestroy() {
        super.onDestroy()
        "onDestroy".log()
    }

    // 暴露给 Activity调用的方法
    fun serviceInnerMethods(msg: String) {
        msg.log()
    }

    open inner class MyBind : Binder() {
        val service: LearnService
            get() = this@LearnService
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        "onStartCommand".log()
        return super.onStartCommand(intent, flags, startId)
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,104评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,816评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,697评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,836评论 1 298
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,851评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,441评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,992评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,899评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,457评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,529评论 3 341
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,664评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,346评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,025评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,511评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,611评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,081评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,675评论 2 359

推荐阅读更多精彩内容

  • 【Android Service】 Service 简介(★★★) 很多情况下,一些与用户很少需要产生交互的应用程...
    Rtia阅读 3,160评论 1 21
  • 一、Service简介 Service是Android程序中四大基础组件之一,它和Activity一样都是Cont...
    小池laucherish阅读 695评论 0 0
  • 前言:本文所写的是博主的个人见解,如有错误或者不恰当之处,欢迎私信博主,加以改正!原文链接,demo链接 Serv...
    PassersHowe阅读 1,421评论 0 5
  • 1.Service 是什么? Service 是 Android 中实现程序后台运行的解决方案,非常适用于去执行那...
    sssssss_阅读 805评论 0 0
  • 1.Service简介 服务是一个应用程序组件,可以在后台执行长时间运行的操作,不提供用户界面。一个应用程序组件可...
    紫豪阅读 21,962评论 11 83