Android进程/线程

Android进程/线程

本文讲解Android系统中的进程/线程的概念
author:psaa
date:2020年9月16日
参考资料:《深入理解Andoird内核设计思想》

Android进程和线程

进程(Process)是一个程序的一个运行实例,以区别于“程序”这一静态概念;而线程则是CPU调度的基本单位。
我们知道,一个应用程序的主入口一般都是main函数,这基本上成了程序开发的一种规范——它是“一切事物的起源”。而main()函数的工作也是前篇一律:

  • 初始化 (比如创建对象、申请资源等)
  • 进入死循环 (在循环中处理各种事件,直到进程退出

这种模型是“以事件为驱动”的软件系统的必然结果,因此几乎存在于任何操作系统和编程语言中。

对于Android应用开发者而言,通常面对的都是Activity、Service等组件,并不需要特别关心进程是什么。因而产生了一些误区,如部分开发者认为系统四大组件就是进程的载体。

实际上四大组件只是application的零件,只能算作进程的组成部分。

ActivityThread

主线程由ZygoteInit启动,经由一系列调用后最终才执行Activity本身的onCreate()函数。最重要的是Zygote为Activity创建的主线程是ActivityThread。

一个没有任何实际功能的Activity程序回启动一个main Thread以及多个Binder Thread。

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        AndroidOs.install();
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
        Process.setArgV0("<pre-initialized>");
        Looper.prepareMainLooper();/*只有主线程才能调用这个函数,普通线程应该使用prepare()*/

        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();/*这个main()是static的,因此这里需要创建一个实例*/
        thread.attach(false, startSeq);/*Activity是有界面显示的,这个函数将与WindowManagerSerice建立联系*/

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler(); /*主线程对应的Handler*/
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();/*主循环开始*/

        throw new RuntimeException("Main thread loop unexpectedly exited");/*如果程序运行到这里说明退出了上面的Looper循环*/
    }

除此之外上述Binder线程并不是Activity应用进程独有的。

  • Service也是寄存于ActivityThread之中的,并且启动流程和Activity基本一致。
  • 启动Service时,也同样需要多个Binder线程的支持。

上述对于启动时Binder线程的描述按照书上为两个,启动Acitivyt和Service时都需要两个Binder线程的支持,但在本人验证中单独启动一个Activity会启动很多个线程(10个左右),Binder线程为3个(很有可能是其他线程所需要的,暂时无法确定),不过我倾向于2个,这是因为在IPC中的进程间通信所需要的工具都是成对出现。

进程与线程的总结:

  • 四大组件并不是程序(进程)的全部,而只是它的“零件”
  • 应用程序启动后,将创建ActivityThread主线程
  • 同一个包中的组件将运行在相同的进程空间中
  • 不同包中的组件可以通过一定的方式运行在一个进程空间中
  • 一个Activity应用启动后至少会有3个线程:即一个主线程和两个Binder线程

Handler,MassageQueue,Runnable和Looper

概述:Looper不断的获取MessageQueue中的一个Message,然后由Handler来处理
  • Runnable和Message可以被压入某个MessageQueue中,形成一个集合

注意,一般情况下某种类型的MeassageQueue只允许保存相同类型的Object。所以实际源码中需要先对Runnable进行相应的转换。

  • Looper循环地去做某件事

它不断的去除message然后传给Handler进行处理,如此循环往复。假如队列为空,那么他会进入休眠。

  • Handler是真正处理事情的地方

它利用自身的处理机制,对传入的各种Object进行相应的处理并产生结果。

Handler
public class Handler {...
    final MessageQueue mQueue;
    final Looper mLooper;
    final Callback mCallback;
}
  1. 每个Thread只对应一个Looper;
  2. 每个Looper只对应一个MessageQueue;
  3. 每个MessageQueue中有N个Message;
  4. 每个Message中最多指定一个Handler来处理事件

由此可以推断出,Thread和Handler是一对多的关系。
Handler主要有两方面的作用:

  • 处理Message,这是它作为处理者的本职所在。
  • 将某个Message压入MessageQueue中。
处理Message

相关实现函数如下

public void dispatchMessage(@NonNull Message msg)
public void handleMessage(@NonNull Message msg)

Looper从MessageQueue中取出一个Message后,首先会调用Handler.dispatchMessage进行消息派发;后者则根据具体的策略将Message分发给相应的责任人。默认情况下Handler的派发流程是:

