Android-Handler学习总结

​​面试官​:你好!我看你简历里提到熟悉 Android 的 Handler 机制,能简单说一下它的作用吗?

候选人​:
        Handler 是 Android 中用来做线程间通信的工具。比如Android 应用的 UI 线程(也叫主线程)是非常繁忙的,它负责处理用户的交互、绘制界面等等。如果我们直接在其他子线程(比如网络请求线程、文件读写线程)里更新 UI,程序就会崩溃,因为 Android 不允许非 UI 线程直接操作 UI 组件。这时候 Handler 就派上用场了。简单来说,它可以做到:

  1. 将子线程中需要更新 UI 的操作,发送到主线程的消息队列中去排队。
  2. 主线程通过 Looper 不断地从这个消息队列中取出消息,然后交给 Handler 自己来处理。
  3. Handler 在收到消息后,就可以安全地在主线程中更新 UI 了。

        举个实际例子吧:主线程启动时,系统会自动创建一个 Looper 和消息队列,所以主线程的 Handler 可以直接用。但如果是子线程,得手动调用 Looper.prepare() 和 Looper.loop(),否则会报错——就像你去餐厅吃饭,主线程是服务员已经站在桌边等你点菜,子线程得自己喊服务员过来。


面试官​:嗯,那主线程为什么可以直接用 Handler?子线程用的时候要注意什么?

候选人​:

        关于主线程为什么可以直接用 Handler:

        这是因为 Android 应用在启动的时候,系统就已经为 主线程在启动时,系统已经帮我们初始化了 Looper(比如在 ActivityThread.main() 里调用了 Looper.prepareMainLooper()loop()),并且调用了 Looper.loop() 方法。这个 Looper 会自动为主线程维护一个消息队列 (MessageQueue)。所以,当我们在主线程中创建 Handler 实例时,它默认就会关联到主线程的 Looper 和 MessageQueue,不需要我们再额外做什么特殊处理。我们直接 new Handler() 就可以了,它自然就能和主线程的Looper配合工作。

        关于子线程用 Handler 的时候要注意什么:

        子线程默认情况下是没有 Looper 的,因此也就没有消息队列。如果想在子线程中使用 Handler 来处理消息(比如子线程之间通信,或者让子线程自己处理一些定时任务),就需要我们手动为这个子线程创建和启动 Looper。具体来说,有以下几个关键点需要注意:

  1. 创建 Looper: 在子线程的 run() 方法中,首先需要调用 Looper.prepare()。这个方法会为当前线程创建一个 Looper 对象,并将其保存在一个 ThreadLocal 变量中,同时也会创建一个 MessageQueue。
  2. 创建 Handler: 在调用了 Looper.prepare() 之后,我们就可以在这个子线程中创建 Handler 实例了。这个 Handler 会自动关联到刚刚创建的 Looper。
  3. 启动消息循环: 创建完 Handler 之后,非常重要的一步是调用 Looper.loop()。这个方法会开启一个无限循环,不断地从 MessageQueue 中取出消息,并分发给对应的 Handler 处理。如果忘记调用 Looper.loop(),那么发送到这个子线程 Handler 的消息将永远得不到处理。
  4. 退出 Looper(如果需要): Looper.loop() 是一个死循环,会阻塞线程。如果子线程的任务执行完毕后不再需要处理消息,或者希望线程能够正常结束,就需要调用 Looper 的 quit()quitSafely() 方法来停止消息循环,从而让线程能够退出。否则,这个子线程会一直处于等待消息的状态,无法被回收,可能会导致资源浪费。
    • quit():会立即清空消息队列中所有消息(包括未处理和延迟消息),然后退出 Looper。
    • quitSafely():则会处理完消息队列中已有的消息后,再安全退出 Looper,不会处理新的消息。通常推荐使用 quitSafely(),因为它更加安全。

        总结一下就是,主线程天生就有 Looper,可以直接用 Handler。子线程想用 Handler,就必须自己动手 Looper.prepare()、创建 Handler、然后 Looper.loop(),并且在不需要的时候记得 Looper.quit()quitSafely() 来释放资源。

        我平时在项目中如果遇到需要在子线程处理消息的情况,通常会优先考虑使用 HandlerThreadHandlerThread 是 Android 提供的一个封装好的类,它继承自 Thread,并且内部已经帮我们处理了 Looper.prepare()Looper.loop() 的逻辑,使用起来会更方便一些,也减少了出错的可能。


