Service详解_绑定服务实现

本篇文章主要讲解Service绑定服务的实现方式以及三种绑定方法。

1. Service绑定服务

绑定服务是Service的另一种使用方式,当Service处于绑定状态时,其代表着客户端-服务器接口中的服务器。当其他组件(如 Activity)绑定到服务时(有时我们可能需要从Activity组建中去调用Service中的方法,此时Activity以绑定的方式开启Service后,我们就可以轻松地方法到Service中的指定方法),组件(如Activity)可以向Service(也就是服务端)发送请求,或者调用Service(服务端)的方法,此时被绑定的Service(服务端)会接收信息并响应,甚至可以通过绑定服务进行执行进程间通信 (即IPC)。

与启动服务不同的是绑定服务的生命周期通常只在为其他应用组件(如Activity)服务时处于活动状态,不会无限期在后台运行,也就是说宿主(如Activity)解除绑定后,绑定服务就会被销毁。那么在提供绑定的服务时,该如何实现呢?实际上我们必须提供一个 IBinder接口的实现类,该类用以提供客户端用来与服务进行交互的编程接口,该接口可以通过三种方法定义接口:

  • 扩展 Binder 类
  • 使用 Messenger
  • 使用 AIDL

下面👇,我们将对这三种方式进行详细的介绍:

1.1 扩展Binder类

如果服务是提供给自有应用专用的,并且Service(服务端)与客户端相同的进程中运行(常见情况),则应通过扩展 Binder 类并从 onBind() 返回它的一个实例来创建接口。客户端收到 Binder 后,可利用它直接访问 Binder 实现中以及Service 中可用的公共方法。如果我们的服务只是自有应用的后台工作线程,则优先采用这种方法。 不采用该方式创建接口的唯一原因是,服务被其他应用或不同的进程调用。

其使用开发步骤如下

  1. 创建BindService服务端,继承自Service并在类中,创建一个实现IBinder 接口的实例对象并提供公共方法给客户端调用
  2. 从 onBind() 回调方法返回此 Binder 实例。
  3. 在客户端中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定服务

注意:此方式只有在客户端和服务位于同一应用和进程内才有效,如对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方式非常有效。

以下是一个扩展 Binder 类的实例,先看看Service端的实现:

class LocalService : Service() {

    companion object {
        const val TAG = "LocalService"
    }

    private lateinit var thread: Thread
    private var quit = false
    private var count = 0

    override fun onBind(intent: Intent?): IBinder? = LocalBinder()

    inner class LocalBinder : Binder() {
        // 声明一个方法,getService。(提供给客户端调用)
        // 返回当前对象LocalService,这样我们就可在客户端端调用Service的公共方法了
        val service: LocalService = this@LocalService
    }

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "Service is invoke Created")
        thread = Thread(Runnable {
            while (!quit) {
                try {
                    Thread.sleep(1000)
                } catch (e: Exception) {
                    e.printStackTrace()
                }
                count++
            }
        })
        thread.start()
    }

    fun getCount(): Int = count

    override fun onUnbind(intent: Intent?): Boolean {
        Log.i(TAG, "Service is invoke onUnbind")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        this.quit = true
        Log.i(TAG, "Service is invoke onDestroy")
    }
}

LocalService类继承自Service,在该类中创建了一个LocalBinder继承自Binder类,LocalBinder中声明了一个getService方法,客户端可访问该方法获取LocalService对象的实例,只要客户端获取到LocalService对象的实例就可调用LocalService服务端的公共方法,如getCount方法,值得注意的是,我们在onBind方法中返回了binder对象,该对象便是LocalBinder的具体实例,而binder对象最终会返回给客户端,客户端通过返回的binder对象便可以与服务端实现交互。接着看看客户端BindActivity的实现:

class BindActivity : AppCompatActivity() {

    private lateinit var conn: ServiceConnection
    private var localService: LocalService? = null

    companion object {
        const val TAG = "BindActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_bind)

        conn = object : ServiceConnection {
            override fun onServiceDisconnected(name: ComponentName?) {
                localService = null
            }

            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                Log.d(TAG, "绑定成功调用:onServiceConnected")
                localService = (service as LocalService.LocalBinder).service
            }

        }

