精华内容
下载资源
问答
  • 源码分析
    千次阅读
    更多相关内容
  • ANDROID源码分析实录

    热门讨论 2016-01-11 09:52:11
    《Android源码分析实录》共分为15章,主要内容包括走进Android世界、硬件抽象层详解、分析JNI(Java本地接口)层、Android内存系统分析、Andmid虚拟机系统详解、IPC通信机制详解、Zygote进程/System进程和应用程序...
  • 《MapReduce2.0源码分析与编程实战》比较系统地介绍了新一代MapReduce2.0的理论体系、架构和程序设计方法。全书分为10章,系统地介绍了HDFS存储系统,Hadoop的文件I/O系统,MapReduce2.0的框架结构和源码分析,...
  • Hadoop源码分析(完整版)

    热门讨论 2015-06-06 18:53:50
    Hadoop源码分析(完整版),详细分析了Hadoop源码程序,为学习Hadoop的人提供很好的入门指导
  • linux内核源码分析系列文章汇总

    千次阅读 2022-04-09 11:05:02
    七、内存专题 1、linux内核源码分析之内存概述 2、linux内核源码分析之内存 3、linux内核源码分析之虚拟内存映射 4、linux内核源码分析之物理内存组织结构 5、linux内核源码分析之伙伴系统(一) 6、linux内核源码...

    目录

    一、进程专题

    二、驱动专题

    三、中断专题

    四、文件系统专题

    五、性能优化专题

    六、网络专题

    七、内存专题


            内核源码分析,分为7大章节,进程、驱动、中断、文件系统、性能优化、网络和内存。其中网络和内存正在更新中,其它的文章已经分析过一轮。接下来优先更新网络,性能优化与内存相关专题的文章。有喜欢内核分析的小伙伴一起交流。

    内核源码版本:linux-5.6.18

    一、进程专题

    1、linux内核源码分析之进程创建

    2、linux内核源码分析之进程调度

    3、linux内核源码分析之实时调度

    4、linux内核源码分析之CFS调度


    二、驱动专题

    1、linux内核源码分析之设备驱动

    2、linux内核源码分析之设备驱动(platform)

    3、linux设备驱动文章汇总


    三、中断专题

    1、linux内核源码分析之中断tasklet

    2、linux内核源码分析中断work_queue

    3、linux内核源码分析之软中断


    四、文件系统专题

    1、linux内核源码分析之proc文件系统(一)

    2、linux内核源码分析之proc文件系统(二)

    3、linux内核源码分析之proc文件系统(三)

    4、linux内核源码分析之sysfs文件系统(一)

    5、linux内核源码分析之sysfs文件系统(二)

    6、linux内核源码分析之sysfs文件系统(三)

    7、linux内核源码分析之虚拟文件系统VFS(一)

    8、linux内核源码分析之虚拟文件系统VFS(二)


    五、性能优化专题

    1、linux性能优化perf

    2、linux内核源码分析之性能优化

    3、linux内核源码分析之RCU

    4、linux内核源码分析之per-CPU

    5、linux内核源码分析之内存屏障和RCU机制

    6、(一文了解)linux性能分析之CPU篇


    六、网络专题

    1、linux内核源码分析之网络数据收发流程

    2、linux内核源码分析之网络协议栈的演变

    3、linux内核网络收包过程—网络子系统与协议栈初始化

    4、linux内核网络收包过程—硬中断与软中断

    5、linux内核网络收包过程—IP协议处理

    6、linux内核网络收包过程—UDP协议处理

    7、linux内核网络收包过程—TCP协议处理

    8、linux 内核中accept实现

    9、linux 内核中epoll实现

    10、linux 内核网络发送技术栈(一)

    11、linux 内核网络发送技术栈(二)

    12、linux 内核中Netlink

    13、一条TCP连接时占用内存空间多少?

    14、TCP建立连接过程(深入源码理解3次握手)

    15、TCP异常连接

    16、tcpdump抓包实现过程

    待更新...

    七、内存专题

    1、linux内核源码分析之内存概述

    2、linux内核源码分析之内存

    3、linux内核源码分析之虚拟内存映射

    4、linux内核源码分析之物理内存组织结构

    5、linux内核源码分析之伙伴系统(一)

    6、linux内核源码分析之伙伴系统(二)

    7、linux内核源码分析之伙伴系统(三)

    8、linux内核源码分析之伙伴系统(四)

    9、linux内核源码分析之slab(一)

    10、linux内核源码分析之slab(二)

    11、linux内核源码分析之slab(三)

    12、linux内核源码分析之slab(四)

    待更新...


    展开全文
  • Hadoop源码分析 完整版 共55章

    千次下载 热门讨论 2011-07-26 22:41:27
    caibinbupt的Hadoop源码分析完整版,包括 HDFS 和 MapReduce。 HDFS: 41章 MapReduce: 14章
  • scratch2.0的源码分析

    热门讨论 2015-10-31 11:12:34
    这是一篇自己关于scratch2.0源码的一些见解,由于自己知识有限,不能保证十分正确,大家可以且看且怀疑
  • 2 源码分析篇主要是对RocketMQ4.4.0及其后续版本中的新加入的功能进行源码级别的剖析,主要包括ACL、消息轨迹、多副本。 3 认知篇主要展示笔者阅读源码的经验分享,以及如何以布道师的身份参与Apache顶级开源项目的...
  • lvs源码分析 ipvs源码分析

    热门讨论 2012-07-28 22:45:00
    lvs源码分析,介绍的很详细。均衡调度算法,IPVS 的连接管理,IPVS 的协议管理,IPVS 的数据包发送,IPVS 的应用管理
  • Docker源码分析

    千次下载 热门讨论 2015-07-29 15:09:57
    国内一部Docker源码分析著作;从源码角度全面解析Docker设计与实现;填补Docker理论与实践之间的鸿沟
  • Handler核心源码分析

    千次阅读 2022-03-05 03:37:43
    目录 Handler的使用 Handler初始化 发送消息 处理消息 ...而在阅读源码的过程中,我一直秉承着郭霖大神的那句话“抽丝剥茧、点到即止”,我们没有必要完全深入每一行代码,通常我们可能只需要知道这...

    目录

    Handler的使用

    Handler初始化

    发送消息

    处理消息

    MessageQueue的阻塞和唤醒

    阻塞

    唤醒

    Handler对我们开发者的启发

    亮点一

    亮点二

    Looper什么时候推出

    Handler常见面试题


    前言

    对于一名开发者来说,阅读源码是一项必修的课程。在学习源码的过程中,我们可以了解到设计模式与源代码开发者的开发习惯。而在阅读源码的过程中,我一直秉承着郭霖大神的那句话“抽丝剥茧、点到即止”,我们没有必要完全深入每一行代码,通常我们可能只需要知道这一行代码的作用就行了。

    大家有没有发现,我们在Android开发过程中,很少遇到过多线程并发的问题,这个就得益于Android为我们提供的线程间通信工具 Handler了,所以我们要了解它是怎么实现跨线程通信的。

    Handler的使用

    我们首先知道Handler是怎么用的,再去剖析其核心源码。

    import androidx.annotation.NonNull;
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.annotation.SuppressLint;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    import android.widget.TextView;
    
    public class MainActivity extends AppCompatActivity {
    	private static final String TAG = "MainActivity";
    	private TextView mTvMain;
    	
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		initView();
    	}
    	
    	@Override
    	protected void onStart() {
    		super.onStart();
    		new Thread(() -> {
    			try {
    				// 模拟耗时操作
    				Thread.sleep(5000);
    				// 获取 Message 实例对象
    				Message msg = Message.obtain();
    				// 设置 Message 对象的识别内容
    				msg.what = Constants.MSG_UPDATE_TEXT;
    				// 通过 Handler 把消息发送出去
    				handler.sendMessage(msg);
    			} catch (InterruptedException e) {
    				Log.e(TAG, "onStart: InterruptedException");
    			}
    		}).start();
    	}
    	
    	private void initView() {
    		mTvMain = findViewById(R.id.tv_main);
    	}
    	
    	@SuppressLint("HandlerLeak")
    	private Handler handler = new Handler() {
    		@Override
    		public void handleMessage(@NonNull Message msg) {
    			switch (msg.what) {
    				case Constants.MSG_UPDATE_TEXT:
    					mTvMain.setText("已完成更新操作");
    			}
    		}
    	};
    }

    接下来我们就按照上面代码案例去剖析Handler源码,顺序依次是Handler初始化、

    Handler初始化

     最终会调用如下方法:

    	/*
    	 * 将此标志设置为 true 以检测扩展此 Handler 类的匿名类、本地类或成员类。 这种类可能会造成泄漏。
    	 */
    	private static final boolean FIND_POTENTIAL_LEAKS = false;
    	
    	// 如果我们没有调用Looper.prepare(),sThreadLocal.get() 将返回null
    	// @UnsupportedAppUsage 不让我们开发者使用
    	static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    	final Looper mLooper;
    	final MessageQueue mQueue;
    	// @UnsupportedAppUsage 不让我们开发者使用
    	final Handler.Callback mCallback;
    	final boolean mAsynchronous;
    	public Handler(@Nullable Handler.Callback callback, boolean async) {
    		if (FIND_POTENTIAL_LEAKS) {
    			final Class<? extends Handler> klass = getClass();
    			// 如果我们创建的Handler类对象是匿名类,或者是成员类(内部类)、或者局部类(方法中创建的类)并且该类不是静态的
    			// 就有内存泄漏的风险。
    			if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
    					(klass.getModifiers() & Modifier.STATIC) == 0) {
    				Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
    						klass.getCanonicalName());
    			}
    		}
    		
    		mLooper = Looper.myLooper();
    		if (mLooper == null) {
    			throw new RuntimeException(
    					"Can't create handler inside thread " + Thread.currentThread()
    							+ " that has not called Looper.prepare()");
    		}
    		mQueue = mLooper.mQueue;
    		mCallback = callback;
    		mAsynchronous = async;
    	}
    	
    	
    	/**
    	 * 返回与当前线程关联的 Looper 对象。 如果调用线程未与 Looper 关联,则返回 null。
    	 *
    	 * @return Looper对象
    	 */
    	public static @android.annotation.Nullable
    	Looper myLooper() {
    		return sThreadLocal.get();
    	}

    这里我们重点关注一下 mLooper = Looper.myLooper(); 这行代码,myLooper的实现是:sThreadLocal.get();这里我们就要说说 ThreadLocal了,可参考我的另一篇博文:并发编程基础(二)—— ThreadLocal及CAS基本原理剖析

    其实说白了,ThreadLocal 正如其名,它就是一个线程本地副本,我们的线程内部会有一个ThreadLocal.ThreadLocalMap的成员:

    再来看看具体的get方法:

    	/**
    	 * 返回此线程中 ThreadLocalMap成员以ThreadLocal为key对应的值(Object类型),如果ThreadLocalMap成员为null,
    	 * 则首先将其初始化,调用 {@link #initialValue} 。
    	 *
    	 * @return 返回当前线程的中 ThreadLocal 对应的值
    	 */
    	public T get() {
    		// 获取当前线程
    		Thread t = Thread.currentThread();
    		// 获取当前线程的 ThreadLocal.ThreadLocalMap 成员
    		ThreadLocalMap map = getMap(t);
    		// 如果map不为空
    		if (map != null) {
    			// 通过 ThreadLocal 获取Entry
    			ThreadLocalMap.Entry e = map.getEntry(this);// 这里的this就是当前线程副本 ThreadLocal 的实例
    			// 如果 Entry 不为空,返回它的 value 属性的值
    			if (e != null) {
    				@SuppressWarnings("unchecked")
    				T result = (T) e.value;
    				return result;
    			}
    		}
    		//否则设置初始值
    		return setInitialValue();
    	}

    上面的代码包含了 ThreadLocalMap.Entry:

    我们发现 Entry 的构造函数包含 ThreadLocal 和 Object,而它又是 ThreadLocalMap  的内部类,那我们就可以理解为 ThreadLocalMap 是以 ThreadLocal 为 key,任意对象为 value 的键值对结构(Map结构),即是一一对应的。

    既然有 get 方法,那就有 set 方法了,我们发现在 Looper.prepare() 调用了 set:

    	static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
        private static void prepare(boolean quitAllowed) {
    		// 如果已经给当前线程的 ThreadLocal 设置过 一个Looper,则抛出异常,这就保证了 一个 ThreadLocal 只对应一个 Looper
    		if (sThreadLocal.get() != null) {
    			throw new RuntimeException("Only one Looper may be created per thread");
    		}
    		// 否则 new 一个 Looper对象,并设置给当前线程的 ThreadLocal
    		sThreadLocal.set(new Looper(quitAllowed));
    	}
    
    	// Looper 的构造方法
    	private Looper(boolean quitAllowed) {
    		mQueue = new MessageQueue(quitAllowed);
    		mThread = Thread.currentThread();
    	}

    通过阅读如上代码,我们可以得出如下结论:

    【1】Handler机制中,Thread 和 Looper是一一对应的:

    要实现这个,我们就要保证我当前线程中只有唯一一个ThreadLocal对象可以绑定  Looper对象,且只能绑定一次。

    Looper的构造方法是私有的,我们开发人员无法通过其他 ThreadLocal对象绑定 Looper,也就说说我们只能通过在某一个线程中调用 Looper.prepare实现ThreadLocal 和Looper 绑定,而 sThreadLocal 又是static final的,那也就是说一旦线程调用了 Looper .prepre 这个静态方法, sThreadLocal 就会被赋值,并且一旦赋值就不可更改,也就是说所有线程只能通过这一个 sThreadLocal 与Looper对象绑定。

    而 prepare方法中,又通过判断 sThreadLocal.get() 是否为 null 的逻辑,保证了每一个线程的 sThreadLocal 只会调用一次 set 方法,即只能绑定一次 Looper。

    【2】MessageQueue和 Looper一一对应

    我们在 Android 源码中全局搜索 MessageQueue 的构造方法,发现只有在 Looper 的构造方法中调用了 new MessageQueue(quitAllowed),并且 MessageQueue 的构造方法是包管理权限,也就是说我们普通开发者是不能调用的,那也就说明了通过 Looper 构造方法唯一创建 MessageQueue 对象,实现了 Looper 和 MessageQueue 的一一对应。

    综合以上两个结论,我们得出 Handler机制中,Thread、ThreadLocal、Looper、MessageQueue这四者是一一对应。

    发送消息

    Handler的主要方法

    调用流程:

    Handler.sendMessage() --> Handler.sendMessageDelayed()--> Handler.sendMessageAtTime()-->  Handler.enqueueMessage()--> MessageQueue.enqueueMessage()

    可以看到最终会调用 MessageQueue.enqueueMessage() 将 Message 插入队列。

    Handler工作流程图

    那么接下来我们剖析enquque方法:

    	Message next; // Message的成员
    	
    	Message mMessages;// MessageQueue 的成员,可以看作是队列的队头
    	boolean enqueueMessage(Message msg, long when) {
            // 省略不是很重要的代码
    
            // 加锁保证线程安全,防止多个线程同时给MessageQueue中插入消息
    		synchronized (this) {
                // 省略不是很重要的代码
    			
    			// 给要插入消息队列(MessageQueue)中的消息指定延迟时间,也就是在多久之后处理此消息
    			msg.when = when;
    			// 把消息队列的队头赋值给 p
    			Message p = mMessages;
    			// 是否唤醒消息队列
    			boolean needWake;
    			// 最开始,消息队列肯定是空的。那如果当前消息队列中没有消息,或者我们插入的消息的等待时间是0,那我们就把消息插入,
    			// 并让其指向null;
    			// 或者消息队列中有一个即将处理的消息,而我们插入的消息等待时间小于即将要处理的消息,那就把我们插入的消息放在其前面。
    			if (p == null || when == 0 || when < p.when) {
    				// 插入的消息的下一个消息指向p,p可能为null
    				msg.next = p;
    				// 把我们要插入的消息赋值给消息队列的队头,实现插入队列操作
    				mMessages = msg;
    				// 当消息队列中没有消息时,就会阻塞,此时 mBlocked为 true
    				needWake = mBlocked;
    			}
    			// 这种情况是消息队列中有 N多个消息了
    			else {
                    // 插入队列中间。 通常我们不必唤醒消息队列,除非队列头部有屏障,并且消息是队列中最早的异步消息。
    				needWake = mBlocked && p.target == null && msg.isAsynchronous();
    				
    				Message prev;
    				// 通过死循环,不停地比较我们要插入的消息与队列中已有的消息
    				for (;;) {
    					// 当前消息队列中用于作比较的消息作为前一个消息
    					prev = p;
    					// 然后让 prev指向 p(p指向p.next)
    					p = p.next;
    					// 从消息队列中的第一个消息开始遍历,直到消息队列的末尾即null值,方可跳出循环;
    					// 或者要插入的消息等待时间小于当前我们正在做比较的消息,此时也跳出循环。
    					if (p == null || when < p.when) {
    						break;
    					}
    				}
    				// 插入消息
    				msg.next = p; // invariant: p == prev.next
    				prev.next = msg;
    			}
    		}
    		return true;
    	}

    Message入队示意图

    处理消息

    入队我们讲完了,下来该讲出队了。出队我们是通过 Looper.loop()实现:

    	/**
    	 * 在当前线程中运行消息队列。 请务必调用 {@link #quit()} 来结束循环。以下代码省略不必要(看不懂)的代码进行解析
    	 */
    	public static void loop() {
    		// 获取当前线程的 Looper 对象
    		final Looper me = myLooper();
    		if (me == null) {
    			// 这个异常在我们初学者会经常遇到,因为初学者总是忘记在子线程调用 Looper.prepare()
    			throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    		}
    		// 获取当前线程的 MessageQueue 对象
    		final MessageQueue queue = me.mQueue;
    		
    		for (; ; ) {
    			// 取出消息 --- 出队
    			Message msg = queue.next(); // 可能会阻塞,当MessageQueue没有消息是,会调用nativePollOnce(ptr, -1);一直挂起
    			if (msg == null) {
    				// 没有消息表示消息队列正在推出
    				return;
    			}
    			
    			try {
    				// target是Message中的 Handler 成员
    				msg.target.dispatchMessage(msg);
    			} catch (Exception exception) { }
    			finally { }
    			
    			// 回收可能正在使用的消息
    			msg.recycleUnchecked();
    		}
    	}

    loop方法中包含一个死循环,通过 MessageQueue.next() 不停地取出消息:

    	/**
    	 * 消息出队,同样省略 N多行不是很重要的(看不懂的)代码
    	 *
    	 * 尝试检索下一条消息, 如果找到返回。
    	 *
    	 * @return 返回即将要被处理的消息
    	 */
    	private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    	
    	Message next() {
    		int pendingIdleHandlerCount = -1; // -1 only during first iteration
    		int nextPollTimeoutMillis = 0;
    		for (;;) {
    			// 调用 native 方法睡眠 nextPollTimeoutMillis 这么多毫秒,如果该值为-1,则会无限等待
    			nativePollOnce(ptr, nextPollTimeoutMillis);
    			synchronized (this) {
    				// 自启动以来的非睡眠正常运行时间毫秒数。
    				final long now = SystemClock.uptimeMillis();
    				Message prevMsg = null;
    				Message msg = mMessages;// mMessages可以看作是消息队列的队头
    				if (msg != null && msg.target == null) {
    					// 被一道屏障挡住了,查找队列中的下一条异步消息。
    					do {
    						prevMsg = msg;
    						msg = msg.next;
    					} while (msg != null && !msg.isAsynchronous());
    				}
    				if (msg != null) {
    					if (now < msg.when) {
    						// 下一条消息尚未准备好,设置延迟时间以在它准备好时唤醒。
    						nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
    					} else {
    						// 下一条消息已就绪,等待被处理,mBlocked置为 false
    						mBlocked = false;
    						if (prevMsg != null) {
    							prevMsg.next = msg.next;
    						} else {
    							mMessages = msg.next;
    						}
    						msg.next = null;
    						// 返回一个消息等待处理
    						return msg;
    					}
    				} else {
    					// 没有消息的情况
    					nextPollTimeoutMillis = -1;
    				}
    				
    				if (pendingIdleHandlerCount < 0
    						&& (mMessages == null || now < mMessages.when)) {
    					// mIdleHandlers是一个ArrayList,通过 addIdleHandler 添加,一般我们不会调用此方法,
    					// 所以大多数情况下 mIdleHandlers.size()是0
    					pendingIdleHandlerCount = mIdleHandlers.size();
    				}
    				if (pendingIdleHandlerCount <= 0) {
    					// 没有要运行的空闲 Handler ,循环并等待更多时间 ,所以大多情况下是一直阻塞的,这也就解释了为什么我们的
    					// Activity 显示出来之后,我们只要一直亮屏,它就不会结束,因为ActivityThread的main方法调用了Looper.loop,
    					// 使得程序一直挂起。
    					mBlocked = true;
    					continue;
    				}
    			}
    		}
    	}

    最终调用  Handler.dispatchMessage(),而 Handler.dispatchMessage() 就会回调 handleMessage()。

    从对MessageQueue的源码剖析过程中,我们可以得出它是一个由单链表构成的具有优先级的队列。为什么这么说呢?
    单链表:Message的next成员也是Message,形成一个单链表。

    优先级:体现在每次消息入队的时候,我们最终调用 enqueueMessage(Message msg, long when) 方法,这里的 when 指的是消息等待处理的时间或者叫做延迟时间,而消息的处理同样会根据这个等待时间的大小顺序来。

    队列:满足先进先出规则。

    MessageQueue的阻塞和唤醒

    阻塞

    其实在上面的代码示例中,我已经讲过在调用 MessageQueue.next() 时,会调用 nativePollOnce(ptr, nextPollTimeoutMillis) 实现阻塞,当 nextPollTimeoutMillis 是-1时,会一直阻塞。没明白的同学再好好看看上面的代码(一定要看注释)。

    唤醒

    在MessageQueue.enqueueMessage() 中有这样一段逻辑:

    //  是否需要唤醒
    boolean needWake;
    // 如果消息队列没有消息,那么 mBlocked 就为 true,那我插入消息时就得唤醒消息队列
    if (p == null || when == 0 || when < p.when) {
        // New head, wake up the event queue if blocked.
        msg.next = p;
        mMessages = msg;
        // 如果 mBlocked 为true,即队列阻塞,则需要唤醒
        needWake = mBlocked;
    }
    
    if (needWake) {
        // 调用 native 方法唤醒队列
        nativeWake(mPtr);
    }
    
    // 没有 Handler 要处理时,或者可以理解为消息队列中没有消息时,消息队列就会阻塞
    if (pendingIdleHandlerCount <= 0) {
        // No idle handlers to run.  Loop and wait some more.
        mBlocked = true;
        continue;
    }

    上面的代码意思是说,在 MessageQueue 中没有 Message 时,它就会阻塞,此时我们插入 Message,就会唤醒 Message。

    Handler对我们开发者的启发

    亮点一

    	/**
    	 * 在此处处理系统消息。
    	 */
    	public void dispatchMessage(@NonNull Message msg) {
    		// 如果 Message 自己有callback,就调用 Message.callback.run()
    		if (msg.callback != null) {
    			handleCallback(msg);
    		} else {
    			// 如果我们创建 Handler 时给 Callback 赋值了,就走这里
    			if (mCallback != null) {
    				// 当 Callback 处理完消息之后,我们可以根据返回值确定,要不要走最后面的 handleMessage(msg);
    				// 这个就与我们的View事件分发机制有些相似点了,根据返回值决定事件是否继续往下分发
    				if (mCallback.handleMessage(msg)) {
    					return;
    				}
    			}
    			handleMessage(msg);
    		}
    	}
    	
    	private static void handleCallback(Message message) {
    		message.callback.run();
    	}
    	
    	/**
    	 * 可以在实例化 Handler 时使用回调接口,以避免必须实现自己的 Handler 子类。
    	 */
    	public interface Callback {
    		/**
    		 * @param msg 一个{@link android.os.Message Message} 对象实例
    		 * @return 如果不需要进一步处理,则为 true
    		 */
    		boolean handleMessage(@NonNull Message msg);
    	}

    上面代码就体现面向对象的封装思想,Message 封装了自己的 Callback ,如果 Message 是设置了它自己的 Callback,就回调自己的 callback.run();同样 Handler 封装了自己的 Callback ,如果 Handler 是设置了它自己的 Callback, 就回调自己的 handleMessage(),并且我们可以根据返回值决定要不要执行 Handler 子类重写的 handleMessage()。这样使得程序很灵活,有点像责任链模式

    亮点二

    大家有没有想过,Handler 调用了 dispatchMessage 之后,就把消息出来完了,那消息是怎么回收的?

    其实在 Looper.loop() 中,最终会调用 Message.recycleUnchecked() 进行所谓的消息回收(其实消息并未被回收),我们来看源码:

    	public static final Object sPoolSync = new Object();
    	
    	/**
    	 * 回收可能正在使用的消息。 处理排队的消息时,由 MessageQueue 和 Looper 在内部使用。
    	 */
    	@UnsupportedAppUsage
    	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;
    
    
            // 加锁,避免多个线程回收消息时,消息池的消息混乱		
    		synchronized (sPoolSync) {
    			if (sPoolSize < MAX_POOL_SIZE) {
    				// 消息池的队头
    				next = sPool;
    				// 把当前处理完的消息赋值给队头
    				sPool = this;
    				// 消息池的消息量加一
    				sPoolSize++;
    			}
    		}
    	}

    然后再看看Message.obtain():

    	/**
    	 * 从消息池中返回一个 Message 实例。 避免我们在很多情况下创建新的消息对象。
    	 */
    	public static Message obtain() {
    		synchronized (sPoolSync) {
    			// 消息池队头不为空
    			if (sPool != null) {
    				// 队头赋值给要返回的消息对象
    				Message m = sPool;
    				// 队头指向 m 的下一个节点
    				sPool = m.next;
    				// m 的下一个节点置空
    				m.next = null;
    				m.flags = 0; // clear in-use flag
    				sPoolSize--;
    				return m;
    			}
    		}
    		return new Message();
    	}

    我们发现上述代码跟我们登机之前做安检的场景很像,Message 就好比是放行李的盒子,Message的成员what、arg1、arg2、objtct就相当于是行李,当我们过完安检之后,盒子不会被丢弃,而是放在安检门口以备下一个过安检的人使用,这好比我们的消息池,其实这里所用的就是享元模式

    那么这样做有什么好处,从内存优化的角度思考,通过 Message.obtain() 获取消息大大减小了 new Message() 的调用,也就减少了连续内存空间被过度破坏,即不至于过度碎片化,也就是内存中连续空间更多了,那OOM出现的概率就小了。可能有同学说GC是干什么吃的,那大家有没有想过,既然 GC会帮我们回收垃圾,释放内存,为什么还会出现OOM。其实 GC即便用了标记整理算法,使得内存空间连续,但是GC线程工作的时候,会STW(stop the world),即其他所有线程都得挂起。而且我们不断地new 对象,又不断地触发 GC,会产生内存抖动,从而导致卡顿,所以从性能优化的角度来讲,我们尽量避免不必要的内存开销。

    Looper什么时候退出

    我们如果在子线程中创建 Looper 经常会有内存泄漏的问题,因为大部分同学都没有释放 Looper。那怎么办释放呢?通过调用 Looper.quitSafely() 或者 Looper.quit()

    	/**
    	 * 安全退出Looper。
    	 * 处理完消息队列中所有剩余的消息后,立即终止 {@link #loop} 方法。 但是,在 loop 终止之前,将不会传递未来到期的待处理的延
    	 * 迟消息。在要求 Looper 退出后,任何向队列发布消息的尝试都将失败。 例如,{@link Handler#sendMessage(Message)} 方法
    	 * 将返回 false。
    	 */
    	public void quitSafely() {
    		mQueue.quit(true);// 安全退出传的参数是 true,而Looper.quit()传的参数是 false
    	}

    最终会调用到MessageQueue.quit():

    	/**
    	 * 退出Looper
    	 *
    	 * @param safe 是否需要安全退出
    	 */
    	void quit(boolean safe) {
    		// 如果不允许退出,会抛异常,ActivityThread中的Looper就不允许退出。
    		if (!mQuitAllowed) {
    			throw new IllegalStateException("Main thread not allowed to quit.");
    		}
    
    		synchronized (this) {
    			// 如果正在退出,则return
    			if (mQuitting) {
    				return;
    			}
    			mQuitting = true;
    
    			if (safe) {
    				// 安全退出
    				removeAllFutureMessagesLocked();
    			} else {
    				// 不安全退出
    				removeAllMessagesLocked();
    			}
    
    			// We can assume mPtr != 0 because mQuitting was previously false.
    			nativeWake(mPtr);
    		}
    	}

    安全退出的实现:

    private void removeAllFutureMessagesLocked() {
    		// 获取当前时间
    		final long now = SystemClock.uptimeMillis();
    		// 消息队列的队头赋值给 p
    		Message p = mMessages;
    		// 如果队头存在
    		if (p != null) {
    			// 如果队头延迟时间大于当前时间,移除所有消息
    			if (p.when > now) {
    				removeAllMessagesLocked();
    			} else {// 继续判断,取队列中所有大于当前时间的消息
    				Message n;
    				for (;;) {
    					n = p.next;
    					if (n == null) {
    						return;
    					}
    					if (n.when > now) {
    						break;
    					}
    					p = n;
    				}
    				p.next = null;
    				// 将所有所有大于当前时间的消息回收,延迟时间小于当前时间的消息即使消息队列退出了,仍然会继续被取出执行
    				do {
    					p = n;
    					n = p.next;
    					p.recycleUnchecked();
    				} while (n != null);
    			}
    		}
    	}

    不安全退出的实现:

    	/**
    	 * 移除消息队列所有消息,包括延迟时间小于当前时间的消息
    	 */
    	private void removeAllMessagesLocked() {
    		Message p = mMessages;
    		// 轮循队列中所有 Message对象,一一缓存到消息池中
    		while (p != null) {
    			Message n = p.next;
    			// 缓存到消息池中
    			p.recycleUnchecked();
    			p = n;
    		}
    		// 队头置空
    		mMessages = null;
    	}

    Handler常见面试题

    1.一个线程有几个 Handler?
    在一个线程中我们可以创建多个 Handler。

    2.一个线程中有几个 Looper?是如何保证的?
    一个线程中只有一个 Looper;详情见本博文 Handler初始化 章节

    3.Handler内存泄漏原因? 为什么其他的内部类没有说过有这个问题?

    首先我们要有一个概念,就是内部类会持有外部类对象。我们在Activity中往往通过 new Handler并重写其 handleMessage 方法,即用匿名内部类的方式创建 Handler对象,此时 Handler就会持有 Activity对象。而我们通过 Handler.sendMessage 发送消息时,最终在 Handler.enqueueMessage 方法中,会让Message的 target成员持有 Handler。如果我们有一个 Message设置了延迟时间 20分钟之后再去处理,而 Activity在20分钟之内退出的话,根据可达性法则,在 Handler.enqueueMessage 方法中,Message可以看作是 GC Root ,Message持有Handler,Handler又持有Activity,就导致了内存泄漏。
    4.如果想要在子线程中new Handler 要做些什么准备?为什么主线程不用做这些准备?

    子线程中创建 Handler要先调用 Looper.prepare(),再调用 Looper.loop();

    主线程其实就是 ActivityThread,Activity就处于 ActivityThread上运行,在 ActivityThread的 main方法中,通过 Looper.prepareMainLooper()获取到Looper对象,也调用了 Looper.loop()取消息。
    5.子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?


    6.既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?取消息呢?
    很简单,加 synchronized 锁

    7.我们使用 Message 时应该如何创建它?
    Message.obtain();因为它采用享元模式,重复利用回收的消息,大大减少new Message() 的概率,从内存优化的角度看,减少不必要的内存开销,可以有效避免内存过度碎片化,从而降低出现OOM的概率。

    8.Looper死循环为什么不会导致应用卡死?

    某些题目答案过段时间再公布, 大家开动聪明的小脑袋先思考,欢迎评论区与我交流,文章有不准确之处,还望指正 ヾ( ̄ー ̄)X(^▽^)ゞ

    展开全文
  • Flutter引擎源码分析(一) - 编译调试

    千次阅读 2022-06-26 02:29:23
    下载引擎代码 ...如果已有engine源码,但是版本不一致,通过配置 sync来解决 编译引擎代码 ninja 构建 真机debug版本 真机release版本 模拟器版本 创建一个flutter项目,运行 xcode启动ios项目 - Runner......

    请添加图片描述

    一、下载引擎代码

    (一)工具准备

    depot_tools 套件包含许多git工作流增强工具,通过这些工具可以协同工作,任何人都可以熟练地处理Chromium的代码库
    在这里插入图片描述

    • github配置SSH

      提示问题

      git@github.com: Permission denied (publickey).

      可能key过期,也就是配置到github上的公钥过期

      也可能本地的私钥删除

      这种情况下,重新配置,easy

      1. cd ~/.ssh

      2. vi known_hosts

      3. 删除里面的github部分

      4. ssh-keygen -t rsa -b 4096 -C “email@qq.com” [生成一对新的公钥,私钥]

      5. vi config

         Host *
        
           AddKeysToAgent yes
           UseKeychain yes
           IdentityFile ~/.ssh/自己生成的私钥文件
        
      6. ssh-add -K ~/.ssh/自己生成的私钥文件 (默认id_rsa, 避免与其他平台冲突,最好自己起一个) [将您的SSH私钥添加到ssh-agent并将密码短语存储在钥匙串中]

      7. pbcopy < ~/.ssh/id_rsa.pub (或者自己起的名字)

      8. 登录个人github,头像 - Settings - SSH and GPG Keys - NEW SSH Key - 粘贴

    • brew 安装 ant工具

    brew install ant

    • brew 安装 ninja

    brew install ninja

    (二)下载引擎

    • 新建flutter_engine目录(注意:路径不能有中文)

      mkdir flutter_engine

    • 创建gclient文件 (通过gclient下载源码)

      touch .glient

    solutions = [
    {
       "managed": False,
       "name": "src/flutter",
       "url": "git@github.com:flutter/engine.git@caaafc5604ee9172293eb84a381be6aadd660317",
        "custom_deps": {},
        "deps_file": "DEPS",
        "safesync_url": "",
    }, 
    ]
    

    执行 glient sync

    cat ~/Developer/kit/flutter/bin/internal/engine.versio
    caaafc5604ee9172293eb84a381be6aadd660317

    用这个结果去替换 .glient 中 ‘flutter/engine.git@’ 后面的长字符串, 此处用的是 3.0.1 (版本一致)

    在这里插入图片描述

    (三)升级操作

    如果已有engine源码,但是版本不一致,通过配置 sync来解决 (M1的话还是再等等吧,目前3.0.1稳定版 M1还做不到完美engine调试)

    • cat ~/Developer/kit/flutter/bin/internal/engine.versio 拿到当前flutter引擎版本号
    • 粘贴覆盖 .glient 文件 'flutter/engine.git@'后面的长串部分
    • 进入到 flutter_engine/src/flutter 目录,
    • 执行一次 git pull
    • 然后 git reset --hard caaafc5604ee9172293eb84a381be6aadd660317
    • 回到 flutter_engine目录,gclient sync --with_branch_heads --with_tags --verbose

    二、编译引擎代码

    在这里插入图片描述

    ninja 构建

    #构建iOS设备使用的引擎
    #真机debug版本
    ./gn --ios --unoptimized
    #真机release版本
    ./gn --ios -unoptimized --runtime-mode=release
    #模拟器版本
    ./gn --ios -simulator --unoptimized
    #主机端(Mac)构建  --  热重载
    ./gn --unoptimized
    

    flutter_engine/src/out 目录下产生4个文件

    image.png

    进入到 flutter_engine/src/out目录,使用ninja编译工程 (相当耗时…)

    ninja -C host_debug_unopt && ninja -C ios_debug_sim_unopt && ninja -C
    ios_debug_unopt && ninja -C ios_release_unopt
    

    三、配置iOS项目代码

    创建一个flutter项目,运行
    xcode启动ios项目 - Runner
    配置脚本

    在这里插入图片描述
    执行脚本,build 脚本target

    在这里插入图片描述

    四、Xcode编译项目能debug关键

    iOS Runner项目 build 启动, 启动阶段,操作pause programexecution

    在这里插入图片描述

    lldb设置符号断点

    在这里插入图片描述

    键入c 回车,跳过,正常启动
    继续键入c,跳过汇编

    在这里插入图片描述

    在这里插入图片描述

    look, 调试的log信息出来了…

    打开ninja编译到 模拟器xcode工程

    open flutter_engine/src/out/ios_debug_sim_unopt/flutter_engine.xcodeproj

    在这里插入图片描述

    之前调试是在 创建的fluuter项目/ios工程Runner里添加的调试log

    vs

    同时在 flutter_engine源码编译的 模拟器项目里 找到了修改的调试log代码

    肯定的是,编译调试engine的目的是达到了,接下来就是搞清楚为什么了

    out/engine编译的模拟器xcode项目里 touchesBegan 目的代码部分, command + shift + J,导航侧栏显示文件 - finder查看

    在这里插入图片描述

    最终调试改动的代码 存在与 flutter_engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm

    VS

    而engine源码编译的xcode工程 在
    flutter_engine/src/out/ios_debug_sim_unopt

    至此,也就是,刚才,engine通过同一份源码,编译出4种xcode工程,源码共享一份

    为什么能关联起来,Flutter引擎源码分析(二) - channel原生通信

    展开全文
  • Android Framework层系列源码分析

    千次阅读 2022-03-21 11:27:34
    有幸从Android应用递进一步,接触Framework层开发,排查问题,不再像以前一样,局限在应用层,所以决定写一个系列专栏,把自己平时工作遇到的问题,以及自己学习Famework层相关源码整理分析记录下来。 目录 ...
  • 【spring_security源码分析】一点就通

    千次阅读 2022-04-23 21:41:35
    【spring_security源码分析】一点就通
  • 源码分析Dubbo系列文章

    万次阅读 多人点赞 2018-06-09 22:57:09
    本系列文章主要针对Dubbo2.6.2(dubbox2.8.4)版本,从源码的角度分析Dubbo内部的实现细节,加深对Dubbo的...2、源码分析Dubbo服务提供者启动流程-上篇 3、源码分析Dubbo服务提供者启动流程-下篇 4、源码分析Dubb...
  • 【SemiDrive源码分析】【Yocto源码分析】02 - yocto/meta-openembedded目录源码分析 本 SemiDrive源码分析 之 Yocto源码分析 系列文章汇总如下: 《【SemiDrive源码分析】【Yocto源码分析】01 - yocto/base目录...
  • Lua源码分析

    2018-01-12 23:08:57
    Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析
  • BTC源码分析 准备

    万次阅读 2021-09-13 16:51:50
    本文将介绍BTC源码分析的事前准备工作,包括源码下载、工具准备等
  • 对openswan的Pluto源码和linux的IPsec内核源码进行了详细的分析,对于理解openswan的运行机制以及linux的IPsec实现很有帮助
  • 当tomcat启动的时候会创建servlet,调用其init方法,我们知道DispatchServlet就是一个...1.创建容器源码分析源码分析 url类图 源码分析 找到了init方法: public final void init() throws ServletException
  • 应该说是把linux内核这部分知识...linux内核版本号由3组数字组成 第一组:目前发布的内核主版本 第二组:偶数表示稳定版本,奇数表示开发中版本 第三组:错误修补次数 1.4.2 源码组织 比较偷懒,直接赋值一个图片即可。
  • opencv2.4.9源码分析——SIFT

    热门讨论 2014-12-24 14:42:25
    详细介绍SIFT算法,opencv的SIFT源码分析,以及应用实例
  • Dubbo服务注册源码分析

    千次阅读 2022-03-31 12:28:54
    本代码版本基于Dubbo2.7.8版本进行源码分析 注册概览 扫描所有@DubboService注解, 加载配置文件, 装载注解中的所有属性, 把每个服务都封装成一个ServiceBean, 注入到Spring容器 容器启动之后刷新上下文时, 触发...
  • opencv2.4.9源码分析——SURF

    热门讨论 2015-01-10 16:43:49
    详细介绍SURF算法,以及opencv的源码分析,并给出应用实例。
  • FBReader源码分析

    热门讨论 2012-12-05 22:51:33
    FBReader是一个开源电子书阅读器, 此文档位android源码分析。共分为五部分: 一。配置。 二。文件结构及基本框架梳理。 三。启动到渲染流程。 四。数据之源Model分析。 五。数据结构以及绘制
  • 拦截器Interceptor的使用 自定义拦截器需要实现HandlerInterceptor接口。 ... import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;...
  • Vue-cli(3.0 + ) 源码分析(一)

    千次阅读 2022-03-22 08:22:01
    下载了Vue-cli源码后没有找到单步调试或添加log的方法,就...接下来我们分析一下源码。 1. 首先看下package.json 版本是5.0.3,最新的版本,description描述:Command line interface for rapid Vue.js development
  • JDK源码分析

    万次阅读 多人点赞 2018-12-31 14:42:36
    2.2.1 add() add方法调用了linkLast方法来添加元素 2.2.2 get(index) get(index)里调用了node(index)方法 2.2.3 remove() checkElementIndex() 就是检查index的值是否合法 node(index) 就是上面分析过得方法 ,根据...
  • 本课程主要讲解OBS源码的编译,OBS功能实现,初始化,显示器录制,窗口的实现录制,以及录制模块源码详细分析,最后基本OBS源码开发了一个录制软件,界面如下: 主要有如下功能 (1)实现桌面,显示器采集、录制 ...
  • BTC源码分析 交易(一)

    万次阅读 2021-09-13 18:07:34
    概念 在《比特币:一种点对点的电子现金系统》中,电子货币被定义为:每一位所有者通过对前一次交易和下一位拥有者的公钥...在BTC源码中,电子货币并无实体类,而是间接以交易表示。交易 数据结构 在0.1.5版本中, ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,056,963
精华内容 422,785
关键字:

源码分析