重学Android——消息机制

《重学安卓》两周年,kotlin协程处理并发 但十分令人遗憾的是,本是从 “实事求是” 出发概括的术语,却频繁被人拿去 “点缀文章” 和 “挂羊头卖狗肉”,这些滥用行为,无形中扭曲和破坏了术语 “实事求是” 的形象, 所以过去两年里,我们积极地举报 “培训机构的洗稿软文”,以及在相关文章的评论区补充参考文献来源, 非常感谢小伙伴们的主动反馈。 本质概括一览 基于深度思考,我们确立下来并广泛传播的 “本质概括” 包括但不限于: Jetpack 架构组件本质: Lifecycle 的本质是解决 “生命周期管理” 的一致性问题 LiveData 的本质是解决 阅读详情

Android消息机制

本文源码Api28

在Android在主线程的创建时,会自动创建一个looper,不需要我们自己来创建。

那么Android应用启动流程中,会由AMS调用的ActivityThread类,它的main入口方法里:

public static void main(String[] args) {
        ...

        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        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();
    	
    	//注意:
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        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的两个方法,Looper.prepareMainLooper以及Looper.loop()。

Looper

先看prepareMainLooper

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

在这里调用到了Looper主要有两个方法之一:prepare,而后面的判断sMainLooper是否为空以及赋值,都是为了保证只prepareMainLooper一次。

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

那么我们可以只看prepare:

    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));
    }

可以看到,其实就是在prepare就做了两件事

  1. 判断当前对象是否为空,不为空就抛出异常----这是一个相对来说比较奇葩的判定,一般我们写程序,都是判断为空抛异常或者new,再仔细看这一段英文“一个线程只能有一个looper”,这就代表一个线程不能多次调用prepare来创建looper。
  2. 如果线程中没有looper,那么那new一个looper将其set到ThreadLocal中去。
ThreadLocal

顺便说一嘴ThreadLocal

ThreadLoacl是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以扣,只有在这个线程中可以获取到存储的数据,对其它线程是无法获取到的——by《Android开发艺术探索》

特点在于使用set来存储数据,get来取出数据。

我们可以在代码中做个试验:

    final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    threadLocal.set(1);
    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "run: "+threadLocal.get());     
        }
    }).start();

得到的结果果然是2019-06-05 22:05:07.933 3076-4709/com.apkcore.studdy D/MainActivity: run: null,可见在哪个线程放在数据,就必做在哪个线程取出。

关于ThreadLocal的内部代码,篇幅有限,下次再一起详细看。


继续来看Looper,我们已经看到在prepare中,如果线程中没有Looper对象时,会new一个looper,并把它加入到ThreadLocal中,那看一下它的构造函数

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

在构造方法中,实例化了一个消息队列MessageQueue,并还会获取到当前的线程,把它赋值给mThread。也就是说消息队列此时已经和当前线程绑定,其作用的区域为当前实例化looper的线程。

那么我们接下来可以看Main方法调用的Looper.loop()方法

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

public static void loop() {
    //获取一个looper对象,可以看到prepare一定在loop()调用之前
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    ...

    for (;;) {
        //从消息队列MessageQueue中取消息,如果消息为空,跳出循环
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        ...
        //如果消息不为空,那么msg交给msg.target.dispatchMessage(msg)来处理
        try {
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
		//方法回收
        msg.recycleUnchecked();
    }
}

去掉了一些对我们分析不是特别重要的代码,对关键代码做了注释,其实就是获取当前的looper对象,并进入一个死循环中,一直从消息队列中取消息,如果消息为null就跳出循环,如果不为空,那么把消息交给msg.target.dispatchMessage(msg)来处理,msg.target在后面会看到,它其实就是Handler,过会分析。最后调用recyleUnchecked来方法回收。

这里有一个知识点queue.next(),我们到下面分析messageQueue的时候一起来分析。

Handler

我们在使用Handler的时候

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0) {
                Log.d(TAG, "handleMessage: 0");
            }
        }
    };

一般是这么使用,当然在Activity直接这么使用,是有可能内存泄露的,但这不是我们这一节要讲的重点。

那么我们先从handler的构造函数开始看

    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
        ...

        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的myLooper方法,从ThreadLocal中取出looper对象,之后判断对象是否为空,为空则抛出异常。不为空则获取到了looper的消息队列,这样,Handler中就持有了messageQueue的引用。

Asynchronous messages represent interrupts or events that do not require global ordering with respect to synchronous messages.  Asynchronous messages are not subject to the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.