        val intent = Intent(this, LocalService::class.java)

        bindServiceBtn.setOnClickListener {
            Log.d(TAG, "绑定调用:bindService")
            bindService(intent, conn, Context.BIND_AUTO_CREATE)
        }

        unBindServiceBtn.setOnClickListener {
            Log.d(TAG, "解除绑定调用:unbindService")
            localService?.let {
                localService = null
                unbindService(conn)
            }
        }

        getCountBtn.setOnClickListener {
            Log.i(TAG, localService?.getCount().toString())
        }
    }
}

在客户端中我们创建了一个ServiceConnection对象,该代表与服务的连接,它只有两个方法, onServiceConnected和onServiceDisconnected,其含义如下:

  • onServiceConnected(ComponentName name, IBinder service)
    系统会调用该方法以传递服务的 onBind() 方法返回的 IBinder。其中service便是服务端返回的IBinder实现类对象,通过该对象我们便可以调用获取LocalService实例对象,进而调用服务端的公共方法。而ComponentName是一个封装了组件(Activity, Service, BroadcastReceiver, or ContentProvider)信息的类,如包名,组件描述等信息,较少使用该参数。
  • onServiceDisconnected(ComponentName name)
    Android 系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。

注意:当客户端取消绑定时,系统“绝对不会”调用该方法。

在onServiceConnected()被回调前,我们还需先把当前Activity绑定到服务LocalService上,绑定服务是通过通过bindService()方法,解绑服务则使用unbindService()方法,这两个方法解析如下:

  • bindService(Intent service, ServiceConnection conn, int flags)
    该方法执行绑定服务操作,其中Intent是我们要绑定的服务(也就是LocalService)的意图,而ServiceConnection代表与服务的连接,它只有两个方法,前面已分析过,flags则是指定绑定时是否自动创建Service。0代表不自动创建、BIND_AUTO_CREATE则代表自动创建。
  • unbindService(ServiceConnection conn)
    该方法执行解除绑定的操作。

Activity通过bindService()绑定到LocalService后,ServiceConnection#onServiceConnected()便会被回调并可以获取到LocalService实例对象mService,之后我们就可以调用LocalService服务端的公共方法了,最后还需要在清单文件中声明该Service。

运行程序,点击绑定服务并多次点击绑定服务接着多次调用LocalService中的getCount()获取数据,最后调用解除绑定的方法移除服务,其结果如下:

01-08 15:30:18.059 24842-24842/com.wangyy.service I/LocalService: Service is invoke onCreate
01-08 15:30:18.059 24842-24842/com.wangyy.service I/LocalService: Service is invoke onBind
01-08 15:30:18.060 24842-24842/com.wangyy.service I/BindActivity: 绑定成功调用:onServiceConnected
01-08 15:30:21.123 24842-24842/com.wangyy.service I/BindActivity: 3
01-08 15:30:22.384 24842-24842/com.wangyy.service I/BindActivity: 4
01-08 15:30:25.607 24842-24842/com.wangyy.service I/BindActivity: 7
01-08 15:30:28.159 24842-24842/com.wangyy.service I/LocalService: Service is invoke onUnbind
01-08 15:30:28.160 24842-24842/com.wangyy.service I/LocalService: Service is invoke onDestroy

通过Log可知,当我们第一次点击绑定服务时,LocalService服务端的onCreate()、onBind方法会依次被调用,此时客户端的ServiceConnection#onServiceConnected()被调用并返回LocalBinder对象,接着调用LocalBinder#getService方法返回LocalService实例对象,此时客户端便持有了LocalService的实例对象,也就可以任意调用LocalService类中的声明公共方法了。

