Android Handler深入学习(源码分析)

2023-05-16

目录:

 

1. 背景

在分析源码之前,先来了解一下Message、MessageQueue、Looper这几个对象。

1.1 Message 消息

定义:是线程间通讯的数据单元,包含着描述信息及任意数据对象,发送到 Handler。

在实际使用中,我们在工作线程中通过 Handler.sendMessage(Message),将携带数据的 Message 信息发送给 Handler,然后再由 Handler 处理,根据不同的信息,通知主线程作出相对应的 UI 工作。

官方文档说明:

/**
 * 定义包含着描述信息及任意数据对象的可发送到 Handler 的信息。额外包含可以不被分配的两个 int 字段及一个 Object 字段。
 * 获取 Message 对象最好的方法是调用 Message.obtain() 或 调用 Handler.obtainMessage() 方法来获取,调用该方法将从可回收的对象池中获取对象。
 */
public final class Message implements Parcelable {
    /**
     * 用户定义的消息代码,以便接收者能够识别此消息。每个 Handler 都有自己的消息代码命名空间,因此不必担心你的 Handler 与其他 Handler 起冲突。
     */
    public int what;

    /**
     * 如果只需要存储几个整数值,以 arg1 和 arg2 为变量去使用 setData(Bundle) 是较低成本的替代选择方法。
     */
    public int arg1;
    public int arg2;

    /**
     * 发送给接收者的任意对象,当使用 Messager 跨进程发送消息时,如果包含着可序列化的框架类时,Message必须是非空的。使用setData()来传输数据。
     * 注意,Android 2.2发行版之前不支持此处的Parcelable对象。
     */
    public Object obj;
    ···略···
复制代码

1.2 MessageQueue 消息队列

定义:用来存储 Message 的数据队列。

官方文档说明:

/**
 * 包含着一系列由 Looper 分发的 Message 的一个低级类。
 * Message 不是直接添加到 MessageQueue 中的,而是通过与 Looper 关联的 Handler 对象来添加的。
 * 你可以使用“loop.myQueue()”方法来获取当前线程的 MessageQueue 对象。
 */
public final class MessageQueue {
    private static final String TAG = "MessageQueue";
    ···略···
}
复制代码

1.3 Looper 消息循环器

定义:用于为线程执行消息循环的一个类。是 MessageQueue 与 Handler 之间的通讯媒介。

官方文档说明:

/**
  * 用于为线程执行消息循环的一个类。
  * 线程默认情况下没有与之关联的消息循环;要创建一个,在将要运行循环的线程中调用 Looper.prepare(),然后调用 Looper.loop() 让它处理消息,直到循环停止。
  * 与消息循环的大多数交互都是通过 Handler 类进行的。
  * 这是一个典型的 Looper 线程实现的例子,使用 Looper.prepare() 和 Looper.loop() 方法创建一个 Handler 对象与 Looper 进行通信。
  * <pre>
  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // 在这里处理传入的消息
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }</pre>
  */
public final class Looper {
     /*
     * API 实现注意事项:
     *
     * 该类包含设置和管理基于 MessageQueue 的事件循环所需的代码。
     * 影响队列状态的api应该在 MessageQueue 或 Handler 上定义,而不是在 Looper 本身上定义。
     * 例如,在队列上定义空闲处理程序和同步屏障,而在 Looper 上定义线程准备、循环和退出。
     */

    private static final String TAG = "Looper";
    ···略···
}
复制代码

1.4 Message、MessageQueue、Looper之间的关系

一句话概括: 存储在 MessageQueue 中的 Message 被 Looper 循环分发到指定的 Handler 中进行处理。

2. Handler 通信机制原理

关于Handler的通信机制工作原理,请看 Carson_Ho大佬的 Android Handler:图文解析 Handler通信机制 的工作原理 写的超棒,图文解析,一目了然。引用其中一Handler通信流程示意图,如下:

 

Thread、Handler、Looper三者之间的数量对应关系;

  • 一个 Thread 可以有多个 Handler。
  • 一个 Handler 只能关联一个 Looper 对象。
  • 反之,一个 Looper 可以被多个 Handler 所关联。

3. 源码分析

针对 Handler.sendMessage(msg) 方法展开分析,分为3步:

  1. 创建 Handler 对象
  2. 创建 Message 对象
  3. 分发 Message

3.1 创建 Handler 对象

创建一个 Handler 对象,也就是实例化一个 Handler 对象,在实际的使用中,我也发现有存在三种情况,分别是:

  1. 在主线程上新建一个 Handler 对象,提供应用程序的 主Looper 对象与之绑定关联。
  2. 创建一个继承于 Handler类的静态内部类,防止内存泄漏。
  3. 在子线程上创建 Handler 对象。

我将一一展开分析:

3.1.1 在主线程上新建一个 Handler 对象,提供应用程序的 主Looper 对象与之绑定关联

如下,我们在主线程中新建 Handler 对象,

//在主线程中新建 Handler 对象,并提供应用程序的 主Looper
mHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        //消息处理

    }
};
复制代码

通过上述代码,我们创建了一个 Handler 对象,并关联应用程序的 主Looper 对象。 这个时候我们看一下 Handler类 的构造函数,如下所示:

/**
 * Use the provided {@link Looper} instead of the default one.
 * 使用提供的 Looper对象, 而不是使用默认的Looper对象
 * @param Looper 不能为 null
 */
public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}
复制代码

可以发现,我们提供给 Handler 的 Looper 是通过 Looper.getMainLooper() 获取的。往下看:

/**
 * 返回应用程序的 主Looper, 它存在于应用程序的主线程中。
 */
public static Looper getMainLooper() {
    synchronized (Looper.class) {
    	//返回sMainLooper, 下一步看看sMainLooper是怎么创建 Looper 对象的。
        return sMainLooper; 
    }
}

如上所诉,getMainLooper() 返回 全局变量 sMainLooper 对象,所以我们需要看 sMainLooper 是什么时候被赋值的,如下所示,在 prepareMainLooper() 方法中,sMainLooper 被 myLooper 赋值:
/**
 * 将当前线程初始化为 Looper,并将其标记为应用程序的 主Looper
 * 应用程序的 主Looper 是由 Android环境 创建的,所以永远不需要自己调用这个函数
 */
public static void prepareMainLooper() {
    //调用 prepare() 方法,如果当前线程没有 Looper 对象,就为之新创建一个 Looper 对象。
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //返回当前线程所关联的 Looper 对象。
        sMainLooper = myLooper();
    }
}

上一步 prepareMainLooper 中:调用了 prepare(false) 来创建 Looper 对象,具体如下:
private static void prepare(boolean quitAllowed) {
    //sThreadLocal.get() 返回 Looper || null,如果返回 Looper 对象,即代表已经为该线程创建了 Looper 对象,
    //从而抛出异常,提示:“每个线程只能创建一个Looper”
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //否则,调用 new Looper()方法新建一个 Looper 对象,并放入 sThreadLocal 变量中。
    sThreadLocal.set(new Looper(quitAllowed)); //quitAllowed = false
}

上一步 prepare() 中:调用了 new Looper() 方法来新创建 Looper 对象,具体如下:
private Looper(boolean quitAllowed) {
    //新建一个 MessageQueue 
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}


在 prepareMainLooper() 方法中,如果 sMainLooper 为 null,就赋值 sMainLooper = myLooper(); , myLooper() 方法如下:
/**
 * 返回当前线程所关联的 Looper 对象,如果所调用的线程没有关联的 Looper 对象,就返回 null
 */
public static @Nullable Looper myLooper() {
    //获取的 Looper 对象,是在 prepare() 方法中通过 sThreadLocal.set(new Looper(quitAllowed)); 所创建的
    return sThreadLocal.get();
}
复制代码

上述代码中,getMainLooper() 方法中的 sMainLooper 是通过 prepareMainLooper() 进行赋值的,具体代码分析如上所述,其流程图如下所示:

 

3.1.2 创建一个继承于 Handler类的静态内部类,防止内存泄漏

上述创建 Handler 对象的方法是 new Handler(Looper.getMainLooper()) ,通过 Looper.getMainLooper() 方法获取 Looper 对象,然后传入到 Handler 的构造函数中,构成绑定关联关系。 但在实际开发中,我们通常将 Handler 写成静态内部类的形式,如下:

//创建 Handler 对象
mHandler = new MyHandler(this);

/**
 * 将 Handler 写成静态内部类,防止内存泄露
 */
public static class MyHandler extends Handler {
    WeakReference<HandlerAddThreadActivity> weakReference;

    public MyHandler(HandlerAddThreadActivity activity) {
        weakReference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        //处理收到的信息
    }
}
复制代码