注释中详细讲了mAsynchronous这个参数的作用,是一个同步障碍,异步消息不受引入的同步障碍的限制,这一点也是同步消息和异步消息的区别了。在后面会讲到。

我们在使用handler发送消息时,一般使用sendMessage来发送,如

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(5000);
            mHandler.sendEmptyMessage(0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();

还是先忽略代码中直接new Thread的问题,继续跟踪源码

    public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        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);
    }

可以看到,都是层层调用,最终调到sendMessageAtTime方法里。先获取了队列的messageQueue,判断queue不能为空,然后调用到了enqueueMessage方法,把它自己的queue,msg和uptimeMillis都一并传了过去。

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

这enqueueMessage方法里,注意了!我们看到了熟悉的字眼msg.target,在Handler的构造方法中,mQueue = mLooper.mQueue;,传入到enqueueMessage方法中的就是这个值,则msg.target赋值为了Handler时,在looper中,把消息交给msg.target.dispatchMessage(msg)来处理,就是交给了Handler来处理。

也就是说Handler发出来的消息,全部发送给了MessageQueue中,并调用enqueueMessage方法,而Looper的loop循环中,又调用了MessageQueue的next方法,把这个消息给dispatchMessage来处理

那么再来看dispatchMessage方法

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

在这里,就能看到handler设置不同的回调的优先级了,如果msg调用了callback,那么只调用此callback,如果没有设置,那么构造方法中有传入callback的话,回调此callback,如果这二者都没有设置,才会调用覆写的Handler里的handleMessage方法

我们继承来看enqueueMessage方法

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

在这里可以看到,最终会调用到queue.enqueueMessage(msg, uptimeMillis);中,那我们接下来看MessageQueue

MessageQueue

messageQueue是在Looper的构造方法中生成的

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

我们继续看它的enqueueMessage方法

boolean enqueueMessage(Message msg, long when) {
        ...
            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;
                    }
                    //注意isAsynchronous是在上面if (mAsynchronous) {  msg.setAsynchronous(true); }这里有设置,默认false
                    if (needWake && p.isAsynchronous()) {
                        //当msg是异步的,也不是链表的第一个异步消息,所以就不用唤醒了
                        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;
    }

源码的关键地方都给了注释,只是对同步分割栏不理解,这个马上就讲。上述源码,就是在消息链表中找到合适的位置,插入message节点。因为消息链是按时间进行排序的,所以主要是比对message携带的when信息,首个节点对应着最先处理的消息,如果message被插入到表头了,就意味着最近唤醒时间也要做调整,把以needwake就设为了true,以便走到nativeWake(mPtr);

同步分割栏

Android 中的 Handler 同步分割栏

Handler之同步屏障机制(sync barrier)

所谓的同步分割栏,可以理解为一个特殊的Message,它的target域为null,它不能通过sengMessageAtTime等方法传入队列中,只能通过调用Looper的postSyncBarrier()

作用

它卡在消息链表的某个位置,当异步线程可以在Handler中设置异步卡子,设置好了以后,当前Handler的同步Message都不再执行,直到异步线程将卡子去掉。在Android的消息机制里,同步的message和异步的message也就是这点区别,也就是说,如果消息列表中没有设置同步分割栏的话,那么其实它俩处理是一样的。


继续看上面的nativeWake()方法,这是一个native方法,对应在C++里,有兴趣的盆友,可以找framworks/base/core/jni/android_os_MessageQueue.cpp来查看它的源码,这里我就不分析了,主要动作就是身一个管道的写入端写入了W。

