1.handler的使用

public class MainActivity extends AppCompatActivity {MyHandler myHandler;private static final int MSG_INDEX_1 = 1;private static final int MSG_INDEX_2 = 2;private static final int MSG_INDEX_3 = 3;private TextView tv1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tv1 = findViewById(R.id.tv1);Message message = new Message();myHandler = new MyHandler(this);message.what = MSG_INDEX_1;myHandler.sendMessageDelayed(message,100);myHandler.sendEmptyMessageDelayed(MSG_INDEX_2, 10000);myHandler.sendEmptyMessageDelayed(MSG_INDEX_3, 5000);}static class MyHandler extends Handler {WeakReference<MainActivity> mWeakReference;public MyHandler(MainActivity activity) {mWeakReference = new WeakReference(activity);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case MSG_INDEX_1:Toast.makeText(mWeakReference.get(), "1111", Toast.LENGTH_SHORT).show();break;case MSG_INDEX_2:Toast.makeText(mWeakReference.get(), "2222", Toast.LENGTH_SHORT).show();break;case MSG_INDEX_3:Toast.makeText(mWeakReference.get(), "3333", Toast.LENGTH_SHORT).show();break;default:}}}
}// 其中有我们容易忽略的handler的创建部分 以下为handler的构造方法public Handler(@Nullable Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();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());}}// 注意点1 之后用到mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}// 注意点2 之后用到mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;}

2.sendMessage

sendMessage有好几种方式,其最终调用的都是public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis)