思考: 疑问?根据上面的代码,我们没有传递 Looper 对象给 Handler 去绑定关联, 那 Handler 为何又能正常工作? 带着疑惑上路:

首先看一下 Handler类 的默认构造函数

/**
 * 默认构造函数将这个处理程序与当前线程的{@link Looper}关联起来。
 * 如果此线程没有looper,则此处理程序将无法接收消息,因此将引发异常。
 */
public Handler() {
    this(null, false);
}

public Handler(@Nullable Callback callback, boolean async) {
	···略···
    //返回与当前线程关联的Looper对象。如果调用线程没有关联的 Looper 对象,则返回null
    mLooper = Looper.myLooper();
    //如果 mLooper 为null,则抛出异常。
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    //获取该 Looper 的 MessageQueue
    mQueue = mLooper.mQueue;

    //callback = null && async = false
    mCallback = callback;
    mAsynchronous = async;
}

看着不明显,mLooper 是通过 Looper.myLooper() 方法获得的.
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

但这个时候我想起了 prepareMainLooper() 方法。
/**
 * 将当前线程初始化为 Looper,并将其标记为应用程序的 主Looper
 * 应用程序的 主Looper 是由 Android环境 创建的,所以永远不需要自己调用这个函数
 */
public static void prepareMainLooper() {
    //调用 prepare() 方法,如果当前线程没有 Looper 对象,就为之新创建一个 Looper 对象。
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //返回当前线程所关联的 Looper 对象。
        sMainLooper = myLooper();
    }
}

疑问解除,无论我们有没有手动添加 Looper 给 Handler, Android环境都会为我们自动创建一个主线程的主Looper对象。
复制代码

总结: 我们创建的 MyHandler 静态内部类,其实也是在主线程上创建的, Android环境会为我们自动创建一个应用程序的 主Looper 对象,主线程与 主Looper 绑定关联。

3.1.3 在子线程上创建 Handler 对象

ok, 解除了刚刚的疑问,我又问自己,那在子线程创建 Handler 对象呢? 当然,如果你在子线程中新创建一个 Handler 对象,创建的方法为:

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
    	//为当前线程创建一个 Looper 
        Looper.prepare();
        
		//在子线程中创建 Handler 对象
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // 在这里处理传入的消息
            }
        };
		//开始消息循环
        Looper.loop();
    }
}
复制代码

在子线程上,我们通过调用 prepare() 来为当前线程创建一个 Looper 对象

/** 将当前线程初始化为 Looper 对象,在实际开始循环之前创建并引用该 Looper 对象。
 * 请确保在调用此方法后调用 loop() 方法,并通过调用 quit() 结束它
 */
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));
}
复制代码

并且在 Handler 与 Looper 构成绑定关联关系后,通过调用 Looper.loop() 方法开始消息循环。

3.2 创建 Message消息对象

关于创建 Message 对象有两种创建方法,分别是:

  1. Message myMessage = new Message();
  2. Message myMessage = Message.obtain()
/**
 * 从全局池返回一个新的消息实例。允许我们在许多情况下避免分配新对象。
 */
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}
复制代码

本文不过多展示。

3.3 发送消息

在工作线程中通过调用 mHandler.sendMessage(myMessage); 方法来发送消息,这一步是消息入消息队列操作,即将 Message 入 MessageQueue。

/**
 * 在当前时间之前所有挂起的消息之后, 将消息推到消息队列的末尾。它将该线程所关联的 Handler 的 handleMessage 方法中被接收消息。
 * @return 如果信息成功加入到消息队列中,返回 true,反之,入队列失败则返回 false, 失败的原因通常是因为处理消息队列的“looper”正在退出。
 */
public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

/**
 * 延时 delayMillis 将消息入消息队列,它将该线程所关联的 Handler 的 handleMessage 方法中被接收消息。
 * @return 如果信息成功加入到消息队列中,返回 true,反之,入队列失败则返回 false, 失败的原因通常是因为处理消息队列的 Looper 正在退出。
 *         注意,true的结果并不意味着将处理该消息——如果 Looper 在消息的交付时间之前退出,则该消息将被删除。
 */
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        //默认 delayMillis = 0
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