回到最上面,我们在讲Looper.loop的源码中,我们还留下了一个msg.next()没有分析

    Message next() {
        ...
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            //阻塞在此
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                //获取next消息,如能得到就返回
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                //尝试拿消息队列里当前第一个消息
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    //如果队列里拿的msg是那个同步分割栏,那么就寻找后面的第一个异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        //下一条消息未就绪。设置超时以在准备就绪时唤醒。
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            //重新设置一下消息队列的头部
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;//返回
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            //处理idleHandlers部分,空闲时handler
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

其实源码中英文注释已经讲得比较直白了,我不过是随便翻译了一下,这个函数里的for循环并不是起循环摘取消息节点的作用,而是为了连贯事件队列的首条消息是否真的到时间了,如果到了,就直接返回这个msg,如果时间还没有到,就计算一个比较精确的等待时间(nextPollTimeoutMillis),计算完后,for循环会再次调用nativePollOnce(mPtr, nextPollTimeoutMillis)进入阻塞,等待合适的时长。

上面代码中也处理了“同步分割栏”,如果队列中有它的话,是千万不能返回的,要尝试寻找其后第一个异步消息。

next里面另一个比较重要的就是IdleHandler,当消息队列处于空闲时,会判断用户是否设置了IdleHandler,如果有的话,则会尝试处理,依次调用IdleHandler的queueIdle()函数。这个特性,我们可以用在性能优化里,比如延迟加载:我们经常为了优化一个Activity的显示速度,可能把一些非必要第一时间启动的耗时任务,放在界面加载完成后进行,但是就算界面加载完成了,耗时任务一样的会占用cpu,当我们正好在操作时,一样可能会造成卡顿的现象,那么我们完全可以利用Idle Handler来做处理。

nativePollOnce

前面说了next中调用nativePollOnce起到了阻塞作用,保证消息循环不会在无消息处理时一直在那循环,它的实现还是在android_os_MessageQueue.cpp文件中,同样在这里不做过多的深入,有兴趣的童鞋可以自行在frameworks/base/core/jni/android_os_MessageQueue.cpp中查看。

这里提一点,在c++中,pollonce调用了c++层的looper对象,在这个looper和我们java中的Looper是不一样的,其内部除了创建一个管道外,还创建了一个epoll来监听管道的读取端。

扩展

Android中为什么主线程不会因为Looper.loop()方法造成阻塞,在此文中详细讲了为什么我们在主线程loop死循环而不会卡死。

对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。

真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

消息循环数据通信采用的是epoll机制,它能显著的提高CPU的利用率,另外Android应用程序的主线程在进入消息循环前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用主线程在消息队列唯恐时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理是唤醒应用程序的主线程。也就是说在无消息时,循环处于睡眠状态,并不会出现卡死情况。

掘金
下面是我的公众号,欢迎大家关注我

Android Framework 核心,为何初级开发需要重点学习Framework? 今年Android开发就业情况来看,不管是大厂还是寥寥几十人的小公司在招人要求上出奇保持了一致:熟练掌握 Android Framework ,甚至精通它。 阅读详情

相关推荐

pytorch-cifar100学习资料汇总 - 基于PyTorch的CIFAR-100图像分类实践

pytorch-cifar100是一个基于PyTorch实现CIFAR-100数据集图像分类的开源项目。该项目实现了多种经典的深度学习网络模型,如ResNet、DenseNet、VGG等,为开发者提供了在CIFAR-100数据集上进行图像分类实验的代码范例。pytorch-cifar100项目为我们提供了在CIFAR-100数据集上进行图像分类实验的完整代码实现。无论你是想要学习PyTorch的使用,还是研究不同网络模型在CIFAR-100上的表现,这个项目都是一个很好的起点。

helloaiworld的博客 1123

Java泛型遇到多态与Java泛型边界

说明: 今天,继续学姐java编程思想-泛型这一章的时候,对于通配符的理解不够明白,于是又网上查了些资料,发现一篇写的很好的文章,在此记录学习一下

yx19861211的博客 1812

MTK平台LCD驱动开发实战:从零开始点亮你的Android屏幕

本文详细介绍了在MTK平台进行Android LCD驱动开发的完整流程。从理解LK与Kernel双核驱动框架入手,手把手讲解驱动结构体、屏幕参数配置、初始化序列编写以及PLL_CLOCK计算等核心步骤,并提供了系统集成配置与实用的调试避坑指南,帮助开发者高效点亮新屏幕。

terraform7cloud的博客 292

【亲测免费】 重学安卓:深入理解Android开发精髓

重学安卓:深入理解Android开发精髓 项目基础介绍 本项目名为“重学安卓”,是一个专注于Android开发技术和架构模式的开源项目。项目主要由Java语言编写,同时也包含部分CMake和C++代码。该项目旨在深入探讨Android开发中的各种概念,分享作者的深度思考和实战经验,帮助开发者更好地理解和运用Android技术。 核心功能 项目的核心功能包括: 深度技术文章分享:项目包含了一系列关...

gitblog_00034的博客 482

用LangFuse+LangChain打造可观测的AI应用:从埋点到成本监控实战

本文详细介绍了如何将开源可观测性平台LangFuse深度集成到基于LangChain构建的AI应用中,特别是电商客服机器人场景。通过从基础SDK集成、提示词管理到成本监控的完整实战,帮助开发者实现对LLM应用内部工作流的清晰追踪、问题排查与费用分析,从而打造稳定、可控的生产级AI应用。

dapp9builder的博客 711

torchsparse安装

torchsparse安装以及安装失败原因分析和解决

AI_BING_的博客 1万+

《重学安卓》:深度思考与实践的结晶

《重学安卓》:深度思考与实践的结晶 项目介绍 《重学安卓》是一个专注于Android开发的付费专栏,自2019年6月在小专栏平台开设以来,已经连接了1111名优秀开发者。这个项目不仅仅是一个学习资源,更是一个深度思考和实践的社区。通过基于“第一性原理”和“实事求是”精神的深度思考方式,项目团队开源了一系列高频使用的仓库,包括《UnPeek-LiveData》等,这些仓库被腾讯音乐、BMW、TCL等...

gitblog_01156的博客 742

【Python&RS】哨兵2号(Sentinel2)轨道条带说明+图幅统计(表格+矢量)

今天给大家带来哨兵2号的图幅编号介绍以及全国省、市所需图幅数的统计。网上教程极少,而且结果也不公开。因为每次下载的时候都不知道需要几幅,所以研究后给大家分享一下。

Remote Sensing迷途小书童 1681

[Android开发] 重学Android-EditText的进阶操作

EditText是我们常用的输入框控件,平常我们只是使用它输入文本,这里记录一些它不太常见的操作和一些解决方案。

Eqiqi的博客 1657

【OpenCV】复杂场景下的高精度圆检测实战(附完整代码)

本文针对OpenCV霍夫圆检测在复杂场景下易失效的问题,提出了一套基于轮廓分析的高精度圆检测实战方案。通过图像预处理、轮廓发现及多轮几何筛选(面积、周长、椭圆拟合、多边形逼近),有效排除噪声和类圆干扰物,显著提升复杂场景下的检测鲁棒性,并附完整可调参的Python代码实现。

weixin_29095587的博客 199

win11下的MySQL安装

目前官方最新版本是8.0.26,考虑到其稳定性、可靠性还需一定周期保证,所以使用官方版求稳定仍然建议5.7系列。3、选择MySQL Servers 5.7.38 -x64,然后按绿色箭头将选中的版本移植到右边选中框中。4、点击下方蓝色Advanced Options,选择安装位置。1、在开始界面搜索MySQL,点击command line。4、在“系统变量”中选择“path”,点击编辑。5、新建——>增加MySQL路径——>确定,12、execute,安装好后next。9、不需要做修改,直接点击next。.

rensweet的博客 2万+

重学 Android 之 Handler 机制

重学 Android 之 Handler 机制 一、前言 在 Android 开发中,handler 机制的使用几乎随处可见,作为面试中的常客,我们真的了解 handler 吗? 想必很多同学会想,当然了,handler 机制不就是 Looper 、handler、MessageQueue吗? 下面抛砖引玉,我们先来看看这些问题 为什么子线程中不可以直接 new Handler() 而主线程中可以? 真的只能在主(UI)线程中更新 ...

戎马书生IT的博客 463

[毕设项目-MCP+ENSP]一句话搞定ENSP自动化:MCP + LLM实战(架构图、工具清单、踩坑全解)

用 MCP + LLM 把“人话”变成对华为 ENSP 的安全自动化操作,三款工具覆盖配置/查询/落盘,配套架构、实战与排坑清单。

逐梦苍穹的博客 2539

重学Android-我对Handler有了新认识

Handler,我们Android工程师每天都在用的东西,它主要可以用来做线程间通信,这一块知识其实我们很熟悉了,但是最近我又翻了一遍Handler源码,发现我对Handler又有了新的认识,所以总结了这篇文章记录一下, 本文的源代码基于8.0。 在文章开始之前,友情提示,我会默认大家有了解Handler,Looper,MessageQueue的基础知识,不清楚的同学可以自行查阅别的资料哈。 ...

weixin_30726161的博客 194

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

重学Android——Binder运行机制

Binder 昨天从源码角度看了Android9.0下Activity的启动流程,其中关于跨进程的调用,都使用到了Binder进行跨进程通信,那么今天来阅读下Android的FrameWork层怎么实现Binder的吧。 IPC IPC是Inter Process Communication的缩写,意思是进程间通信。在Android系统中,每个应用都运行在一条独立的进程上,具有自己的DVM实例,而...

weixin_34256074的博客 202

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

重学 Android 面试题

目录 Android Java 网络 Kotlin 项目经验 算法 设计模式 性能优化 高频问题: Android 1,Activity生命周期 2,Activity启动模式及功能和使用场景 3,Android动画有哪些?之间有什么区别? 4,Android事件分发机制 5,View绘制流程 6,滑动冲突 7,AyscTask 异步消息处理 8,Handler 9,Handler防止内存泄漏的方法 10,自定义View 11,AIDL 12,Android有几种进

ezconn的博客 1657

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

深入探讨Android异步精髓Handler

Google采用Handler把主线程和子线程精巧地联系起来——子线程中进行耗时的业务逻辑,然后利用Handler通知主线程刷新UI。其实,在AsyncTask和IntentService的内部亦使用了Handler实现其主要功能。抛开这两者不谈,当我们打开Android源码的时候也随处可见Handler的身影。所以,Handler是Android异步操作的核心和精髓,它在众多领域发挥着极其重要甚至是不可替代的作用。

谷哥的小弟 2万+

重学SurfaceFlinger之View绘制到Surface过程剖析

本章我们沿着代码逻辑学习了应用是如何申请到画布、使用android的View系统如何绘图、绘图完成后如何提交buffer,本章所述的逻辑均是指通过android的View系统绘图的过程,也可以称其为hwui绘图流程,从上面代码流程可以知道,hwui的绘图流程是被vsync信号触发的,开始于vsync信号到达UI线程调用performTraversals函数, hwui的画面更新是被vsync信号驱动的。这句是将指令发送给GPU执行,这其中是如何“翻译”的细节笔者暂时尚未研究,这里先不做讨论。

learnframework的博客 534

两个多月学习安卓的心得,给新手一点建议

大三暑假只是稍微看了看第一行代码,算不上学习安卓。真正开始学习是从2017.9.15开始,到现在2017/12/7,从一个啥都不会的菜鸡到现在会了一点点东西的菜鸡。   1.做的app很少。   先对着网上代码做了画图板。忘光了。   看了三个视频系列,一个是基于安卓原生的播放器app,一个是完善的照相和摄像app,一个是即时通讯app。其中第二个最后叫我们去集成环信,心情可想而知(我...

qq_36523667的博客 3767

[Java]重学Java-Java平台

任职于Sun微系统的詹姆斯·高斯林等人于1990年代初开发Java语言的雏形,最初被命名为Oak,目标设置在家用电器等小型系统的编程语言,应用在电视机、电话、闹钟、烤面包机等家用电器的控制和通信。这与微软公司所倡导的注重精英和封闭式的模式完全不同,此外,微软公司后来推出了与之竞争的.NET平台以及模仿Java的C#语言。Java的原生类库提供了对多线程编程的支持,这使得Java在一些高并发的应用场景下也能发挥很好的性能,并发编程也往往成为初级Java工程师成长为中级Java工程师的必备知识之一。

qq_24428851的博客 614

周末不知道学什么?这份 Android 优秀技术文章清单请收下

Android Weekly 是之前每周都会做的,是把当周内更新或者在解决问题的时候搜到的优秀文章收集起来,最初只是自己的一个收藏,后来有小伙伴说不如分享出来,于是就在知乎搞了个专栏,每周...

Gracker的专栏 600

推荐一个相当强的 Node.js 全栈妹子

简单介绍推荐一位好朋友 koala,计算机科班出身,大学做开发就获得过多个国家一等奖选手,大一开始接触编程,工作从事过 Android 移动端,前端,目前在做 Node.js 后端开发,...

小生方勤 188

纯C开发Android APK:NativeActivity实战指南

Android应用开发本质上是运行在Linux内核之上的系统级工程,其底层支撑并非Java语言,而是C/C++与Bionic库。NativeActivity作为Android官方支持的纯原生入口机制,允许开发者绕过ART虚拟机、Java字节码和DEX加载流程,直接以C语言驱动完整APK生命周期。该方案核心价值在于消除JNI开销、规避GC不确定性、提升实时性与确定性,特别适用于音视频编解码、嵌入式协议栈、工业HMI及AR/VR等对延迟与稳定性要求严苛的场景。结合NDK构建、AAssetManager资源管理、

weixin_33925615的博客 265
上一篇: 匹配字符串之——KMP算法深入理解
下一篇: 重学Android——View的事件分发
ApkCore
博客等级 码龄12年 8粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值