一个Android App最少有多少个线程?

6,948 阅读7分钟

守护线程

Signal Catcher线程

Signal Catcher是一个守护线程,用于捕获 SIGQUIT, SIGUSR1 信号,并采取相应的行为。

Android系统中,由Zygote孵化而来的子进程,包含system_server进程和各种APP进程都存在一个Signal Catcher线程,但是Zygote进程本身没有这个线程。

在Process类中有SIGQUIT,SIGUSR1 的定义:

    public static final int SIGNAL_QUIT = 3;
    public static final int SIGNAL_KILL = 9;
    public static final int SIGNAL_USR1 = 10;

当前进程的Signal Catcher线程接收到信号SIGNAL_QUIT,则挂起进程中的所有线程并DUMP所有线程的状态。

当前进程的Signal Catcher线程接收到信号SIGNAL_USR1,则触发进程强制执行GC操作。

发送信号的方式:

Process.sendSignal(android.os.Process.myPid(), Process.SIGNAL_QUIT);
Process.sendSignal(android.os.Process.myPid(), Process.SIGNAL_USR1);

当我们直接在代码中调用上诉代码会发生什么?

Process.SIGNAL_USR1:

I/Process: Sending signal. PID: 12363 SIG: 10
I/etease.popo.ap: Thread[3,tid=12373,WaitingInMainSignalCatcherLoop,Thread*=0x793d816400,peer=0x13700020,"Signal Catcher"]: reacting to signal 10
I/etease.popo.ap: SIGUSR1 forcing GC (no HPROF) and profile save
I/etease.popo.ap: Explicit concurrent copying GC freed 18773(4MB) AllocSpace objects, 7(140KB) LOS objects, 68% free, 2MB/8MB, paused 130us total 13.633ms

Process.SIGNAL_QUIT

I/Process: Sending signal. PID: 12812 SIG: 3
I/etease.popo.ap: Thread[3,tid=12823,WaitingInMainSignalCatcherLoop,Thread*=0x793d816400,peer=0x148051b0,"Signal Catcher"]: reacting to signal 3
I/etease.popo.ap: Wrote stack traces to '[tombstoned]'

RenderThread 渲染线程

Android 5.0之后新增的一个线程,用来协助UI线程进行图形绘制。所有的GL命令执行都放在这个线程上。渲染线程在RenderNode中存有渲染帧的所有信息,并监听VSync信号,因此可以独立做一些属性动画。

在清单文件APP中添加:android:hardwareAccelerated="false" 启动APP将会不存在渲染线程;硬件加速在Android中试默认开启的。

Render Thread在运行时主要是做以下两件事情:

  • Task Queue的任务,这些Task一般就是由MainThread发送过来的,例如,MainThread通过发送一个DrawFrameTask给RenderThread的TaskQueue中,请求RenderThread渲染窗口的下一帧。
  • PendingRegistrationFrameCallbacks列表的IFrameCallback回调接口。每一个IFrameCallback回调接口代表的是一个动画帧,这些动画帧被同步到Vsync信号到来由RenderThread自动执行。具体来说,就是每当Vsync信号到来时,就将一个类型为DispatchFrameCallbacks的Task添加到RenderThread的TaskQueue去等待调度。一旦该Task被调度,就可以在RenderThread中执行注册在PendingRegistrationFrameCallbacks列表中的IFrameCallback回调接口了。

FinalizerDaemon 析构守护线程

对于重写成员函数finailze的对象,他们被GC决定回收时,并没有马上被回收,而是被放入到一个队列中,等待FinalizerDaemon守护线程去调用他们的成员函数finalize,然后再被回收。

FinalizerWatchdogDaemon 析构监护守护线程。

用来监控FinalizerDaemon线程的执行。一旦检测哪些重写了成员函数finalize的对象在执行成员函数finalize时超出一定的时候,那么就会退出VM。

ReferenceQueueDaemon 引用队列守护线程

我们知道在创建引用对象的时候,可以关联一个队列。当被引用对象引用的对象被GC回收的时候呀,被引用对象就会呗加入到其创建时关联的队列中去,这个加入队列的操作就是由ReferenceQueueDaemon守护线程来完成的。这样应用程序就可以知道哪些被引用对象引用的对象已经被回收了。

HeapTrimmerDaemon 堆裁剪守护线程

用于执行裁剪堆的操作,也就是用来将哪些空闲的堆内存归还给系统。

HeapTaskDaemon 线程

Android每个进程都有一个HeapTaskDaemon线程,在该线程内进行GC操作。 HeapTaskDaemon 继承自 Daemon 对象。Daemon 对象实际是一个Runnale,并且内部会创建一个线程,用于执行当前这个 Daemon unnable,这个内部线程的线程名就叫 HeapTaskDaemon。

private static class HeapTaskDaemon extends Daemon {
        private static final HeapTaskDaemon INSTANCE = new HeapTaskDaemon();

        HeapTaskDaemon() {
            super("HeapTaskDaemon");
        }

        // Overrides the Daemon.interupt method which is called from Daemons.stop.
        public synchronized void interrupt(Thread thread) {
            VMRuntime.getRuntime().stopHeapTaskProcessor();
        }

        @Override public void runInternal() {
            synchronized (this) {
                if (isRunning()) {
                  // Needs to be synchronized or else we there is a race condition where we start
                  // the thread, call stopHeapTaskProcessor before we start the heap task
                  // processor, resulting in a deadlock since startHeapTaskProcessor restarts it
                  // while the other thread is waiting in Daemons.stop().
                  VMRuntime.getRuntime().startHeapTaskProcessor();
                }
            }
            // This runs tasks until we are stopped and there is no more pending task.
            VMRuntime.getRuntime().runHeapTasks();
        }
    }