Message.callback(Runnable对象)是否为空

在不为空的情况下,将优先通过callback来处理。

Handler.mCallback是否为空

在不为空的情况下,调用mCallback.handlerMessage

如果两个对象都不存在,才调用Handler.handlerMessage

将Message压入MessageQueue

相关功能函数:

1、Post系列
public final boolean post(@NonNull Runnable r)
public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis)
public final boolean postDelayed
...
2、Send系列
public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)//return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg)
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis)

send处理的参数直接是Message,post则需要先把需要先把Runnable转换成Message。
Handler通过如下方法将Runnable转换成Message

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

@UnsupportedAppUsage
private static Message getPostMessage(Runnable r, Object token) {
    Message m = Message.obtain();
    m.obj = token;
    m.callback = r;
      

准备好Message之后,程序调用sendMessageDelayed来执行下一步操作。这个函数可以设定延迟多长事件后再发送消息,其内部又通过当前事件+延时时长(SystemClock.uptimeMillis() + delayMillis)计算出具体是在哪个时间点发送消息

SystemClock.uptimeMillis()表示系统开机到当前的时间总数,单位是毫秒,但是,当系统进入深度睡眠(CPU休眠、屏幕休眠、设备等待外部输入)时间就会停止,但是不会受到时钟缩放、空闲或者其他节能机制的影响。

既然最终都是依靠Handler来处理消息,为什么不直接执行要压入MessageQueue呢?这其实体现了程序设计一个良好的习惯,即“有序性”。

MessageQueue

正如其名,MessageQueue是一个消息队列,因而它具有“队列”的所有常规操作。

  • 新建队列
  • 元素入队
  • 元素出队
  • 删除元素
  • 销毁队列
 MessageQueue(boolean quitAllowed) {
     mQuitAllowed = quitAllowed;
     mPtr = nativeInit(); //nativeInit会在本地创建一个NativeMessageQueue对象,然后直接赋值给成员变量。这一系列操作实际上都是通过内存指针进行
 }

 boolean enqueueMessage(Message msg, long when)//元素入队
 Message next() //@UnsupportedAppUsage 元素出队
 void removeMessages(Handler h, int what, Object object)//删除元素
 void removeMessages(Handler h, Runnable r, Object object)/删除元素
 private void dispose() {
        if (mPtr != 0) {
            nativeDestroy(mPtr);//调用本地方法销毁MessageQueue
            mPtr = 0;
        }
    }
Looper

需要注意的是Looper在主线程和普通线程钟的调用方式不一样。

普通线程范例
class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                ...//处理消息的地方。继承Handler的子类通常需要修改该函数
            }
        };
        Looper.loop();//进入主循环
    }
}

概括来看只有3个步骤:

  1. Looper的准备工作(prepare)
  2. 创建处理消息的handler
  3. Looper开始运作(loop)

但是mHandler是如何保证把外部的消息投递到Looper所管理的MessageQueue中的。
Looper中有一个非常重要的成员变量

// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = newThreadLocal<Looper>();