我们以sendMessageDelayed为例开始分析

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {// MessageQueue与Message结合一起组成了一个队列结构MessageQueue queue = mQueue;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) {...synchronized (this) {...msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlocked && p.target == null && msg.isAsynchronous();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.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}

我们需要着重分析入队的方法 以下面代码为例 分析Message与MessageQueue为什么构成了一个队列 是如何入队列的

        myHandler = new MyHandler(this);Message message = new Message();message.what = MSG_INDEX_1;myHandler.sendMessageDelayed(message,100);myHandler.sendEmptyMessageDelayed(MSG_INDEX_2, 10000);myHandler.sendEmptyMessageDelayed(MSG_INDEX_3, 5000);

其他逻辑不难理解 主要分析enqueueMessage内部逻辑

 boolean enqueueMessage(Message msg, long when) {...synchronized (this) {...msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// 不成立// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlocked && p.target == null && msg.isAsynchronous();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.nextprev.next = msg;}...}return true;}

这个入队的逻辑 简单来说是按照执行时刻入队 越先执行的msg 越先入队 比如我的代码执行入队操作后的结构类似下面这样

3.handleMessge

那么handleMessge是在哪里触发的呢

实际上我们忽略了Handler是在Activity主线程执行的  在ActivityThread的main函数 有两句重要的执行

Looper.prepareMainLooper以及Looper.loop

    public static void main(String[] args) {...// 初始化主线程的LooperLooper.prepareMainLooper();...ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}Looper.loop();}public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();private static void prepare(boolean quitAllowed) {// sThreadLocal 中存储了ThreadLocalMap 该map以当前线程为key looper为value 确保一个线程只有一个looperif (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}// 一个线程只创建一次loopersThreadLocal.set(new Looper(quitAllowed));}

至于Looper.loop则是开启死循环 遍历MessageQueue中的Message 让handler执行handleMessage

4.Looper.loop方法

    public static void loop() {final Looper me = myLooper();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 blockif (msg == null) {// No message indicates that the message queue is quitting.return;}...try {// msg.target 是handler 即执行handler的dispatchMessage方法msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}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();}}// Handler.javapublic void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}

5.handler的整体工作流程

1.looper.prepare

主要负责创建Looper对象 looper对象每个线程只有一个 保存在Looper的静态变量ThreadLocal<Looper>  sThreadLocal中

ThreadLocal保存形式为保存在ThreadLocalMap 是一个Map key为当前线程 value为looper

保证一个线程只有一个looper

2.创建handler

创建handler时 就将handler关联到相关线程的looper和messageQueue 形成 Handler- thread-looper-queue的对应关系

其中有个异常 如果没有在创建Handler之前调用looper.prepare(创建looper) 抛出以下异常

            throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");

3.sendMessage

该方法主要是构建消息队列 按照消息的执行时间将Message形成队列 该队列会保存在Looper中

4.looper.loop

该方法主要是从消息队列取出对头元素 交由handler执行,之后会触发Handler的handleMessage方法

6.handler框架模拟

本节所有文件都是自己新建的,目的是模拟ActivityThread中handler,looper等相关类的生命周期 与Android原生的文件同名文件没有关系

1.Message

public class Message {// 何时执行Messagepublic long when;// 用于区分各个Messagepublic int what;// 标记当前Message由哪个handler处理public Handler target;// 链表结构 指向下一个MessageMessage next;// 用于通过Message传递信息给Handlerpublic Object obj;
}

2.MessageQueue

public class MessageQueue {// 不是严格意义的队列 入队列按照when入队列 出队则是和队列的规则一致// 虽然只有一个Message 然而Message.next 指向下一个Message 依次类推 知道某一个Message的next指向null 其实组成一个队列Message mMessages;// 按照when的value 从小到大入队列boolean enqueueMessage(Message msg, long when) {System.out.println("enqueueMessage msg.what " +msg.what);synchronized (this) {msg.when = when;Message p = mMessages;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.Message prev;for (; ; ) {prev = p;p = p.next;if (p == null || when < p.when) {break;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}}return true;}// 从队列头部出队列Message next() {for (; ; ) {synchronized (this) {// Try to retrieve the next message.  Return if found.final long now = System.currentTimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {// Stalled by a barrier.  Find the next asynchronous message in the queue.do {prevMsg = msg;msg = msg.next;} while (msg != null);}if (msg != null) {if (now < msg.when) {// Next message is not ready.  Set a timeout to wake up when it is ready.} else {// Got a message.if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;return msg;}} else {// No more messages.}}}}
}

3.Handler

public class Handler {MessageQueue mQueue;public Handler() {Looper looper = Looper.myLooper();if(looper == null){// 创建Handler之前必须调用Looper.prepare// 即创建Handler之前 必须能根据当前线程取出当前线程的Looper 否则报错throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}// 将一一对应的Looper-Queue中的Queue赋值给当前handler中的Queue// 最终形成 Handler-Looper-Thread 的相互对应的关系//          |       |//          Q u e u emQueue= looper.mQueue;}// 发送消息 本质是入队列public void sendMessage(Message message) {sendMessageDelayed(message,0);}// 发送消息 本质是入队列public final boolean sendMessageDelayed(Message msg, long delayMillis){if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, System.currentTimeMillis() + delayMillis);}// 发送消息 本质是入队列public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {return false;}return enqueueMessage(queue, msg, uptimeMillis);}public void handleMessage(Message msg) {}// 发送消息 本质是入队列private boolean enqueueMessage(MessageQueue queue, Message msg,long uptimeMillis) {// 确认Message交由本handler处理msg.target = this;return queue.enqueueMessage(msg, uptimeMillis);}}

4.Looper

public class Looper {// Looper内部只有一个Looper实例 存储在sThreadLocalstatic final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();public MessageQueue mQueue;public static Looper myLooper() {return sThreadLocal.get();}public Looper() {// 一个looper匹配唯一一个队列mQueuemQueue = new MessageQueue();}public static void prepare() {if (sThreadLocal.get() != null) {// 确保一个Looper只返回唯一一个Looper实例throw new RuntimeException("Only one Looper may be created per thread");}// 在sThreadLocal中存储Looper实例 键值对为 thread-loopersThreadLocal.set(new Looper());}/**    ThreadLocal的set方法*     public void set(T value) {*         // 获取当前thread*         Thread t = Thread.currentThread();*         // 以当前thread为key Looper为value 存储在ThreadLocalMap*         // 即 ThreadLocalMap 存储了唯一的键值对 thread-looper*         // 一个thread只有一个looper 且必须有一个(否则loop时报错)*         ThreadLocalMap map = getMap(t);*         if (map != null)*             map.set(this, value);*         else*             createMap(t, value);*     }*//*** Run the message queue in this thread. Be sure to call* {link #quit()} to end the loop.*/public static void loop() {final Looper me = myLooper();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 blockif (msg == null) {// No message indicates that the message queue is quitting.return;}try {// 由调用sendMessage的handler调用handleMessagemsg.target.handleMessage(msg);} catch (Exception exception) {throw exception;}// 回收消息部分省略// msg.recycleUnchecked();}}
}

5.TextView

public class TextView {private Thread mThread;public TextView(){mThread = Thread.currentThread();}public void setText(CharSequence text){checkThread();System.out.println("update "+text);}void checkThread() {if (mThread != Thread.currentThread()) {throw new RuntimeException("Only the original thread that created a view hierarchy can touch its views.");}}
}

6.Activity

public class Activity {public void onCreate(){System.out.println("onCreate execute");}public void onResume(){System.out.println("onResume execute");}public TextView findViewById(int tv) {return new TextView();}
}

7.MyActivity

public class MyActivity extends Activity{private TextView mTextView;private Handler mHandler = new Handler(){// 由于Handler在主线程创建 Handler构造方法Looper.myLooper时取得了主线程的Looper// handleMessage在主线程执行public void handleMessage(Message msg) {System.out.println("msg.what 必定=999 "+msg.what);mTextView.setText((String)msg.obj);System.out.println("handleMessage在主线程执行: "+Thread.currentThread());};};@Overridepublic void onCreate() {super.onCreate();mTextView = findViewById(RR.id.text_view);new Thread(){public void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}// mTextView.setText("");// Only the original thread that created a view hierarchy can touch its views.// 子线程禁止直接操作UI// 如果想在子线程创建Handler 需要调用Looper.prepare 否则抛出异常// Can't create handler inside thread that has not called Looper.prepare()// Looper.prepare();// Handler my = new Handler();// Looper.loop();// 开启子线程System.out.println("child thread "+Thread.currentThread());Message message = new Message();message.obj = "update text";message.what = 999;// 向在主线程中创建的mHandler发送消息 消息入栈 之前在ActivityThread已经开启轮询Looper.loop();// 之后则会被mHandler handle这个messagemHandler.sendMessage(message);};}.start();}@Overridepublic void onResume() {super.onResume();}
}

8.RR(模拟R文件)

public class RR {public final static class id{public static final int text_view = 0x0011;}
}

9.ActivityThread

public class ActivityThread {final H mH = new H();public static final int RESUME_ACTIVITY         = 107;public static void main(String[] args) {System.out.println("当前是主线程: "+Thread.currentThread());// 初始化当前Thread的looperLooper.prepare();ActivityThread thread = new ActivityThread();// 开始Activity的生命周期thread.attach(false);// 开启消息的轮询Looper.loop();}private void attach(boolean b) {Activity myActivity = new MyActivity();// onCreate执行myActivity.onCreate();Message message = new Message();// 将Activity传递进Messagemessage.obj = myActivity;message.what = RESUME_ACTIVITY;// message在mH的队列中入队 在轮询之后 会由mH处理MessagemH.sendMessage(message);}private class H extends Handler {@Overridepublic void handleMessage(Message msg) {if (msg.what == RESUME_ACTIVITY){// 处理attach中的messageActivity mainActivity = (Activity) msg.obj;mainActivity.onResume();}}}}

执行结果:

当前是主线程: Thread[main,5,main]
onCreate execute
enqueueMessage msg.what 107
onResume execute
child thread Thread[Thread-2,5,main]
enqueueMessage msg.what 999
msg.what 必定=999 999
update update text
handleMessage在主线程执行: Thread[main,5,main]

7.handler相关的常见面试题

1.MessageQueue 是什么结构?

MessageQueue 是队列的数据结构,以message为节点构成先入先出的队列

2.ThreadLocal 是怎么保证线程安全的

ThreadLocal 是线程隔离的 即使保存了一个static的ThreadLocal 实例 实际上使用的时候ThreadLocal 的get与set方法都还是根据当前线程来存取

观察ThreadLocal的set与get方法

    public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}ThreadLocalMap getMap(Thread t) {return t.threadLocals;}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}

可以知道ThreadLocal 之所以是线程隔离的 是因为每个Thread有自己的ThreadLocalMap,该map以当前线程为key 想要保存的值为value,这意味着ThreadLocal 会根据当前线程的不同,存取的值也在不同的线程的ThreadLocalMap中

借助

https://zhuanlan.zhihu.com/p/133433717

的例子

public class Test {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {// 复写initialValue方法为ThreadLocal设置一个初始值,并获取调用了threadLocal的线程id@Overrideprotected Integer initialValue() {System.out.println("current thread id:"+ Thread.currentThread().getId());System.out.println("threadLocal==>"+threadLocal);return 10;}};public static void main(String[] args) {// main方法就对应一个线程了,我们在主线程中对threadLocal的值进行修改System.out.println("mian thread");System.out.println("threadLocal in main thread:" + threadLocal.get());threadLocal.set(100); // 改变threadLocal的值System.out.println("threadLocal in main thread again:"+ threadLocal.get());System.out.println("start new thread");// 新创一个线程,并获取threadLocal的值Thread thread = new Thread(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubSystem.out.println("threadLocal in new thread:"+ threadLocal.get());}});thread.start();}
}

乍一看 test使用了类变量threadLocal 应该所有线程使用同一个实例

然而输出如下

mian thread
current thread id:1
threadLocal==>Father.Test$1@15db9742
threadLocal in main thread:10
threadLocal in main thread again:100
start new thread
current thread id:11
threadLocal==>Father.Test$1@15db9742
threadLocal in new thread:10

这是因为虽然threadLocal 是同一个变量 但是观察上面ThreadLocal的set与get方法 可知 它存储在Map的key是当前的Thread,因此实际上不同的线程存储在不同的ThreadLocalMap中。

3.使用handler的常见错误

何时会引发java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

为什么在主进程中创建Handler没有这样的问题呢?

在子线程创建Handler并且不调用looper的prepare方法

至于主进程中创建Handler没有这个问题,自然是主进程自己已经帮我们做了

 // ActivityThread.javapublic static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Set the reporter for event logging in libcoreEventLogger.setReporter(new EventLoggingReporter());// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);Process.setArgV0("<pre-initialized>");// 准备主进程的looperLooper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);// 启动looper的循环// 跟踪这里 Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}// Looper.javapublic static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) {...try {// 分配msgmsg.target.dispatchMessage(msg);end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}...}}// Handler.javapublic void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}// 处理msghandleMessage(msg);}}ActivityThread.javaprivate class H extends Handler {public void handleMessage(Message msg) {switch (msg.what) {case LAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");final ActivityClientRecord r = (ActivityClientRecord) msg.obj;r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;case RELAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");ActivityClientRecord r = (ActivityClientRecord)msg.obj;handleRelaunchActivity(r);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;case PAUSE_ACTIVITY:...}}

红橙Darren视频笔记 Handler源码简析与handler框架模拟 ThreadLocal相关推荐

  1. 红橙Darren视频笔记setContentView源码分析 xml加载的过程

    setContentView过程分析 从继承Activity的类开始进行分析 MainActivity setContentView(R.layout.activity_main); Activity ...

  2. 红橙Darren视频笔记 UML图简介

    整体架构复制自红橙原视频的课堂笔记 因为他这一课没有博客,所以没有转载链接,CSDN没有转载地址是无法作为转载类型的文章发表的,暂时标记为原创 参考链接 https://blog.csdn.net/r ...

  3. 红橙Darren视频笔记 类加载机制(API28) 自己写个热修复 查看源码网站

    第一部分 类加载机制 一个Activity是如何被Android虚拟机找到的? 在之前的文章 红橙Darren视频笔记 自定义View总集篇(https://blog.csdn.net/u011109 ...

  4. 红橙Darren视频笔记 Behavior的工作原理源码分析

    主要coordinatorlayout的代码来自coordinatorlayout-1.0.0-sources.jar 本文从源码介绍 CoordinatorLayout 的 behavior 怎么工 ...

  5. 红橙Darren视频笔记 代理模式 动态代理和静态代理

    红橙Darren视频笔记 代理模式 动态代理和静态代理(Android API 25) 关于代理模式我之前有过相关的介绍: https://blog.csdn.net/u011109881/artic ...

  6. 红橙Darren视频笔记 利用阿里巴巴AndFix进行热修复

    注意 由于AndFix在2017年左右就停止更新了,在最新版本的apk上遇到很多问题,我最终也没有成功进行热修复.本节主要是学习热修复的原理 在上一篇 红橙Darren视频笔记 自己捕获异常并保存到本 ...

  7. 红橙Darren视频笔记 ViewGroup事件分发分析 基于API27

    本节目标,通过案例,先看程序运行结果,然后跟踪源码,理解为什么会有这样的输出,继而理解view group的分发机制,感觉和证明题很像呢. 考虑以下程序的运行结果: case1: public cla ...

  8. 红橙Darren视频笔记 仿QQ侧滑效果

    这一篇没有什么新的内容 就是改写 红橙Darren视频笔记 仿酷狗侧滑效果 的侧滑的效果 1.去掉淡入淡出效果 2.加上黑色模板效果 效果: 去掉淡入淡出效果很简单 就是注释掉onScrollChan ...

  9. ffmpeg实战教程(十三)iJKPlayer源码简析

    要使用封装优化ijk就必须先了解ffmpeg,然后看ijk对ffmpeg的C层封装! 这是我看ijk源码时候的笔记,比较散乱.不喜勿喷~ ijk源码简析: 1.ijkplayer_jni.c 封装的播 ...

最新文章

  1. 学习Spring Boot
  2. lscpu命令查看CPU统计信息
  3. AM-GM均值不等式的一种简证
  4. 征战蓝桥 —— 2013年第四届 —— C/C++A组第9题——剪格子
  5. c/c++面试试题(一)
  6. gin-jwt对API进行权限控制
  7. 详析 Kubernetes 在边缘计算领域的发展
  8. Linux/Windows/MacOS各个操作系统下推荐应用集合
  9. java字符排序_如何按字母顺序对字符串进行排序java
  10. 关于Timestamp的valueOf()方法
  11. Android反编译,看这一篇就够了
  12. 华为5G空口新技术(2015年)
  13. 编程实战(2)——Python解微分方程方法总结
  14. Python编程:从入门到实践.pdf :Python 基础笔记,最基本的 Python语法,快速上手入门 Python
  15. oracle中的start with connect by用法
  16. 5分绩点转4分_gpa5分制换算4分制(5分绩点转4分)
  17. Python基于PyTorch实现BP神经网络ANN回归模型项目实战
  18. 一触即达!佰达慧兴携新风向标开展新零售电商产业升级
  19. 网罗全网最优质AI社区!
  20. html5关于校庆作品名称,五十五周年校庆标语

热门文章

  1. css为什么要用浮动_CSS中有几种定位?如何使用?
  2. 帆软按钮控件变查询_如何设置finereport按钮控件的可用时间
  3. 微信小程序开发教程第八章:微信小程序分组开发与左滑功能实现
  4. 自定义 Spark item 的渲染器
  5. js match()方法
  6. linux+分离线程+退出,Linux下线程终止操作.pdf
  7. Java基础知识——Java常用类的使用
  8. C/C++ list链表的理解以及使用
  9. 計算機二級-java09
  10. 数据挖掘的好书_唐宇迪:入门数据挖掘,我最推荐这本书