Binder 线程

每个APP进程在启动之后会创建一个binder线程池,用于相应IPC客户端的请求。

例如APP与AMS等服务之间可以通过IPC双向通信,当APP作为服务端的时候,就需要通过Binder线程相应来自AMS的请求。一个Server进程中有一个最大的Binder线程数限制,默认为16个binder线程。

与系统服务通信或者自行实现多进程Binder通信大致需要注意一下几点:

  • 与系统通信的方法,建议使用try cache进行防护,防止App出现崩溃。
  • 需要注意调用频率,部分API调用频率过快响应会比较慢,从而导致主线程卡顿甚至ANR。

主线程

主线程也较UI线程。

这个线程作为Android 开发都比较熟悉。

三方线程

OkHttp相关

如果你使用OkHttp作为网络请求库,那么工程中会有以下几类线程

OkHttp Dispatcher

用于实际执行HTTP请求

  @get:Synchronized
  @get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }

OkHttp TaskRunner

4.x版本引入,用于管理和调度内部任务

  companion object {
    @JvmField
    val INSTANCE = TaskRunner(RealBackend(threadFactory("$okHttpName TaskRunner", daemon = true)))

    val logger: Logger = Logger.getLogger(TaskRunner::class.java.name)
  }

OkHttp ConnectionPool

负责清理和回收无用的连接池

  private val cleanupTask = object : Task("$okHttpName ConnectionPool") {
    override fun runOnce() = cleanup(System.nanoTime())
  }

Okio Watchdog

OkHttp中使用Watchdog来处理超时逻辑

  private class Watchdog internal constructor() : Thread("Okio Watchdog") {
    init {
      isDaemon = true
    }

    override fun run() {
      while (true) {
        try {
          var timedOut: AsyncTimeout? = null
          synchronized(AsyncTimeout::class.java) {
            timedOut = awaitTimeout()

            // The queue is completely empty. Let this thread exit and let another watchdog thread
            // get created on the next call to scheduleTimeout().
            if (timedOut === head) {
              head = null
              return
            }
          }

          // Close the timed out node, if one was found.
          timedOut?.timedOut()
        } catch (ignored: InterruptedException) {
        }
      }
    }
  }

Glide相关

如果你使用Glide加载图片,那么工程中会有以下几类线程

"glide-source-thread-x"

用于从网络、文件系统或其他数据源中加载原始图像数据

  public static GlideExecutor.Builder newSourceBuilder() {
    return new GlideExecutor.Builder(/* preventNetworkOperations= */ false)
        .setThreadCount(calculateBestThreadCount())
        .setName(DEFAULT_SOURCE_EXECUTOR_NAME);
  }

"glide-disk-cache-thread-x"

用于从缓存中读取数据

  public static GlideExecutor.Builder newDiskCacheBuilder() {
    return new GlideExecutor.Builder(/* preventNetworkOperations= */ true)
        .setThreadCount(DEFAULT_DISK_CACHE_EXECUTOR_THREADS)
        .setName(DEFAULT_DISK_CACHE_EXECUTOR_NAME);
  }

source-unlimited

  public static GlideExecutor newUnlimitedSourceExecutor() {
    return new GlideExecutor(
        new ThreadPoolExecutor(
            0,
            Integer.MAX_VALUE,
            KEEP_ALIVE_TIME_MS,
            TimeUnit.MILLISECONDS,
            new SynchronousQueue<Runnable>(),
            new DefaultThreadFactory(
                new DefaultPriorityThreadFactory(),
                DEFAULT_SOURCE_UNLIMITED_EXECUTOR_NAME,
                UncaughtThrowableStrategy.DEFAULT,
                false)));
  }

animation

用于执行动画

  public static GlideExecutor.Builder newAnimationBuilder() {
    int maximumPoolSize = calculateAnimationExecutorThreadCount();
    return new GlideExecutor.Builder(/* preventNetworkOperations= */ true)
        .setThreadCount(maximumPoolSize)
        .setName(DEFAULT_ANIMATION_EXECUTOR_NAME);
  }

ARouter相关

如果你使用ARouter作为路由

ARouter task pool No.x , thread No.X

创建位置

    public static DefaultPoolExecutor getInstance() {
        if (null == instance) {
            synchronized (DefaultPoolExecutor.class) {
                if (null == instance) {
                    instance = new DefaultPoolExecutor(
                            INIT_THREAD_COUNT,
                            MAX_THREAD_COUNT,
                            SURPLUS_THREAD_LIFE,
                            TimeUnit.SECONDS,
                            new ArrayBlockingQueue<Runnable>(64),
                            new DefaultThreadFactory());
                }
            }
        }
        return instance;
    }

另外ARouter也为开发者提供了使用自己线程池的接口:

    static synchronized void setExecutor(ThreadPoolExecutor tpe) {
        executor = tpe;
    }

三方库总结

关于三方库中的线程池这里仅列举了几个库。如果你的工程中含有大量的三方库,我详细也会存在大量的工作线程。

一些三方库会提供接口允许开发者将其替换成工程内部统一维护的线程池,这样可以做到工程中线程的收敛。笔者遇到最离谱的应该是腾讯X5浏览器,由另外一个小组的同事引入到项目中,集成后发现其引入了大量的线程(大概几十个)。

其他

我个人不太喜欢发这些纯概念的东西,更喜欢总结一些在工程中遇到的问题、对应的解决方案以及思考。后面考虑尽量少发这类笔记,多输出一些思考。