Android进程和线程简述

作者: coderdendi | 来源:发表于2017-01-18 22:59 被阅读482次

    围绕Android中的进程与线程做了简要的概述。

    按照操作系统中的描述

    线程是CPU调度的最小单元,同时线程是一种有限的系统资源

    而进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用

    一个程序至少有一个进程,一个进程至少有一个线程

    操作系统并没有将多个线程看做多个独立的应用,来实现进程间的调度和管理以及资源分配,这就是进程和线程的重要区别

    差别

    进程是系统进行资源分配和调度的一个独立单位。

    线程是进程的一个实体,是CPU调度和分配的基本单位,是比进程更小的能独立运行的基本单位,线程本身不拥有系统资源(除了必不可少的资源如程序计数器、寄存器、栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

    主要差别在于是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的健壮,但是多进程在切换时,资源耗费大,效率要差。

    进程

    进程在执行过程中拥有独立的内存单元,而多个线程共享内存,极大的提高了程序的运行效率。

    进程保活的方式:这篇文章

    Foreground process

    • 有一个Activity且它
      • 正在交互
    • 有一个Service且它
      • 绑定到正在交互的Activity
      • "前台运行",startForeground()
      • 正在执行生命周期回调 onCreate() onStart() onDestroy()
    • 有一个BroadcastReceiver且它
      • 正在执行onReceive()

    Visible process

    • 有一个Activity且它
      • 不在交互,但仍可见
    • 有一个Service且它
      • 绑定到可见Activity

    Service process

    • 普通Service

    故而对于耗时的比如上传等,新建一个Service是比在activity中新建一个线程好得多的。

    Background process

    • 所有Activity都对用户不可见

    会被保存在LRU列表中,即最近查看的最晚被终止

    Empty process

    系统有时候会使用空进程做为缓存,以缩短下一次在其中运行组建所需的启动时间。

    额外说明

    若一个进程A依赖于另一个进程B,则进程B的优先级可能会被提升并保证B的优先级高于A顶优先级。

    ​ 举例 (A优先级永远高于B):

    • A中的ContentProvider提供数据给B
    • A中的某个service绑定到B的某个组件

    进程间通信

    IPC InterProcess Communication

    RPC Remote Procedure Call

    Android默认每个app是一个进程,但也可以通过android:process属性使每个app有多个进程,或者多个app共享某个进程。

    有时候通过多进程的方式获取多份内存空间。一般是指定android:process属性,还有一种非常规的方法,通过JNI在native层去fork一个新的进程。

    多进程的特性

    • 不同的内存空间,数据无法共享
    • 需要谨慎处理代码中的线程同步
    • 需要提防多进程并发导致的文件锁和数据库锁时效的问题

    具体问题:

    1. 静态成员和单例模式完全失效
    2. 线程同步机制失效
    3. SharedPreferences可靠性下降
    4. Application会多次重建
    

    进程间通信方式

    摘自任玉刚的《把玩Android多进程》ppt

    Screen Shot 2017-01-07 at 16.37.37

    Parcelable和Serializable

    Serializable使用简单但是开销很大,序列化和反序列化过程需要大量的IO操作,一般用于将对象序列化到存储设备中或者将对象序列化后通过网络传输。

    Parcelable使用麻烦,但效率很高。

    Binder

    binder是Android中的一种跨进程通信方式

    可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder.

    从Android Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁。

    从Android应用层来说,Binder是客户端和服务端进行通信的媒介。当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

    更安全,比如socket的ip地址可以进行伪造,而Binder机制从协议本身就支持对通信双方做身份校验,因而大大提升了安全性,这个也是Android权限模型的基础。

    Binder通信模型

    如下图,伪装。即代理模式。对代理对象的操作会通过驱动最终转发到Binder本地对象上去完成,当然使用者无需关心这些细节。

    Binder伪装

    Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,它的引用却遍布于系统的各个进程中。最诱人的是,这个引用和java里引用一样既可以是强类型,也可以是弱类型,而且可以从一个进程传给另一个进程,让大家都能访问同一Server,就像一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。

    线程

    线程状态转换

    Android中的线程

    Android系统基于精简过后的linux内核,Linux系统的调度器在分配time slice的时候,采用的CFS(Completely fair scheduler)策略,不仅会参考单个线程的优先级,还会追踪每个线程已经获取到的time slice数量。优先级高的线程不一定能在争取timeslice上有绝对的优势。

    Android将进程分为多个group,其中有两种比较重要:

    • default group
      • 能获得绝大部分的timeslice(UI线程就属于此列)
    • background group
      • 工作线程,最多被分配10%的timeslice

    其中background group需要开发者显示的归位(官方建议

    new Thread(new Runnable(){
        @Override
        public void run(){
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            // do sth
        }
    }).start();
    

    线程间通信

    1. 共享内存
    2. 文件、数据库
    3. Handler
    4. Java里的wait notify notifyAll

    UI / Main thread

    系统启动时创建的线程,用来处理页面的绘制。

    不可以把耗时操作放在ui线程中,如网络请求、数据库的读写等等,阻塞超过5s会发生ANR错误

    因为Android UI toolkit不是线程安全的,故而所有的页面绘制都必须放在UI线程中做。

    有个黑科技是可以在Activity的onResume()前使用非UI线程绘制UI,因为检测线程是否是UI线程是在ViewRootImpl中进行检测的,而ViewRootImpl是在onResume()时才会进行初始化的

    仅限了解,请勿在实际项目中尝试。

    在非UI线程中更新UI

    简单情况

    • Activity.runOnUiThread(Runnable)
    • View.post(Runnable)
    • View.postDelayed(Runnable,long)
    • handler
    • AsyncTask

    Handler介绍

    Screen Shot 2017-01-07 at 17.25.19

    主要分为三个部分:

    • Looper
      • 工人,完成MessageQueue里面的任务
      • 用来执行消息队列中的消息,本质是一个while循环,通过pipe机制进行同步
      • 每个Thread最多拥有一个Looper,而Thread只有拥有了Looper,才能初始化Handler
    • MessageQueue
      • 任务队列,采用FIFS
    • Handler
      • 将消息放入MessageQueue

    注意点

    Can't create handler inside thread that has not called Looper.prepare()

    handler所在的线程必须调用过Looper.prepare方法,否则没有looper来进行工作

    public static final void prepare(){
        if(sThreadLocal.get()!=null){
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //关于ThreadLocal的介绍,放在文章末尾,此处可以理解为一个referfence,可以set和get
        sThreadLocal.set(new Looper());
    }
    

    所以需要在新开的线程中显示调用Looper.prepare()方法,否则无法在此线程中新建handler(不然sThreadLocal中是取不到looper来进行更新操作的)

    Android中有一个现成的类HandlerThread,可以方便使用

    HandlerThread handlerThread = new HandlerThread("thread_name");
    handlerThread.start();
    //MyHandler extends android.os.Handler
    mHandler = new MyHandler(handlerThread.getLooper());
    

    Thread类

    • 启动了新的线程,没有任务的概念,不能做状态的管理。
    • start之后,run当中的代码就一定会执行到底,中途无法取消。
    • 作为匿名内部类持有了外部类的引用,在线程退出之前,会阻碍GC的回收,在一段时间内造成内存泄露
    • 没有线程切换的接口,要传递处理结果到UI线程,需要些额外的线程切换代码
    • 如果从UI线程启动,该线程优先级默认为Default

    AsyncTask<Params,Progress,Result>

    重写方法:

    • onPreExecute
      • 在执行耗时线程前被调用
    • doInBackground(Params...)
      • 在后台线程执行,返回Result,执行过程中调用publicProgress(Progress)来进行任务进度的更新
    • onPostExecute(Result)
      • 在UI线程中执行
    • onProgressUpdate(Progress...)
      • 在UI线程执行,在publishProgress方法调用后执行

    在不同的系统版本上串行与并行的执行行为不一致

    必须遵守的规则:

    1. Task实例必须在UI线程中创建
    2. execute方法必须在UI线程中调用
    3. 该Task只能被执行一次,多次调用时会出现异常
    4. 不要手动调用onPreExecute....等生命周期方法,使用publishProgress()更新进度

    ThreadPoolExecutor

    Thread、AsyncTask适合处理单个任务的场景,HandlerThread适合串行处理多任务的场景。当需要并行的处理多任务时,ThreadPoolExecutor是更好的选择。

    提供复用机制(线程池)

    public static Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
            TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
    
    //execute
    THREAD_POOL_EXECUTOR.execute(Runnable XX);
    

    代价

    • 每一个新线程至少消耗64kb内存
    • 线程的切换回带来额外开销(switch context)
    • 尽量复用已有的工作线程

    ThreadLocal

    很多地方出现这个东西,其实它是一个容器,用来存放线程的静态局部变量,保证每一个线程都拥有单独的静态成员变量,保证了线程安全。

    ThreadLocal<T>可以近似的认为是Map<Thread,T>,它的get方法就是以当前线程为key去map中取对应的T

    ThreadLocal为每一个线程提供了一个独立的副本。

    Sample

    比如说下面这段代码为每个线程创建一个计数器,这时使用不同的线程获得的number就不同。

    public interface Sequence{
        int getNumber();
    }
    
    public class SequenceByThreadLocal implements Squence{
        private static ThreadLocal<Integer> numberContainer = new ThreadLocal<Integer>(){
            @Override
            protected Integer initialValue(){
                return 0;
            }
        };
    
        public int getNumber(){
            numberContainer.set(numberContainer.get()+1);
            return numberContainer.get();
        }
    }
    

    一个简易实现

    其实这个数据结构很简单,可以用代码做一个简易的实现

    public class MyThreadLocal<T>{
        private Map<Thread,T> container = Collections.synchronizedMap(new HashMap<Thread,T>());
    
        public void set(T vaule){
            container.put(Thread.currentThread(),value);
        }
    
        public T get(){
            Thread thread = Thread.currentThread();
            T value = container.get(thread);
            if(value == null && !container.containsKey(key){
                valuee = initValue();
                container.put(thread,value);
            }
            return value;
        }
    
        public void remove(){
            container.put(Thread.currentThread());
        }
    
        protected T initialValue(){
            return null;
        }
    }
    

    相关文章

      网友评论

        本文标题:Android进程和线程简述

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