/**
 * 在指定时间入队列,深度睡眠的时间会增加额外的执行延迟。它将该线程所关联的 Handler 的 handleMessage 方法中被接收消息。
 * @param uptimeMillis 发送消息的绝对时间,使用{SystemClock # uptimeMillis}获取。       
 * @return 如果信息成功加入到消息队列中,返回 true,反之,入队列失败则返回 false, 失败的原因通常是因为处理消息队列的 Looper 正在退出。
 *         注意,true的结果并不意味着将处理该消息——如果 Looper 在消息的交付时间之前退出,则该消息将被删除。
 */
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    //获取当前 Looper 对象的消息队列
    MessageQueue queue = mQueue;
    //如果当前 Looper 对象没有消息队列,则抛出异常,返回 false, 代表消息入队列失败
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

//将消息放入消息队列
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

根据消息的处理时间入消息队列
boolean enqueueMessage(Message msg, long when) {
    //获得当前的 Handler 对象,如果为 null, 则抛出异常
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //如果消息已经被使用,即已经入队列,则抛出异常
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        //如果 Looper 已经退出,抛出异常,入队列失败,返回false
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        //如果队列里没有消息,或者新消息的处理时间排在最前,即作为消息队列新的队头插入消息队列
        if (p == null || when == 0 || when < p.when) {
            //新的队列头部,如果队列是 blocked 的状态则需要唤醒该事件队列。
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            //插入处理时间插入到队列的中间。通常我们不需要唤醒事件队列,除非是队列头部的屏障,并且消息是队列中最早的异步消息
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
复制代码

3.4 消息循环,派发消息

消息入 MessageQueue 队列后,由 Looper 循环消息队列,然后派发消息,如下所示:

/**
 * 在线程中运行消息队列,并确保调用 quit() 方法来结束消息循环
 */
public static void loop() {
    //获取当前线程关联的 Looper 对象,如果该线程没有关联的 Looper 对象,则抛出异常
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //获取该 Looper 对象的消息队列
    final MessageQueue queue = me.mQueue;

    //循环消息队列
    for (;;) {
        //取出消息队列中的消息对象,如果消息队列为空,则 block 该线程。
        Message msg = queue.next(); // might block
        if (msg == null) {
            //没有消息则表明消息队列正在退出,解除消息循环
            return;
        }

        try {
            //消息对应的 Handler 分发消息
            msg.target.dispatchMessage(msg);  
        } 
      
        //回收正在使用的消息
        msg.recycleUnchecked();
    }
}

/**
 * 处理系统消息
 */
public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //如果 Message.callback 为空,执行 handleMessage(msg),即回调复写 handleMessage(msg) 方法。
        handleMessage(msg);
    }
}

/**
 * 回收可能正在使用的消息,由 MessageQueue 和 Looper 在处理排队消息时在内部使用。
 */
void recycleUnchecked() {
    //将消息标记为正在使用,同时将其保留在回收的对象池中。并清楚其余所有细节
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;
    ···
}
复制代码

Looper 消息循环,发送消息给指定的 Handler中,触发我们覆写的 handleMessage(Message) 方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Handler深入学习(源码分析) 的相关文章

