Android面试一天一题(Day 25:进程和线程)

作者: goeasyway | 来源:发表于2016-09-09 07:42 被阅读3731次

    大家一定听说过这个段子:哲学老师说门卫才是哲学家,因为他总是在问:“你是谁?你从哪儿来?要到哪儿去?”

    一切学科的问题,最终都会归结于哲学问题,那么我们是否也可以用哲学的思维来指导Android开发呢?在进行Android应用开发时,有没有从这几个问题来思考过你的代码呢?实现中,我们大多时候其实只关注怎么进行应用层的开发(产出APK),似乎觉得没有必要对Android的机制和来龙去脉搞得那么清楚。

    但以我多年的“行医”经验总结,对Android机制和工作原理有深入的了解,更可以快速地诊断遇到的问题(BUG),特别能够对那些靠查看代码依然让人摸不着头绪的难题进行排查。所以,这一节我们来了解一下进程和线程相关的知识,它们可以让你更清楚的想明白应用的一些状态。

    面试题:如何理解Android应用的进程?

    进程是一个动态的过程,每一个App的运行都是在一个独立的进程中,进程有自己独立的内存和数据空间,进程的名字就是App的packageName,这些进程都是从Zygote进程Fork出来的,并受AMS(ActivityManagerService)管理。

    默认情况下,同一App的所有组件均在相同的进程中运行,但也可以允许App有多个进程。在AndroidManifest.xml里边给四大组件配置android:process属性,就可以让这些组件在指定的进程中运行,这些进程名字都是packageName:name这种,以区分是属于哪个App,我一般称之为辅助进程。

    此外,您还可以设置android:process(即不以“:”开头,以符合包命名的格式字符串为它的值),使不同应用的组件在相同的进程中运行,但前提是这些应用共享相同的Linux用户ID并使用相同的证书进行签署。

    常有些开发不知道为什么自己的Application.onCreate中的代码执行了两次,如果你遇到这样的情况可以检查一下AndroidManifest.xml是否给某个组件配置了android:process属性。每个进程创建后,都会启动一个主线程(Looper接收消息),每个组件启动前都会先创建Application实例(一个进程只创建一个)。

    优先级

    当系统内存不足时,Android系统会选择终止掉一部份进程,回收其所占用的内存空间。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。

    重要性从高到低如下所示:

    前台进程
    可见进程
    服务进程
    后台进程
    空进程

    其实也可以用优先级(oom_adj值)来表示这个重要性。App进程的优先级是在com.android.server.am.ProcessList
    类里边定义的。所以当内存不足的时候,进程优先级低的(oom_adj越大的,系统进程是负数),占内存大的App进程将会被优先kill掉。

    很多项目都比较“变态”,总想长期霸占内存(如消息推送),所以就有了各种进程保活的黑科技面市。黑科技我们占且不论,对于一般提高进程优先级的方法,大家还是应该了解一些。

    如:

    1. 进程要运行一些组件,不要成为空进程。
    1. 远行一个Service,并设置为前台运行方式(startForeground)。
    2. AndroidManifest.xml中配置persistent属性(persistent的App会被优先照顾,进程优先级设置为PERSISTENT_PROC_ADJ=-12)

    关于第2点,摘抄一段代码给大家看:

        private void keepAlive() {
            try {
                Notification notification = new Notification();
                notification.flags |= Notification.FLAG_NO_CLEAR;
                notification.flags |= Notification.FLAG_ONGOING_EVENT;
                startForeground(0, notification); // 设置为前台服务避免kill,Android4.3及以上需要设置id为0时通知栏才不显示该通知;
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    

    在Service的onCreate方法调用keepAlive()即可,其实就是是欺骗系统把自己当成一个一直在通知栏的Notification。不过这种方式,并不保证在所有的机型上都有效。

    线程

    线程是CPU调度的基本单元,一个应用都有一个主线程负责处理消息。一个应用启动后,至少会有3个线程,一个主线程(UI线程)和2个Binder线程。Zygote进程(APK所在的进程也是由Zygote进程Fork出来的)还会产生有一些Daemon线程如:ReferenceQueueDaemon、FinalizerDaemon、FinalizerWatchdogDaemon、HeapTaskDaemon,从名字大家也可以对它们的用途猜出一二。

    线程间可以共享资源,为了保存UI的更新不会混乱,所以更新UI控件时要求在主线程进行更新,即需要保证更新UI是线程安全的。有时还可以问问面试者,什么是线程安全,貌似不知道这个概念的人也不少。

    线程同步方面,之前章节有介绍过,这里就不在讲了。

    小结

    关于进程其实有很多可以讲的,比如DVM, Native进程,或者讲讲Linux进程方面的知识也可以。面试的时候,并不一定需要面试者回答标准答案,或者一定要答对。很多时候,是要面试者对问题进行思考并用自己的语言把自己的理解表达出来,对错并不是特别重要。

    相关文章

      网友评论

      • ac2c0732fd94:关于线程安全理解有深入了。线程间的资源共享会让 UI 更新混乱(如果同时有两个线程更新 UI,是这个线程先更新呢,还是另外一个先更新),而强制规定必须在 UI 线程更新 UI 的话就要走 Handler 通信,这套通知机制是使用队列来进行,也就会有先来后到(不过这里面是如何确认先来后到的是不需要开发者了解的?),也就保证了 UI 更新正确性。

        有什么问题吗?博主。
        LiYanZhao:线程安全问题是可以通过加锁等同步机制解决的,但是同步机制会有性能损耗,会影响UI的绘制,所以Android采用单线程模型。保证UI更新正确的并不是Handler通信机制,保证UI正确更新的是Android的单线程模型,你使用任何一种通信手段,只要能把消息发送到UI线程,UI线程都能正确处理。Android中通过子线程也是有可能更新UI的,在Activity的onCreate方法中通过子线程更新UI是有可能成功的,UI线程的限制只是ViewRootImpl保存了创建它的线程,在更新时会检查是否创建ViewRootImpl的线程进行的调用,如果不是则抛异常,而在Activity的onCreate执行时ViewRootImpl还未创建,此时在子线程中对View做更新操作只是修改了View对象的属性,不会触发ViewRootImpl的线程检查。当然,如果你的View是在子线程中通过WindowManager添加,ViewRootImpl的创建线程就是进行操作的子线程,那你的View的更新操作也只能在创建ViewRootImpl的子线程中进行。
        ac2c0732fd94:@goeasyway 懂的,只是没对‘线程安全’这个词没去理解。
        goeasyway:@野生ChaoS 不在UI线程更新一般会报异常,handler的messagequeue是先进先出的队列。
      • 90f81f3a5ff9:继续收藏好文章
      • 无奇W:一个主线程(UI线程)和2个Binder线程。请问这两个Binder线程是什么?没有查到合适的资料,不知道查什么关键字,请指教。。
        前进121: @无奇W activity会受到ams,wms的管理,需要数据的交换,但是他们又不在同一个进程中,binder就充当数据传递媒介,binder也有自己独立的线程

      本文标题:Android面试一天一题(Day 25:进程和线程)

      本文链接:https://www.haomeiwen.com/subject/nzfwrttx.html