值得注意的是,我们多次调用bindService方法绑定LocalService服务端,而LocalService得onBind方法只调用了一次,那就是在第一次调用bindService时才会回调onBind方法。接着我们点击获取服务端的数据,从Log中看出我们点击了3次通过getCount()获取了服务端的3个不同数据,最后点击解除绑定,此时LocalService的onUnBind、onDestroy方法依次被回调,并且多次绑定只需一次解绑即可。此情景也就说明了绑定状态下的Service生命周期方法的调用依次为onCreate()、onBind、onUnBind、onDestroy。ok~,以上便是同一应用同一进程中客户端与服务端的绑定回调方式。

1.2 使用 Messenger

Messenger可以翻译为信使,通过它可以在不同的进程中共传递Message对象(Handler中的Messager,因此 Handler 是 Messenger 的基础),在Message中可以存放我们需要传递的数据,然后在进程间传递。如果需要让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口,客户端就可利用 Message 对象向服务发送命令。同时客户端也可定义自有 Messenger,以便服务回传消息。这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,也就是说Messenger是以串行的方式处理客户端发来的消息,这样我们就不必对服务进行线程安全设计了。

Messenger 使用的主要步骤:

  1. 服务实现一个 Handler,由其接收来自客户端的每个调用的回调
  2. Handler 用于创建 Messenger 对象(对 Handler 的引用)
  3. Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端
  4. 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用Messenger将 Message 对象发送给服务
  5. 服务在其 Handler 中(在 handleMessage() 方法中)接收每个 Message

以下是一个使用 Messenger 接口的简单服务示例,服务端进程实现如下:

class MessengerService : Service() {

    companion object {
        const val MSG_SAY_HELLO = 1
        const val TAG = "MessengerService"
    }

    /**
     * 创建Messenger并传入Handler实例对象
     */
    private val messenger: Messenger = Messenger(IncomingHandler())

    /**
     * 用于接收从客户端传递过来的数据
     */
    class IncomingHandler : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO -> {
                    Log.i(TAG, "thanks,Service had receiver message from client!")
                }
                else -> {
                    super.handleMessage(msg)
                }
            }
        }
    }

    /**
     * 当绑定Service时,该方法被调用,将通过mMessenger返回一个实现
     * IBinder接口的实例对象
     */
    override fun onBind(intent: Intent?): IBinder? {
        Log.i(TAG, "Service is invoke onBind")
        return messenger.binder
    }

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "Service is invoke onCreate")
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.i(TAG, "Service is invoke onUnbind")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i(TAG, "Service is invoke onDestroy")
    }
}

首先我们同样需要创建一个服务类MessengerService继承自Service,同时创建一个继承自Handler的IncomingHandler对象来接收客户端进程发送过来的消息并通过其handleMessage(Message msg)进行消息处理。接着通过IncomingHandler对象创建一个Messenger对象,该对象是与客户端交互的特殊对象,然后在Service的onBind中返回这个Messenger对象的底层Binder即可。下面看看客户端进程的实现:

class MessengerActivity : AppCompatActivity() {

    private lateinit var conn: ServiceConnection
    private var mService: Messenger? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_messenger)

        conn = object : ServiceConnection {
            override fun onServiceDisconnected(name: ComponentName?) {
                mService = null
            }

            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                mService = Messenger(service)
            }

        }

        val intent = Intent(this, MessengerService::class.java)

        bindServiceBtn.setOnClickListener {
            bindService(intent, conn, Context.BIND_AUTO_CREATE)
        }

        unBindServiceBtn.setOnClickListener {
            mService?.let {
                mService = null
                unbindService(conn)
            }
        }

        sendMsgBtn.setOnClickListener {
            sayHello()
        }
    }

    private fun sayHello() {
        mService?.let { messenger ->
            val msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0)
            try {
                messenger.send(msg)
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
    }
}

在客户端进程中,我们需要创建一个ServiceConnection对象,该对象代表与服务端的链接,当调用bindService方法将当前Activity绑定到MessengerService时,onServiceConnected方法被调用,利用服务端传递给来的底层Binder对象构造出与服务端交互的Messenger对象,接着创建与服务交互的消息实体Message,将要发生的信息封装在Message中并通过Messenger实例对象发送给服务端。关于ServiceConnection、bindService方法、unbindService方法,前面已分析过,这里就不重复了,最后我们需要在清单文件声明Service和Activity,由于要测试不同进程的交互,则需要将Service放在单独的进程中,因此Service声明如下:

<service
    android:name=".MessengerService"
    android:process=":remote" />

其中android:process=":remote"代表该Service在单独的进程中创建

接着多次点击绑定服务,然后发送信息给服务端,最后解除绑定,Log打印如下:

01-08 18:04:13.753 26092-26092/com.wangyy.service:remote I/MessengerService: Service is invoke onCreate
01-08 18:04:13.754 26092-26092/com.wangyy.service:remote I/MessengerService: Service is invoke onBind
01-08 18:04:21.214 26092-26092/com.wangyy.service:remote I/MessengerService: thanks,Service had receiver message from client!
01-08 18:04:21.429 26092-26092/com.wangyy.service:remote I/MessengerService: thanks,Service had receiver message from client!
01-08 18:04:23.069 26092-26092/com.wangyy.service:remote I/MessengerService: Service is invoke onUnbind
01-08 18:04:23.071 26092-26092/com.wangyy.service:remote I/MessengerService: Service is invoke onDestroy

通过上述例子可知Service服务端确实收到了客户端发送的信息,而且在Messenger中进行数据传递必须将数据封装到Message中,因为Message和Messenger都实现了Parcelable接口,可以轻松跨进程传递数据。

以上的例子演示了如何在服务端解释客户端发送的消息,但有时候我们可能还需要服务端能回应客户端,这时便需要提供双向消息传递了,下面就来实现一个简单服务端与客户端双向消息传递的简单例子。

先来看看服务端的修改,在服务端,我们只需修改IncomingHandler,收到消息后,给客户端回复一条信息。

class IncomingHandler : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO -> {
                    Log.i(TAG, "thanks,Service had receiver message from client!")
                    //回复客户端信息,该对象由客户端传递过来
                    val client = msg.replyTo
                    //获取回复信息的消息实体
                    val replyMsg = Message.obtain(null, MSG_SAY_HELLO)
                    val bundle = Bundle()
                    bundle.putString("reply", "ok~,I had receiver message from you! ")
                    replyMsg.data = bundle
                    try {
                        client.send(replyMsg)
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }

                }
                else -> {
                    super.handleMessage(msg)
                }
            }
        }
    }

接着修改客户端,为了接收服务端的回复,客户端也需要一个接收消息的Messenger和Handler,其实现如下:

 private val mReceiverReplyMsg = Messenger(ReceiverReplyMsgHandler())

    class ReceiverReplyMsgHandler : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MessengerService.MSG_SAY_HELLO -> {
                    Log.i(TAG, "receiver message from service:" + msg.data.getString("reply"))
                }
                else -> {
                    super.handleMessage(msg)
                }
            }

        }
    }

除了添加以上代码,还需要在发送信息时把接收服务器端的回复的Messenger通过Message的replyTo参数传递给服务端,以便作为同学桥梁,代码如下:

private fun sayHello() {
        mService?.let { messenger ->
            val msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0)
            msg.replyTo = mReceiverReplyMsg
            try {
                messenger.send(msg)
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
    }

ok~,到此服务端与客户端双向消息传递的简单例子修改完成,我们运行一下代码,看看Log打印,如下:

2019-01-08 21:46:13.498 4259-4259/com.wangyy.service:remote I/MessengerService: Service is invoke onCreate
2019-01-08 21:46:13.500 4259-4259/com.wangyy.service:remote I/MessengerService: Service is invoke onBind
2019-01-08 21:46:50.758 4259-4259/com.wangyy.service:remote I/MessengerService: thanks,Service had receiver message from client!
2019-01-08 21:46:50.782 3736-3736/com.wangyy.service I/MessengerActivity: receiver message from service:ok~,I had receiver message from you! 
2019-01-08 21:52:11.365 4259-4259/com.wangyy.service:remote I/MessengerService: Service is invoke onUnbind
2019-01-08 21:52:11.367 4259-4259/com.wangyy.service:remote I/MessengerService: Service is invoke onDestroy

由Log可知,服务端和客户端确实各自收到了信息,到此我们就把采用Messenge进行跨进程通信的方式分析完了,最后为了辅助大家理解,这里提供一张通过Messenge方式进行进程间通信的原理图:


messenger_process.png

1.3 使用 AIDL

由于Messenger是以串行的方式处理客户端发来的消息,如果当前有大量消息同时发送到Service(服务端),Service仍然只能一个个处理,这也就是Messenger跨进程通信的缺点了,因此如果有大量并发请求,Messenger就会显得力不从心了,这时AIDL(Android 接口定义语言)就派上用场了, 但实际上Messenger 的跨进程方式其底层实现 就是AIDL,只不过android系统帮我们封装成透明的Messenger罢了 。因此,如果我们想让服务同时处理多个请求,则应该使用 AIDL。 在此情况下,服务必须具备多线程处理能力,并采用线程安全式设计。使用AIDL必须创建一个定义编程接口的 .aidl 文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,随后可在服务内对其进行扩展。

这部分主要是跨进程通信的内容,在此不过多的讲解,后面会有专门的文章讲述AIDL。

关于绑定服务的注意点
  1. 多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一 IBinder 传递至任何其他绑定的客户端。当最后一个客户端取消与服务的绑定时,系统会将服务销毁(除非 startService() 也启动了该服务)。
  2. 通常情况下我们应该在客户端生命周期(如Activity的生命周期)的引入 (bring-up) 和退出 (tear-down) 时刻设置绑定和取消绑定操作,以便控制绑定状态下的Service,一般有以下两种情况:
  • 如果只需要在 Activity 可见时与服务交互,则应在 onStart() 期间绑定,在 onStop() 期间取消绑定。
  • 如果希望 Activity 在后台停止运行状态下仍可接收响应,则可在 onCreate() 期间绑定,在 onDestroy() 期间取消绑定。
  1. 通常情况下(注意),切勿在 Activity 的 onResume() 和 onPause() 期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,这样反复绑定与解绑是不合理的。此外,如果应用内的多个 Activity 绑定到同一服务,并且其中两个 Activity 之间发生了转换,则如果当前 Activity 在下一次绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务,因此服务的绑定不应该发生在 Activity 的 onResume() 和 onPause()中。
  2. 我们应该始终捕获 DeadObjectException DeadObjectException 异常,该异常是在连接中断时引发的,表示调用的对象已死亡,也就是Service对象已销毁,这是远程方法引发的唯一异常,DeadObjectException继承自RemoteException,因此我们也可以捕获RemoteException异常。
  3. 应用组件(客户端)可通过调用 bindService() 绑定到服务,Android 系统随后调用服务的 onBind() 方法,该方法返回用于与服务交互的 IBinder,而该绑定是异步执行的。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,271评论 6 524
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,533评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,580评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,203评论 1 303
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,204评论 6 401
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,664评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,014评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,991评论 0 280
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,536评论 1 326
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,558评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,678评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,267评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,997评论 3 341
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,429评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,580评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,259评论 3 382
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,744评论 2 366

推荐阅读更多精彩内容

  • [文章内容来自Developers] 绑定服务是客户端-服务器接口中的服务器。绑定服务可让组件(例如 Activi...
    岳小川阅读 1,104评论 0 1
  • 绑定服务: 绑定服务是客户端-服务器接口中的服务器。绑定服务可让组件(例如 Activity)绑定到服务、发送请求...
    pifoo阅读 1,235评论 0 4
  • 绑定服务是客户端-服务器接口中的服务器。绑定服务可让组件(例如 Activity)绑定到服务、发送请求、接收响应,...
    鹿小纯0831阅读 454评论 0 0
  • 2.1 Activity 2.1.1 Activity的生命周期全面分析 典型情况下的生命周期:在用户参与的情况下...
    AndroidMaster阅读 3,064评论 0 8
  • 协同力:时刻辅佐工作的进行 1.团队需要一个明确的使命,这样才有明确行动方向。明确的使命!具体。2.结果说明一切,...
    KillerManA阅读 317评论 0 1