面试官​:如果子线程不调用 Looper.loop() 会怎么样?

候选人​:
        线程会直接结束,Handler 收不到任何消息。loop() 方法内部是个死循环,但不用担心卡死,因为没消息时会通过 Linux 的 ​epoll 机制​ 休眠,有消息时再唤醒。比如主线程的 Looper 虽然一直循环,但没消息时 CPU 占用几乎是 0。

        那子线程的 Handler 就收不到消息了。比如我写了个子线程的 Handler,但忘记调 loop(),结果发送的消息石沉大海,日志里还会抛异常。不过不用担心死循环卡死线程,因为 Looper 内部用了 Linux 的 ​epoll 机制,没消息时会休眠,有消息才唤醒——就像你晚上睡觉,手机静音了,但有人打电话进来会立刻震醒你。


面试官​:提到消息队列,Handler 的 postDelayed() 能保证准时执行吗?

候选人​:

        不一定准!比如我设置了 5 秒后弹 Toast,但如果手机休眠了 3 秒,实际可能要 8 秒后才执行。因为 postDelayed 用的是系统非休眠时间(SystemClock.uptimeMillis()),休眠时间不算在内。另外,如果主线程前面有耗时操作,比如解析大文件,后面的消息都得排队等着——就像堵车时,你就算预约了时间,也可能迟到。


面试官​:那如果子线程发消息到主线程,什么时候切换到主线程执行?

候选人​:
        子线程发消息时,消息会被加到主线程的 MessageQueue。此时子线程的任务就结束了,主线程的 Looper 会在下次循环取到这个消息,并在主线程执行 handleMessage()。整个过程 ​没有显式的线程切换,只是消息被不同线程的 Looper 处理了。


面试官​:Handler 导致内存泄漏遇到过吗?怎么解决?

候选人​:
        遇到过!比如在 Activity 里直接写 Handler handler = new Handler() { ... },这个 Handler 会隐式持有 Activity 的引用。如果 Activity 销毁时 Handler 还有未处理的消息,就会导致 Activity 无法被回收。