这是一个静态类型的变量,意味着一旦import了Looper后,sThreadLocal就已经存在并构建完毕。ThredLocal对象是一种特殊的全局变量,因为它的“全局”性只限于自己所在的线程,而外界所有线程(即使是同一进程)一概无法访问到它。这说明每个线程的Looper都是独立的。
为什么在Looper的调用中不需要传入handler对象或者handler对象没有指定Looper呢?
sThreadLocal会创建一个只针对当前线程的Looper及其他相关的数据对象

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {//这个判断保证了一个Thread中只有一个Looper存在
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

Handler是如何与Looper关联起来的呢?
答案是通过Handler的构造函数

public Handler() {
    ...
    mLooper = Looper.myLooper();//还是通过sThreadLocal.get来获取当前线程中的Looper实例
    ...
    mQueue = mLooper.mQueue;//mQueue是Looper与Handler之间沟通的桥梁
    mCallback = null;
}

这样Handler、Lopper、MessageQueue即联系起来,Handler执行Post/Send方法时,会将消息压入Queue中(也就是mLooper.mQueue)。一旦Looper处理到这条消息,它又会从中调用Handler来进行处理。

UI主线程——ActivityThread
public static void main(String[] args){
    ...
    Looper.prepareMainLooper();
    ActivityThread threwad = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler(); /*主线程对应的Handler*/
        }
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

比较主线程与普通线程中Looper的使用可以发现

  • 主线程使用prepareMainLooper和普通线程prepare
  • Handler不同,普通线程生成一个与Looper绑定的Handler就行,主线程是从当前线程中获取的Handler(thread.getHandler)

preapareMainLooper与prepare的比较

    public static void prepareMainLooper() {
        prepare(false);//参数false表示该线程不允许退出
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();//
        }
    }
    
    public static void prepare() {
        prepare(true);//普通线程默认允许线程退出
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

Main Thread使用的prepareMainLooper(),这个函数通过prepare为主线程生成一个ThreadLocal的Looper对象,并让sMainLooper指向它。这样的目的是其他线程如果要求获取主线程Looper,只需调用getMainLooper()即可。
Google通过这样的方式巧妙的区分开各线程的Looper并界定了他们的访问权限。

sMainThreadHandler

当ActivityThread对象创建时,会在内部同时生成一个继承自Handler的H对象。

@UnsupportedAppUsage
final H mH = new H();
...
class H extends Handler {...

AcitivityThread.main中调用的thread.getHandler()返回的就是mH。这个mH就是主线程中的负责处理各种消息的Handler了。
最后分析一下looper()函数,在文章开头提到的“以事件为驱动”的经典模型如下:

伪代码
main(){
    initialize();//初始化操作
    while(getMessage())//不断的获取斌执行消息,直到收到退出的消息
    {
        TranslateMessage();//对消息进行必要的前期处理
        DispatchMessage();//分配消息给相应的元素进行处理
    }
}

这个模型在每个系统平台上的实现细节会有所差异,但本质都是一样的。即

  • 创建处理消息的环境
  • 循环处理消息

也就是说,消息是推动整个系统动起来的基础,即便操作系统本身也是如此。
再来看Looper.loop的实现:

    public static void loop() {
        final Looper me = myLooper();//loop函数也是静态的,所以它只能访问静态数据。函数myLooper则调用sThreadLocal.get()来获取与之匹配的Looper实例(其实就是去除之前prepare中创建的那个Looper对象)
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        ...
        for (;;) {//消息循环开始
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);//开始派发消息,变量target实际上是一个Handler,所以dispatchMessage最终调用的是Handler中的处理函数
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
            msg.recycleUnchecked();//消息处理完毕会将消息回收
        }

loop()函数的主要工作就是不断地从消息队列中去除需要处理的事件,然后分发给相应的责任人(msg.target).如果消息队列为空,它很可能会进入睡眠让出CPU资源。儿在具体事件的处理过程中,程序会post新的事件到队列中。另外其他进程也可能投递新的事件到这个队列中。apk应用程序就是不停的执行“处理队列事件”的工作,直至它退出运行。
最后,可以给出基于AcitivityThread的进程模型:
基于ActivityThread的进程模型

Android进程是指在Android系统中运行的应用程序的实例。每个进程包含多个线程线程进程内的执行单元。进程线程的管理对于程序的性能和内存优化非常重要。 为了进行程序内存优化,可以考虑以下几个方面: 1. 降低内存占用:可以通过合理设计和优化程序的数据结构和算法,减少不必要的内存占用。避免无限制地加载大量数据,及时释放不再使用的内存资源。 2. 避免内存泄漏:内存泄漏是指程序中分配的内存没有被及时释放,导致内存资源无法再被其他程序使用。在Android开发中,避免内存泄漏的一种常见方法是使用弱引用来持有对象,及时释放不再需要的对象。 3. 优化进程间通信:Android中的进程间通信(IPC)是指不同进程之间进行数据交换和通信的机制。为了减少IPC的开销,可以考虑使用轻量级的数据传输方式,如通过Intent传递数据,避免使用大量的跨进程通信。 4. 使用合适的线程管理:Android提供了多种线程管理方式,如使用AsyncTask、Handler等,可以根据具体的需求选择合适的线程管理方式来提高程序的性能和响应速度。 5. 使用内存管理工具:Android提供了一些内存管理工具,如Profiler和Memory Analyzer等,可以帮助开发者分析和优化程序的内存使用情况,及时发现和解决内存问题。 综上所述,通过优化程序的内存占用、避免内存泄漏、优化进程间通信、合理使用线程管理和使用内存管理工具,可以帮助提高Android程序的性能和内存优化。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [android进程线程](https://blog.csdn.net/wangbuji/article/details/124755430)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值