随机推荐

  • 解决从PDF复制文字后乱码问题

    背景 需要从PDF复制文字出来做笔记 xff0c 可是谁知道PDF通过adobe打开后复制出来后是乱码 xff0c 如下图所示 xff1a 解决 尝试过安装字体 xff0c 可惜没卵用 方法1 CAJViewer打开 用该软件打开后复制 x
  • Android Studio不能启动模拟器原因探秘 The emulator process for AVD xxx has terminated

    文章背景 在Android Studio中创建模拟器后 xff0c 启动模拟器时弹出提示 The emulator process for AVD Pixel 2 API 31 has terminated xff0c 但是并没有显示具体错
  • AndroidStudio-快捷键-格式化代码

    Windows Ctrl 43 Alt 43 L Ctrl 43 Shift 43 F 无效 亲测 和qq热键冲突 我的解决方式是把qq除捕获屏幕外的热键全部设置为无 Mac OPTION 43 CMD 43 L
  • 安装APK时报错:Failure [INSTALL_FAILED_TEST_ONLY: installPackageLI]

    使用AS自动运行时会在app build outputs apk debug文件夹下自动生成测试APK xff1a app debug apk xff0c 用命令adb install app debug apk时报错 xff1a Fail
  • 计算机网络-划分子网 四大类必会题型

    必记知识点 A类 xff1a 0 126 xff0c 默认子网掩码 xff1a 255 0 0 0 B类 xff1a 128 191 xff0c 默认子网掩码 xff1a 255 255 0 0 C类 xff1a 192 223 xff0c
  • C语言-解释复杂声明

    基本术语 xff1a 声明符 xff1a int a 就是一个声明符 标识符 定义的变量名字 xff0c 如 xff1a int a xff0c 那么a就是一个标识符 1 两个原则 xff1a 始终从内往外读声明符 xff0c 括号优先级高
  • Android EditText 不自动获取焦点

    在Activity上面显示一个EditText xff0c 进入该页面时想阻止这个EditText自动获取焦点而自动调起键盘 思路如下 xff1a 可以采取让父级控件来获取焦点就可以了 例如说在这个EditText外面包一个LinearLa
  • Wireshark中无法显示网卡列表的解决方法

    1 问题描述 打开Wireshark时 xff0c 都会有一个网卡列表 xff0c 在该列表中显示了电脑的所有网卡 但是 xff0c 有时打开Wireshark时 xff0c 该网卡列表不显示 xff0c 如图1所示 图1 不显示网卡列表
  • Android事件分发与事件处理源码分析

    一 前言 Android中事件分发与事件处理是一个老生常谈的问题了 xff0c 自己在网上也看过很多文章 xff0c 但是大部分人都只是抛出一些结论或是一些流程图或者干脆就是一些运行demo的截图等 xff0c 对于这些结论和流程图是怎么来
  • 解决小米pad USB安装apk时AS报错:INSTALL_FAILED_USER_RESTRICTED

    设置 更多设置 开发者选项 gt 取消启用MIUI优化 用USB接口 xff0c 选择传输文件 xff08 MTP xff09
  • git 通过 comment 关键字查找 commit

    git log grep 61 word 比如 xff1a git log grep 61 同步
  • git合并多个 Commit

    在使用 Git 作为版本控制的时候 xff0c 我们可能会由于各种各样的原因提交了许多临时的 commit xff0c 而这些 commit 拼接起来才是完整的任务 那么我们为了避免太多的 commit 而造成版本控制的混乱 xff0c 通
  • git查看某次提交的文件列表

    Git操作常用 xff1a 一 查看某次提交的文件列表 首先使用git log查看历史提交记录 xff1a 复制你想要查看记录的某个提交代号9ddc9dca00b 使用命令git show 9ddc9dca00b stat查看详细文件列表
  • Android SoundPool插入耳机后依然有外放声音

    使用soundPool播放声音 xff0c 当手机已经接通耳机时 xff0c 还会有外放声音 xff0c 是因为在初始化soundpool是用的流类型 xff08 streamType xff09 导致的 xff0c 有些流类型系统是一定会
  • 一文搞懂Android JetPack组件原理之Lifecycle、LiveData、ViewModel与源码分析技巧

    Lifecycle LiveData和ViewModel作为AAC架构的核心 xff0c 常常被用在Android业务架构中 在京东商城Android应用中 xff0c 为了事件传递等个性化需求 xff0c 比如ViewModel间通信 V
  • Linux下安装npm

    1 root 登录linux 2 没有目录就自己创建一个 cd usr local node 3 下载安装包 wget https npm taobao org mirrors node v4 4 7 node v4 4 7 linux x
  • AudioService之音频输出通道切换

    前言 xff1a 音频输出的方式有很多种 xff0c 外放即扬声器 xff08 Speaker xff09 听筒 xff08 Telephone Receiver xff09 有线耳机 xff08 WiredHeadset xff09 蓝牙
  • Android音频——音量调节

    一 音量相关概念 1 相关术语解释 track volume 单个App设置音量时设置的是这个 xff0c 它只影响本App的音量 stream volume xff1a 设置某一stream的音量 xff0c Android系统中支持10
  • 电话状态权限及IMEI获取流程源码分析

    IMEI是设备唯一性的一个重要指标 xff0c 这篇文章对IMEI获取做一些分析 xff0c 以达到以下两个目的 xff1a 1 梳理Android源码中获取IMEI流程 2 理解获取IMEI时 xff0c 源码中权限调用流程 备注 xff
  • Android Handler深入学习(源码分析)

    目录 xff1a 1 背景 在分析源码之前 xff0c 先来了解一下Message MessageQueue Looper这几个对象 1 1 Message 消息 定义 xff1a 是线程间通讯的数据单元 xff0c 包含着描述信息及任意数