前言
来到公司实习一个半月了,一直在做测试,每天都是重复机械性的动作,脑子都快生锈了。前几天佳哥让我们几个实习生选择一个想要深入的模块,听到消息我兴奋了半天。冷静下来之后我去询问华仔师兄哪个模块能学到更多的东西,师兄告诉我他是做iPod的,他说这个模块应该是最难的,会接触到JNI、IPC通信、多线程和很多设计模式的知识,我又兴奋了,第二天就找佳哥确定了下来。师兄给了我一份代码,然后跟我讲解了开发流程、MFi认证、代码执行流程并且带我认识了DQA做iPod测试的曾姐,这里再次谢谢师兄啦~自己开始看代码的时候简直一头雾水,好多类,每个类都有好多方法和变量,跳转过来跳转过去,跳转之间我看到了Service、ServiceConnetion,可惜之前学的这部分内容差不多已经忘光光了,所以我在网上百度了半天,终于有点眉目了。为了防止以后出现这种忘记之前学的内容的情况,所以我决定开始写博客,主要记录自己学到的东西和一些笔记,顺带当成日志咯,废话说完了,开始正题吧。
什么是Service?
在Android的四大组件里面,Activity用来展示一个界面,与用户交互,Service则是处理一些“后台”的事物。我们在Activity里面执行代码都是在主线程里面,如果直接在主线程里面执行费时操作的话就会产生ANR(Application Not Responding)错误,所以按理说如果在Service中去执行这些费时的操作比较符合情理吧,但是通过分别在Activity的onCreate和在Service的onCreate中打印线程id:
Log.d(TAG, "pid : " + Process.myPid());
可以发现两者运行在同一个线程当中。什么鬼?这样的话,在Service的线程中执行费时操作不就会引起ANR错误吗(第一次面试的时候被问到这个问题,我也是这个萌萌哒反应)?直接在Service中执行费时操作确实会引起ANR,所以需要在Service中开子线程去执行费时操作。新司机们可能会问了,那既然是同一个线程,为什么不直接在Activity中开子线程执行费时操作咯,干嘛这么麻烦?这个时候就引申出如下的问题。
在Activity和Service中开线程有什么区别?
Android系统是穿上Dalvik和JVM外衣的Linux,一个app执行的时候,Linux创建一个进程来跑一个Dalvik虚拟机,在虚拟机中执行我们的应用代码,顺带给这个进程分配一些内存。当Android系统内存不足的时候,它老人家就会销毁一些进程来释放内存,那应该销毁哪些进程呢?正在和用户交互、用户看得见的肯定不能销毁,不然会让用户不开心,正在执行一些重要的任务的进程也不能销毁,比如播放音乐、下载文件这类的进程,销毁了也会让影响用户感受。那应该销毁哪些进程才合适呢?或者,我们换个方向思考:上述的进程为什么会被Andoird系统判断不能销毁?因为它们使用了Service中开线程的方式去执行费时操作的。反言之,如果我们的app在执行费时操作的时候只是在Acticvity中开子线程去执行,进程的优先级会比较低,在系统内存不足的时候就会被优先回收掉。以后再来说这个进程的分类吧,接下来看一下Service的生命周期咯
Service的生命周期
- void onCreate():在Service被创建的时候回调,这里面可以执行一些初始化的工作。
- int onStartCommand(Intent intent, int flags, int startId):这个方法是在开启Service过程的onCreate之后回调,里面的参数intent可以用来传一下数据给Service。来看看API怎么说:
Note that the system calls this on your service's main thread. A service's main thread is the same thread where UI operations take place for Activities running in the same process. You should always avoid stalling the main thread's event loop. When doing long-running operations, network calls, or heavy disk I/O, you should kick off a new thread, or use {@link android.os.AsyncTask}.
这个方法在Service的主线程里面调用,Service的主线程跟Activity里面进行UI操作是在同一个线程,不应该在里面做事件循环、费时操作和大量I/O操作,应该开一个新的线程或者AsyncTask去完成这些事。
- ** IBinder onBind(Intent intent)** : 在绑定Service的时候回调,这个方法能够返回一个IBinder的对象,跟onStartCommand最大的区别就在这里。下一篇我们详细来说,先来看看这个方法API怎么说:
Return the communication channel to the service. May return null if clients can not bind to the service. The returned {@link android.os.IBinder} is usually for a complex interface that has been <a href="{@docRoot}guide/components/aidl.html">described using aidl.
Note that unlike other application components, calls on to the IBinder interface returned here may not happen on the main thread of the process</em>. More information about the main thread can be found in <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html">Processes and Threads.
返回一个与Service通信的通道,客户端不能bind到Service的时候可能会返回null。返回的IBinder通常是一个用AIDL描述过的复杂的接口。不像其他的组件,这个方法返回值得过程可能不在跟主线程所在的进程里面完成。
第二段解释有点不明白啊,“可能不在跟主线程所在的进程里面完成”,我的理解是Service在注册的时候被指定了process,跟主进程不再是同一个进程。既然不在同一个进程了,那么那些堆啊栈啊的内存区也就不同了,这个时候很多东西在使用的时候就不能像以前我们还在同一片内存里面那么幸福愉快了,比如大家的静态成员变量不能一起用了,同步锁也失败了,单利模式也不行了等等,有机会再详细说吧。当Service跟要使用Service的Client不在同一个进程的时候,这里就需要使用跨进程通信了,API第一段中说的"用AIDL描述过的复杂的接口"就是其中一种方法,下下一篇将详细说这个东西。
- boolean onUnbind(Intent intent) : 在Unbind我们已经开启的Service的时候回调,有一个参数可以传必要的信息过来,来看看API怎么说:
Called when all clients have disconnected from a particular interface published by the service. The default implementation does nothing and returns false.
当Client与Service传给它的接口断开连接的时候在Service端调用,默认的实现什么都没干,返回一个false。
没啥好说的,下一个。
- void onRebind(Intent intent) : 在Client重新绑定Service的时候回调,这个方法回调的前提就是之前你得绑定过,并且已经Unbind了,我们来看看API:
Called when new clients have connected to the service, after it had
previously been notified that all had disconnected in its
{@link #onUnbind}. This will only be called if the implementation
of {@link #onUnbind} was overridden to return true.
当已经连接到Service的Client在onUnbind里面断开了与Service的连接,然后新的Client已经连接到Service的时候回调这个方法,并且实现重写这个onUnbind这个方法的时候得返回true才行。
就是说要想重新绑定的话,前一个已经绑定的Client得先断开连接,并且onUnbind方法的返回值要重写成true,但是我在测试的时候发现光按照这样的说不行,我得执行流程是直接bind,然后unbind(已经重写了返回true),然后我再次bind的时候什么都没回调啊,然后又查了一下,找到原因是我在bind之前没有先startService,为什么会这样呢?我们来研究以下执行流程:
- *第一种回调流程:onCreate -> onBind -> onUnbind -> onDestroy *
- *第二种回调流程:onCreate -> onStartCommand -> onBind -> onUnbind -> onRebind *
我们看,第一种流程没有执行start,在unbind之后,直接就destroy了,也就是说Service已经销毁了,当我们再次bind的时候,当然就不是rebind了,而是重新回调bind,第二种流程在bind之前执行了start,当我们onUnbind的时候,不会回调onDestroy,Service还存在,我们再来bind的时候就会去回调onRebind啦。
- void onDestroy() : 这个悲伤的方法无以言表,直接看API吧:
Called by the system to notify a Service that it is no longer used and is being removed.The service should clean up any resources it holds (threads, registered receivers, etc) at this point. Upon return, there will be no more calls in to this Service object and it is effectively dead. Do not call this method directly.
当Service被系统告知"你没用了"的时候回调,在这个方法里面应该做一些回收资源的工作,线程、注册的广播接收器、etc等等,回调完这个方法之后,Service里面的方法不会再有任何回调,这个Service绝对已经挂掉了。
正如API所说,Service没用了就会回调这个方法,做一些回收工作。那如果不回收会怎么样呢?随带让我想起了OOM,OOM的原因主要有两种,一个是图片回收没处理好产生的OOM,另外一种就是Activity中很多引用、资源等没有及时回收引起的OOM,以后有机会再说这个吧。回头看看写了好多啊,比写过的所有作文都用心。我的一血先到这里吧,如果文中出现不正确的地方非常欢迎各位大神帮小弟指出来,在这里感激不尽~下一篇将会写我理解到的startService和bindService的区别。