解决办法有两种:

  1. 静态内部类 + 弱引用​:
    static class SafeHandler extends Handler {private WeakReference<Activity> mActivity;SafeHandler(Activity activity) {mActivity = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {Activity activity = mActivity.get();if (activity != null) { /* 处理消息 */ }}
    }
  2. onDestroy() 移除所有消息​:
    @Override
    protected void onDestroy() {super.onDestroy();handler.removeCallbacksAndMessages(null);
    }

面试官​:如果让你设计一个定时任务,每隔 1 秒更新 UI,用 Handler 怎么实现?

候选人​:
可以用 postDelayed() 递归调用。比如:

private void scheduleUpdate() {handler.postDelayed(new Runnable() {@Overridepublic void run() {updateUI(); // 更新 UIscheduleUpdate(); // 再次调用自己,形成循环}}, 1000); // 延迟 1 秒
}

但要注意在页面销毁时移除回调,否则 Runnable 会一直持有 Activity 导致泄漏。

但一定要注意在页面销毁时移除回调,不然就算页面关了,Runnable 还在后台跑——就像你出门忘了关灯,电费白白浪费。


面试官​:最后一个问题,知道什么是 ​同步屏障(Sync Barrier)​​ 吗?

候选人​:
        同步屏障是 MessageQueue 里的一种特殊消息(target 为 null),用来阻塞同步消息,优先处理异步消息。比如系统在绘制 UI 时,会插入同步屏障,保证 Choreographer 的渲染消息优先执行。
代码里可以通过 MessageQueue.postSyncBarrier() 插入屏障,处理完后再调用 removeSyncBarrier() 移除。


ThreadLocal 在 Handler 机制中的作用

1. ThreadLocal 的角色:线程专属的“储物柜”​
  • 核心作用​:
    ThreadLocal 是每个线程的“私人储物柜”,用来保存线程独有的数据。在 Handler 机制中,每个线程的 ​Looper​ 就是通过 ThreadLocal 存储的,确保线程隔离。
    举个例子:主线程和子线程各自有一个“储物柜”,主线程的柜子里放着主线程的 Looper,子线程的柜子放自己的 Looper,互不干扰。

  • 源码验证​:

    public final class Looper {// ThreadLocal 存储每个线程的 Looperstatic final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();// 初始化当前线程的 Looperpublic static void prepare() {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper per thread!");}sThreadLocal.set(new Looper(false)); // 存入当前线程的储物柜}// 获取当前线程的 Looperpublic static @Nullable Looper myLooper() {return sThreadLocal.get(); // 从储物柜取出}
    }
  • 面试回答​:
    “ThreadLocal 就像每个线程的专属储物柜。比如主线程启动时,系统自动在它的柜子里放了一个 Looper,所以主线程的 Handler 可以直接用。但子线程的柜子一开始是空的,必须手动调用 Looper.prepare() 放一个 Looper 进去,否则创建 Handler 时会报错。”


2. 为什么每个线程只能有一个 Looper?​
  • 设计约束​:
    Android 规定一个线程只能有一个 Looper,避免多个消息循环竞争资源。ThreadLocal 的 set() 方法会检查是否已有 Looper,重复创建直接抛异常。

  • 源码佐证​:

    private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) { // 如果柜子里已经有 Looperthrow new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed)); // 第一次放进去
    }
  • 面试回答​:
    “这就好比一个线程只能有一个‘消息管家’(Looper)。ThreadLocal 的 prepare() 方法会检查柜子是否已经有管家,如果有就直接报错。这种设计防止了多个管家抢活干,导致消息处理混乱。”


Handler 与 Choreographer 的关系

1. Choreographer 如何利用 Handler?​
  • 核心机制​:
    Choreographer 负责协调 UI 绘制和 VSYNC 信号,它内部通过 Handler 发送异步消息,并插入同步屏障,确保绘制任务优先执行。

  • 源码解析​:

    // Choreographer 内部使用 Handler 发送异步消息
    private final class FrameHandler extends Handler {public FrameHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_DO_FRAME:doFrame(System.nanoTime(), 0); // 处理绘制任务break;// 其他消息处理...}}
    }// 发送异步消息(带同步屏障)
    private void postFrameCallback() {// 插入同步屏障,阻塞普通消息mHandler.postMessageAtTime(Message.obtain(mHandler, MSG_DO_FRAME), delay);msg.setAsynchronous(true); // 标记为异步消息
    }
  • 面试回答​:
    “Choreographer 就像一个交通指挥员,负责在 VSYNC 信号到来时触发 UI 绘制。它内部通过 Handler 发送一个异步消息(类似‘紧急任务’),并插入同步屏障,让普通消息靠边站。这样绘制任务就能插队优先执行,避免掉帧。”


2. 同步屏障与异步消息的作用
  • 同步屏障​:
    一种特殊消息(target=null),阻塞后续同步消息,只允许异步消息执行。
    应用场景​:UI 绘制、动画等需要高优先级的任务。

  • 源码验证​:

    // MessageQueue 处理同步屏障
    Message msg = mMessages;
    if (msg != null && msg.target == null) { // 遇到同步屏障do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous()); // 寻找下一个异步消息
    }
  • 面试回答​:
    “同步屏障就像地铁里的‘紧急通道’,普通消息(同步消息)被拦住,只有异步消息(比如 UI 绘制)能通过。这样系统能优先处理关键任务,比如保证 60fps 的流畅度。”


3. 为什么 UI 刷新不直接用普通 Handler?​
  • 性能优化​:
    直接使用普通 Handler 可能导致绘制任务被其他消息阻塞。通过同步屏障和异步消息,Choreographer 确保绘制任务在 VSYNC 信号到来时立即执行。

  • 实际案例​:
    当用户滑动列表时,Choreographer 在下一个 VSYNC 周期触发绘制,避免中途被其他消息(如网络回调)打断,从而减少卡顿。

  • 面试回答​:
    “如果直接用一个普通 Handler 处理 UI 刷新,可能有其他消息(比如数据加载)堵在前面,导致绘制延迟。而 Choreographer 通过同步屏障和异步消息,让绘制任务‘插队’,确保在 16ms 内完成,避免掉帧。”


总结回答(自然口语化)​

“ThreadLocal 就像线程的私人储物柜,保证每个线程的 Looper 独立。比如主线程的柜子自动放了 Looper,子线程需要手动准备。而 Choreographer 是 UI 流畅的关键,它用 Handler 发送异步消息,并通过同步屏障让绘制任务优先执行,就像给紧急任务开绿灯。这种机制确保动画和滑动不会卡顿,是 Android 流畅 UI 的基石。” ​


面试官​:我看你项目里用了 Handler,能说说为什么 Handler 会导致内存泄漏吗?具体是怎么发生的?

候选人​:
当然可以。Handler 的内存泄漏主要发生在 ​非静态内部类 + 延迟消息​ 的场景。比如在 Activity 里直接写:

public class MainActivity extends AppCompatActivity {private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// 更新 UI}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mHandler.postDelayed(() -> {// 延迟 10 秒执行任务}, 10_000);}
}

问题核心​:
当用户旋转屏幕导致 Activity 销毁时,如果延迟消息尚未执行,这条消息会通过 Message → Handler → Activity 的引用链,阻止 Activity 被回收。
就像你借了别人的充电宝(Activity),但对方(Handler)还没用完(消息未处理),充电宝就一直没法归还(内存泄漏)。


面试官​:那具体是怎么形成引用链的?能不能从源码层面解释?

候选人​:
没问题,我们从源码看泄漏链路:

  1. Message 持有 Handler​:

    // Message 类
    public class Message implements Parcelable {Handler target;  // 发送消息的 Handler
    }

    当调用 handler.sendMessage(msg) 时,msg.target 被赋值为当前 Handler。

  2. 非静态 Handler 持有 Activity​:
    非静态内部类 Handler 隐式持有外部 Activity 的引用(类似 MainActivity.this)。

  3. MessageQueue 持有 Message​:

    // MessageQueue 内部维护消息链表
    Message mMessages;  // 链表头节点

引用链​:
MessageQueue → Message → Handler → Activity
即使 Activity 销毁,只要消息还在队列中,这条链就会阻止 GC 回收 Activity。


面试官​:你们项目里是怎么解决这个问题的?有实际案例吗?

候选人​:
我们项目里用 ​静态内部类 + 弱引用 + 生命周期管理​ 三管齐下。举个实际场景:

场景​:在直播间发送弹幕,需要 Handler 定时刷新 UI,同时处理礼物动画回调。

解决方案​:

  1. 静态 Handler + 弱引用​:

    private static class SafeHandler extends Handler {private final WeakReference<LiveRoomActivity> activityRef;public SafeHandler(LiveRoomActivity activity) {activityRef = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {LiveRoomActivity activity = activityRef.get();if (activity == null || activity.isDestroyed()) return;switch (msg.what) {case MSG_UPDATE_DANMU:activity.updateDanmuList();break;case MSG_SHOW_GIFT:activity.playGiftAnimation();break;}}
    }
  2. 生命周期管理​:

    @Override
    protected void onDestroy() {super.onDestroy();// 移除所有消息,彻底断掉引用链mHandler.removeCallbacksAndMessages(null);
    }
  3. 消息复用优化​:

    // 复用消息对象,避免频繁创建
    Message msg = Message.obtain();
    msg.what = MSG_UPDATE_DANMU;
    mHandler.sendMessageDelayed(msg, 1000);

效果​:直播间频繁进出测试中,内存泄漏率降为 0,ANR 减少 30%。


面试官​:如果不用弱引用,直接在 onDestroy 移除消息能解决问题吗?

候选人​:
可以,但不完全可靠。比如以下场景:

  1. 异步回调延迟​:
    网络请求在 onDestroy 之后才返回,回调中调用 Handler 发送消息,此时 Handler 可能已经销毁,导致空指针。

  2. 多线程竞争​:
    如果子线程在 onDestroy 执行过程中发送消息,可能漏删消息。

最佳实践​:
双重保险——弱引用防止意外持有,onDestroy 移除消息确保彻底清理。就像出门时既锁门(移除消息)又带钥匙(弱引用),双重保障。


面试官​:有没有遇到过 Handler 导致的内存泄漏很难排查?怎么解决的?

候选人​:
确实遇到过。有一次线上报 OOM,但 LeakCanary 没抓到明显泄漏。后来用 ​Android Profiler + 代码审查​ 才定位到问题。

排查过程​:

  1. Profiler 抓堆转储​:
    发现 Activity 实例数量异常,存活时间远超生命周期。

  2. 分析引用链​:
    发现某个 Message 持有自定义 Handler 子类,而 Handler 持有 Activity。

  3. 代码审查​:
    发现同事写了一个 ​匿名 Handler 子类,在自定义 View 中发送延迟消息,但未及时移除。

修复方案​:

  • 将匿名 Handler 改为静态内部类 + 弱引用;
  • 在 View 的 onDetachedFromWindow 中移除消息。

教训​:
匿名内部类 Handler 是隐藏杀手,必须强制代码规范审查。


面试官​:如果用 Kotlin 协程或 LiveData 替代 Handler,能完全避免泄漏吗?

候选人​:
大部分情况可以,但需要正确使用。比如:

协程方案​:

// 在 ViewModel 中启动协程
viewModelScope.launch {val data = withContext(Dispatchers.IO) { fetchData() } // 子线程执行_uiState.value = data // 主线程更新
}

LiveData 方案​:

public class MyViewModel extends ViewModel {private MutableLiveData<String> data = new MutableLiveData<>();void loadData() {Executors.io().execute(() -> {String result = fetchData();data.postValue(result); // 自动切主线程});}
}

优势​:

  • 自动绑定生命周期,Activity 销毁时自动取消订阅;
  • 无需手动管理消息队列,代码更简洁。

注意点​:
如果协程或 LiveData 持有 Context 引用(如误用 requireContext()),仍可能泄漏。所以关键还是遵循 ​生命周期感知​ 原则。

大厂高频追问答案​:

  • 问:为什么匿名 Runnable 也会导致泄漏?​
    ​:匿名 Runnable 是匿名内部类,隐式持有外部类(如 Activity)引用。如果通过 postDelayed 发送,消息会持有 Runnable → Activity 的引用链。

  • 问:主线程的 Looper 为什么不会泄漏?​
    ​:主线程的 Looper 生命周期和进程一致,不需要回收。而子线程的 Looper 必须手动 quit(),否则线程无法结束,导致 Handler 持续持有引用。

  • 问:如何检测 MessageQueue 中的残留消息?​
    ​:通过反射获取 MessageQueue.mMessages 链表,遍历检查是否有未处理的 Message 指向目标 Handler。​


面试官​:听说你在项目里用过 Handler,能聊聊它的工作原理吗?比如 post()postDelayed() 有什么区别?

候选人​:
当然可以!其实 post()postDelayed() 骨子里是同一个方法,就像双胞胎兄弟。比如 post() 底层调用的是 sendMessageDelayed(..., 0),而 postDelayed() 只是多传了个延迟时间参数。

举个例子吧:

// 这两个调用本质上是一样的
handler.post(() -> updateUI());  
handler.postDelayed(() -> updateUI(), 0); // 效果和 post() 一样

但细节上有点差别——post() 会直接把消息塞到队列头部,而 postDelayed(0) 是按时间排序插入。如果队列里已经有消息在排队,postDelayed(0) 的消息可能得等前面的处理完才能执行。


面试官​:听起来像是延迟时间的问题。那如果我在 Activity 里用 postDelayed() 发了个 10 分钟的延迟任务,退出 Activity 后会有问题吗?

候选人​:
这问题我们踩过坑!如果 Handler 是非静态内部类,消息会一直抓着 Activity 不放,就像有人借了你的充电宝(Activity)不还,结果你手机(内存)直接没电(OOM)。

具体原因​:
消息队列(MessageQueue)里那个延迟 10 分钟的任务还没执行,而消息 → Handler → Activity 这条链子会一直存在。就算用户退出了 Activity,这条链子也会让 Activity 卡在内存里没法回收。


面试官​:那你们是怎么解决的?总不能不用 Handler 了吧?

候选人​:
我们用了 ​三重防御​:

  1. 静态内部类​:把 Handler 变成“工具人”,不跟 Activity 绑定;
  2. 弱引用​:加个橡皮筋(WeakReference),Activity 被回收时自动松手;
  3. 生命周期管理​:在 onDestroy() 里清空消息队列,就像退房前关水电。

比如这样改代码:

private static class SafeHandler extends Handler {private WeakReference<Activity> weakActivity; // 橡皮筋绑着 Activity@Overridepublic void handleMessage(Message msg) {Activity activity = weakActivity.get();if (activity == null) return; // 发现 Activity 没了就收工// 安全干活...}
}// Activity 销毁时彻底清理
@Override
protected void onDestroy() {super.onDestroy();handler.removeCallbacksAndMessages(null); // 把消息队列全清空
}

面试官​:如果不用弱引用,只在 onDestroy 移除消息够吗?

候选人​:
不够稳!我们有个血的教训:同事在 onDestroy 里漏删了一条消息,结果用户快速进出页面 10 次后直接闪退。后来用 ​LeakCanary​ 一查,发现 8 个 Activity 尸体躺在内存里!

排查过程​:

  1. LeakCanary 显示引用链是 Message → Handler → Activity
  2. 发现是某个网络回调在 Activity 销毁后调了 Handler;
  3. 最后在基类 Activity 的 onDestroy 加了个全局清空消息的逻辑,一劳永逸。

面试官​:如果让你设计一个图片下载库,用 HandlerThread 还是线程池?

候选人​:
果断选线程池!比如这样设计:

// 开个 4 线程的池子,并发下载
ExecutorService pool = Executors.newFixedThreadPool(4); pool.execute(() -> {Bitmap bitmap = downloadImage(url); // 子线程下载runOnUiThread(() -> imageView.setImageBitmap(bitmap)); // 切主线程更新
});

理由​:

  • 线程池能并发处理多张图片,速度比单线程的 HandlerThread 快多了;
  • 可以控制最大线程数(比如 4 个),避免手机 CPU 被吃满;
  • 复用线程资源,不像频繁创建 Thread 那样浪费内存。

Android面试总结之Handler 机制深入探讨原理、应用与优化_android handler原理 面试-CSDN博客https://blog.csdn.net/2301_80329517/article/details/146558080

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/82327.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【iOS】分类、扩展、关联对象

分类、扩展、关联对象 前言分类扩展扩展和分类的区别关联对象key的几种用法流程 总结 前言 最近的学习中笔者发现自己对于分类、扩展相关知识并不是很熟悉&#xff0c;刚好看源码类的加载过程中发现有类扩展与关联对象详解。本篇我们来探索一下这部分相关知识&#xff0c;首先…

30.第二阶段x64游戏实战-认识网络数据包发送流程

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 上一个内容&#xff1a;29.第二阶段x64游戏实战-技能冷却 发送数据包的方式&#xff08;函数&#xff09;操作系统提供…

【每日一题】【前缀和优化】【前/后缀最值】牛客练习赛139 B/C题 大卫的密码 (Hard Version) C++

牛客练习赛139 B题 大卫的密码 (Easy Version) 牛客练习赛139 C题 大卫的密码 (Hard Version) 大卫的密码 题目背景 牛客练习赛139 题目描述 给定一个 n m n\times m nm的网格图&#xff0c;我们使用 ( i , j ) (i,j) (i,j)表示网格中从上往下数第 i i i行和从左往右数第…

文件夹图像批处理教程

前言 因为经常对图像要做数据清洗&#xff0c;又很费时间去重新写一个&#xff0c;我一直在想能不能写一个通用的脚本或者制作一个可视化的界面对文件夹图像做批量的修改图像大小、重命名、划分数据训练和验证集等等。这里我先介绍一下我因为写过的一些脚本&#xff0c;然后我…

【Unity实战笔记】第二十四 · 使用 SMB+Animator 实现基础战斗系统

转载请注明出处&#xff1a;&#x1f517;https://blog.csdn.net/weixin_44013533/article/details/146409453 作者&#xff1a;CSDN|Ringleader| 1 结构 1.1 状态机 1.2 SMB 2 代码实现 2.1 核心控制 Player_Base_SMB 继承 StateMachineBehaviour &#xff0c;控制变量初始…

Python虚拟环境再PyCharm中自由切换使用方法

Python开发中的环境隔离是必不可少的步骤,通过使用虚拟环境可以有效地管理不同项目间的依赖,避免包冲突和环境污染。虚拟环境是Python官方提供的一种独立运行环境,每个项目可以拥有自己单独的环境,不同项目之间的环境互不影响。在日常开发中,结合PyCharm这样强大的IDE进行…

大模型智能体入门扫盲——基于camel的概述

前言 本篇博客想带读者进行一个智能体入门扫盲&#xff0c;了解基础知识&#xff0c;为什么用camel呢&#xff0c;因为小洛发现它们文档对这种智能体的基本组件介绍得很全面深入。 基础概念 agent 一个典型的agent智能体包含三个核心部分&#xff1a; 感知模块&#xff1…

目标检测 RT-DETR(2023)详细解读

文章目录 主干网络&#xff1a;Encoder&#xff1a;不确定性最小Query选择Decoder网络&#xff1a; 将DETR扩展到实时场景&#xff0c;提高了模型的检测速度。网络架构分为三部分组成&#xff1a;主干网络、混合编码器、带有辅助预测头的变换器编码器。具体来说&#xff0c;先利…

DeepSeek 赋能数字农业:从智慧种植到产业升级的全链条革新

目录 一、数字农业的现状与挑战二、DeepSeek 技术解析2.1 DeepSeek 的技术原理与优势2.2 DeepSeek 在人工智能领域的地位与影响力 三、DeepSeek 在数字农业中的应用场景3.1 精准种植决策3.2 病虫害监测与防治3.3 智能灌溉与施肥管理3.4 农产品质量追溯与品牌建设 四、DeepSeek …

<uniapp><vuex><状态管理>在uniapp中,如何使用vuex实现数据共享与传递?

前言 本专栏是基于uniapp实现手机端各种小功能的程序&#xff0c;并且基于各种通讯协议如http、websocekt等&#xff0c;实现手机端作为客户端&#xff08;或者是手持机、PDA等&#xff09;&#xff0c;与服务端进行数据通讯的实例开发。 发文平台 CSDN 环境配置 系统&…

高速串行差分信号仿真分析及技术发展挑战续

7.3 3.125Gbps 差分串行信号设计实例仿真分析 7.3.1 设计用例说明 介绍完 Cadence 系统本身所具有的高速差分信号的仿真分析功能之后&#xff0c;我们以一个实例来说明 3.125Gbps 以下的高速差分系统的仿真分析方法。 在网上下载的设计文件“Booksi_Demo_Allegro160_Finishe…

【Golang】部分语法格式和规则

1、时间字符串和时间戳的相互转换 func main() {t1 : int64(1546926630) // 外部传入的时间戳&#xff08;秒为单位&#xff09;&#xff0c;必须为int64类型t2 : "2019-01-08 13:50:30" // 外部传入的时间字符串//时间转换的模板&#xff0c;golang里面只能是 &quo…

第十六章:数据治理之数据架构:数据模型和数据流转关系

本章我们说一下数据架构&#xff0c;说到数据架构&#xff0c;就很自然的想到企业架构、业务架构、软件架构&#xff0c;因为个人并没有对这些内容进行深入了解&#xff0c;所以这里不做比对是否有相似或者共通的地方&#xff0c;仅仅来说一下我理解的数据架构。 1、什么是架构…

Day126 | 灵神 | 二叉树 | 层数最深的叶子结点的和

Day126 | 灵神 | 二叉树 | 层数最深的叶子结点的和 1302.层数最深的叶子结点的和 1302. 层数最深叶子节点的和 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 这道题用层序遍历的思路比较好想&#xff0c;就把每层的都算一下&#xff0c;然后返回最后一层的和就…

PCIE 4.0 vs PCIE 5.0固态硬盘——区别、科普与选购场景全解析

随着数字内容和高性能计算需求的爆发&#xff0c;固态硬盘&#xff08;SSD&#xff09;已成为PC、游戏主机和工作站不可或缺的核心硬件。面对市面上层出不穷的新一代SSD产品&#xff0c;大家最常见的一个疑惑&#xff1a;**PCIe 4.0和PCIe 5.0固态硬盘&#xff0c;到底有啥区别…

vue pinia 独立维护,仓库统一导出

它允许您跨组件/页面共享状态 持久化 安装依赖pnpm i pinia-plugin-persistedstate 将插件添加到 pinia 实例上 pinia独立维护 统一导出 import { createPinia } from pinia import piniaPluginPersistedstate from pinia-plugin-persistedstateconst pinia creat…

Dify源码学习

文章目录 1 大模型基本原理1.1 model_context_tokens、max_tokens和prompt_tokens1.1.1 三者之间的关系1.1.2 总结对比 2 Dify源代码2.0 前后端代码跑起来【0】准备开发环境【1】下载代码【2】运行后端&#xff08;1&#xff09;Start the docker-compose stack&#xff08;2&a…

连接表、视图和存储过程

1. 视图 1.1. 视图的概念 视图&#xff08;View&#xff09;&#xff1a;虚拟表&#xff0c;本身不存储数据&#xff0c;而是封装了一个 SQL 查询的结果集。 用途&#xff1a; 只显示部分数据&#xff0c;提高数据访问的安全性。简化复杂查询&#xff0c;提高复用性和可维护…

微信小程序中,解决lottie动画在真机不显示的问题

api部分 export function getRainInfo() {return onlineRequest({url: /ball/recruit/getRainInfo,method: get}); }data存储json数据 data&#xff1a;{rainJson:{} }onLoad方法获取json数据 onLoad(options) {let that thisgetRainInfo().then((res)>{that.setData({r…

从加密到信任|密码重塑车路云一体化安全生态

目录 一、密码技术的核心支撑 二、典型应用案例 三、未来发展方向 总结 车路云系统涉及海量实时数据交互&#xff0c;包括车辆位置、传感器信息、用户身份等敏感数据。其安全风险呈现三大特征&#xff1a; 开放环境威胁&#xff1a;V2X&#xff08;车与万物互联&#xff0…