Android第十一次面试多线程篇

news2025/6/3 7:15:07

面试官​:
“你在项目里用过Handler吗?能说说它是怎么工作的吗?”

候选人​:
“当然用过!比如之前做下载功能时,需要在后台线程下载文件,然后在主线程更新进度条。这时候就得用Handler来切回主线程。简单来说,Handler就像个快递员,负责把子线程的任务包裹(比如更新UI的指令)送到主线程去执行。”

面试官追问​:
“比喻挺有意思。那这个‘快递员’是怎么把消息从子线程送到主线程的?”

候选人​:
“其实核心是靠三个东西:HandlerLooperMessageQueue。比如主线程启动时,系统会默认创建一个Looper,它内部维护了一个消息队列(MessageQueue)。当我在子线程通过Handler发送消息,这个消息会被放到主线程的队列里。然后主线程的Looper会循环检查队列,一有消息就取出来,交给对应的Handler处理。”


面试官​:
“那如果我在子线程里直接new一个Handler,会有什么问题吗?”

候选人​:
“这里有个坑!如果子线程没有提前准备Looper,直接new Handler会崩溃。比如这样——”

new Thread(() -> {
    // ❌ 错误写法:子线程默认没有Looper
    Handler handler = new Handler();
}).start();

“正确的做法是先调用Looper.prepare()创建Looper,再启动循环:”

new Thread(() -> {
    Looper.prepare();  // ✅ 初始化Looper
    Handler handler = new Handler();
    Looper.loop();     // 启动消息循环
}).start();

面试官​:
“提到主线程的Looper,你知道它是怎么初始化的吗?”

候选人​:
“这个我之前研究过源码!主线程的Looper是在ActivityThreadmain()方法里初始化的。大概流程是:”

  1. 启动App​:系统调用ActivityThread.main()
  2. 准备Looper​:调用Looper.prepareMainLooper(),创建主线程的Looper和消息队列。
  3. 开启循环​:调用Looper.loop(),让主线程进入无限循环,不断处理消息(比如点击事件、UI更新)。
    “所以主线程的Handler能一直运行,全靠这个死循环撑着。”

面试官​:
“实际开发中有没有遇到过Handler导致的问题?比如内存泄漏。”

候选人​:
“遇到过!比如在Activity里声明一个非静态内部类的Handler,如果Activity关闭时还有未处理的消息,Handler会持有Activity的引用,导致内存泄漏。我们项目里用了一个经典解法——”

// ✅ 正确写法:静态内部类 + 弱引用
static class SafeHandler extends Handler {
    private WeakReference<Activity> mActivity;

    SafeHandler(Activity activity) {
        mActivity = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        Activity activity = mActivity.get();
        if (activity == null) return;
        // 处理消息...
    }
}

“另外,在Activity的onDestroy()里,还要调用handler.removeCallbacksAndMessages(null)清空所有消息。”


面试官​:
“假设现在有个需求:每隔1秒更新一次UI上的计时器。用Handler怎么实现?”

候选人​:
“可以用postDelayed()递归调用。比如这样——”

private int count = 0;
private Handler handler = new Handler();

private void startTimer() {
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            textView.setText(String.valueOf(++count));
            handler.postDelayed(this, 1000); // 递归调用
        }
    }, 1000);
}

// 停止时调用
private void stopTimer() {
    handler.removeCallbacksAndMessages(null);
}

“不过要注意及时移除回调,否则退出页面时可能还在后台跑。”


面试官​:
“如果不用Handler,还有其他方式实现线程间通信吗?”

候选人​:
“当然!比如用AsyncTask(虽然过时了)、LiveData+协程,或者RxJava的线程切换。比如协程可以这样写——”

// 在ViewModel里
fun startTask() {
    viewModelScope.launch(Dispatchers.IO) {
        val data = fetchData() // 后台执行
        withContext(Dispatchers.Main) {
            updateUI(data)      // 切回主线程
        }
    }
}

“不过Handler的优势是更底层,适合需要精细控制消息队列的场景,比如实现定时任务或延迟操作。”


面试官​:
“最后一个问题:为什么主线程的Looper不会导致ANR?”

候选人​:
“这是个好问题!虽然Looper.loop()是死循环,但主线程大部分时间处于休眠状态,通过Linux的epoll机制监听消息队列。当没有消息时,线程会释放CPU进入休眠;有新消息(比如点击事件、屏幕刷新信号)时,线程被唤醒处理消息。所以只要不在主线程做耗时操作,循环本身不会阻塞,也就不会ANR。”


面试官​:
“回答得很清晰。你还有什么问题想问吗?”

候选人​:
“咱们项目中有没有特别依赖Handler的场景?比如自定义消息协议或者复杂定时任务?”

面试官​:
(假设回答)
“有的!比如IM模块的消息重发机制,用Handler管理消息的延迟重试;还有首页的轮播图动画,用Handler的postDelayed实现自动切换。之后你可以参与这部分优化。”

Handler 的工作流程与底层原理

1. ​核心组件及其关系
  • Handler​:消息的发送者和处理者。
  • Looper​:消息循环的核心,每个线程有且只有一个。
  • MessageQueue​:消息队列,按时间排序存储消息(单链表实现)。
  • Message​:携带数据和目标Handler的单元。

关系图​:

Thread
  ├── Looper
  │     └── MessageQueue
  └── Handler(关联到Looper)
2. ​工作流程
步骤1:初始化Looper(子线程)​
  • 子线程中必须手动调用 Looper.prepare() 创建Looper。
  • Looper.loop() 启动消息循环。
new Thread(() -> {
    Looper.prepare(); // 初始化Looper(内部创建MessageQueue)
    Handler handler = new Handler(Looper.myLooper());
    Looper.loop();    // 开始循环处理消息
}).start();
步骤2:发送消息
  • 通过 Handler.sendMessage()Handler.post(Runnable) 发送消息。
Message msg = Message.obtain();
msg.what = 1;
msg.obj = "Data";
handler.sendMessage(msg); // 消息入队到MessageQueue
步骤3:消息处理
  • Looper从MessageQueue取出消息,调用目标Handler的 dispatchMessage()
  • 最终触发 handleMessage()Runnable.run()
public void handleMessage(Message msg) {
    switch (msg.what) {
        case 1: updateUI((String) msg.obj); break;
    }
}
3. ​底层原理
消息存储(MessageQueue)​
  • 数据结构​:单链表按 when(执行时间)排序。
  • 入队操作​:enqueueMessage() 添加消息到链表合适位置。
  • 出队操作​:next() 取出下一条消息(可能阻塞)。
消息循环(Looper.loop())​
  • 无限循环​:持续调用 MessageQueue.next()
  • 阻塞唤醒机制​:
    • 队列为空时,通过 epollpipe 进入休眠。
    • 新消息入队时唤醒线程(通过 nativeWake())。
线程隔离(ThreadLocal)​
  • Looper存储​:每个线程的Looper实例通过 ThreadLocal<Looper> 保存。
  • 获取方式​:Looper.myLooper() 返回当前线程的Looper。
4. ​主线程的Looper
  • 默认初始化​:ActivityThread的 main() 方法调用 Looper.prepareMainLooper()
  • 永不退出​:主线程的Looper循环保证应用持续响应事件。
// ActivityThread.java
public static void main(String[] args) {
    Looper.prepareMainLooper(); 
    Looper.loop(); // 主线程进入无限循环
}
5. ​性能优化与注意事项
  • 避免阻塞主线程​:耗时操作仍要放在子线程。
  • 内存泄漏​:Handler持有外部类(如Activity)引用时,需用弱引用。
static class SafeHandler extends Handler {
    private WeakReference<Activity> mActivity;
    SafeHandler(Activity activity) {
        mActivity = new WeakReference<>(activity);
    }
    public void handleMessage(Message msg) {
        Activity activity = mActivity.get();
        if (activity == null) return;
        // 处理消息
    }
}
6. ​代码示例:子线程与主线程通信
// 主线程创建Handler
Handler mainHandler = new Handler(Looper.getMainLooper());

// 子线程执行任务后更新UI
new Thread(() -> {
    String result = doBackgroundWork();
    mainHandler.post(() -> textView.setText(result)); // 切换到主线程
}).start();

Kotlin协程详解:原理、优势与应用场景

一、协程的核心概念

协程(Coroutine)​​ 是一种轻量级的并发设计模式,允许以同步编码风格实现异步任务。它通过挂起(Suspend)和恢复(Resume)机制管理任务,避免传统多线程开发的复杂性。


二、协程为何优于线程?​
对比维度协程线程
创建开销极低(约几十字节)高(约1MB内存 + 系统调用开销)
切换成本无需操作系统介入(用户态切换)需内核介入(上下文切换开销大)
并发数量单线程可运行数万协程受限于系统资源(通常数百个)
开发复杂度同步代码风格,逻辑清晰需处理锁、回调、线程同步等问题
资源利用率挂起时释放线程,避免阻塞线程阻塞时资源浪费

三、协程的工作原理
1. 挂起与恢复
  • 挂起函数(Suspend Function)​​:
    使用suspend关键字标记的函数,可在不阻塞线程的情况下暂停执行(如等待网络响应)。
    suspend fun fetchData(): String {
        delay(1000) // 模拟耗时操作,非阻塞
        return "Data loaded"
    }
  • 底层机制​:
    协程通过状态机Continuation实现挂起恢复。编译器将挂起函数转换为带有回调的状态机代码。
2. 协程调度器(Dispatchers)​
  • 调度线程池​:
    launch(Dispatchers.IO) { /* 执行IO操作 */ }
    launch(Dispatchers.Main) { /* 更新UI */ }
  • 调度策略​:
    • IO​:适用于网络、文件操作(线程池优化)
    • Default​:CPU密集型任务(如排序、计算)
    • Main​:UI线程操作(Android主线程)
3. 结构化并发
  • 协程作用域(CoroutineScope)​​:
    管理协程生命周期(如viewModelScopelifecycleScope),确保任务不会泄漏。
    viewModelScope.launch {
        val data = fetchData() // 自动绑定ViewModel生命周期
        updateUI(data)
    }

四、协程如何避免回调地狱?​
1. 回调地狱示例
// 传统嵌套回调
api.getUser { user ->
    api.getProfile(user) { profile ->
        api.getFriends(profile) { friends ->
            updateUI(friends) // 嵌套层级深,难维护
        }
    }
}
2. 协程解决方案
viewModelScope.launch {
    try {
        val user = api.getUser()        // 挂起,不阻塞线程
        val profile = api.getProfile(user)
        val friends = api.getFriends(profile)
        updateUI(friends)               // 顺序执行,逻辑清晰
    } catch (e: Exception) {
        showError(e)
    }
}
  • 优势​:
    • 线性代码​:消除嵌套,可读性高
    • 统一异常处理​:try/catch捕获所有异步错误

五、协程的典型应用场景
  1. 网络请求​:
    suspend fun loadData() = withContext(Dispatchers.IO) {
        retrofitService.fetchData()
    }
  2. 数据库操作​:
    fun insertUser(user: User) = viewModelScope.launch(Dispatchers.IO) {
        database.userDao().insert(user)
    }
  3. 并发任务组合​:
    val result1 = async { task1() }
    val result2 = async { task2() }
    val combined = result1.await() + result2.await() // 并行执行
  4. 超时与重试​:
    val data = withTimeoutOrNull(5000) { // 5秒超时
        retry(3) { // 重试3次
            fetchData()
        }
    }

六、协程与线程的底层协作
  • 线程池复用​:
    协程调度器基于ExecutorService,通过线程池复用减少开销。
  • 挂起优化​:
    挂起时释放线程资源,交由其他协程使用,最大化利用CPU。

Kotlin协程实战

面试官​:
“你在项目里用过Kotlin协程吗?能举个实际例子说说它解决了什么问题吗?”

候选人​:
“当然!之前做商品详情页的时候,需要同时调三个接口:商品信息、用户评论、推荐列表。如果用传统的回调,代码会嵌套三层,像俄罗斯套娃一样,维护起来特别头疼。后来换成协程,代码直接‘拉直’了——”

viewModelScope.launch {
    try {
        // 同步写法,其实是异步执行!
        val product = api.fetchProductDetails()  // 第一个接口
        val comments = api.fetchComments(product.id)  // 等第一个完成后调第二个
        val recommendations = api.fetchRecommendations()  // 等前两个都完成
        
        // 统一更新UI
        showData(product, comments, recommendations)
    } catch (e: Exception) {
        // 一个try/catch抓住所有网络错误
        showErrorToast("加载失败,请重试")
    }
}

“这样一来,代码像写同步逻辑一样直观,新人也能快速看懂,而且异常处理集中,不会漏掉某个回调里的错误。”


面试官追问​:
“听起来确实简洁。那协程到底是怎么做到‘假装’同步的?底层不会卡住主线程吗?”

候选人​:
“这就是协程的聪明之处!比如api.fetchProductDetails()是个挂起函数,执行到的时候,协程会悄悄挂起,把线程让出来去处理其他任务。等网络数据回来了,协程再‘醒来’,从刚才挂起的位置继续执行。整个过程主线程完全没被阻塞,用户滑屏幕照样流畅。”

面试官​:
“那如果我要同时调三个接口,等所有结果一起回来再刷新界面,用协程怎么优化?”

候选人​:
“这时候可以用async并发!比如这样——”

viewModelScope.launch {
    // 同时发起三个请求
    val productDeferred = async { api.fetchProductDetails() }
    val commentsDeferred = async { api.fetchComments() }
    val recommendationsDeferred = async { api.fetchRecommendations() }
    
    // 等三个全部完成(实际耗时等于最慢的那个接口)
    val product = productDeferred.await()
    val comments = commentsDeferred.await()
    val recommendations = recommendationsDeferred.await()
    
    updateUI(product, comments, recommendations)
}

“比串行调用快多了!之前用回调得用计数器或者RxJava的zip操作符,协程两行代码搞定。”


面试官​:
“协程会不会导致内存泄漏?比如页面关了但请求还没回来。”

候选人​:
“这就是结构化并发的优势了!比如用viewModelScope启动协程,当ViewModel被销毁时,所有关联的协程会自动取消。如果这时候正在等网络请求,会直接中断,避免内存泄漏。我们之前有个页面没注意这个,用户快速进出会导致旧的请求继续回调,用了viewModelScope后问题彻底解决。”


面试官​:
“如果遇到老代码用回调的API,怎么接入协程?”

候选人​:
“Kotlin给了逃生舱!比如有个老版SDK用回调返回数据,可以用suspendCoroutine把它包成挂起函数——”

suspend fun legacyFetchData(): String = suspendCoroutine { continuation ->
    legacyApi.getData(object : Callback {
        override fun onSuccess(result: String) {
            continuation.resume(result) // 成功时恢复协程
        }
        override fun onFailure(error: Throwable) {
            continuation.resumeWithException(error) // 抛异常
        }
    })
}

// 新代码里直接调用
viewModelScope.launch {
    val data = legacyFetchData() // 像普通挂起函数一样用
}

“这样旧代码也能融入协程体系,团队迁移成本低。”


面试官​:
“假设你要下载10张图片,怎么用协程控制并发,同时不拖垮手机?”

候选人​:
“这时候得用协程的调度器!比如这样——”

// 限制最多同时3个下载
val dispatcher = Dispatchers.IO.limitedParallelism(3) 
viewModelScope.launch {
    val jobs = (1..10).map { index ->
        launch(dispatcher) {  // 限制并发的协程池
            downloadImage(index) 
        }
    }
    jobs.joinAll() // 等所有下载完成
    showToast("下载完成!")
}

“如果用普通线程池,开10个线程内存可能扛不住。协程的Dispatchers.IO自带线程复用,加上并发限制,既高效又省资源。”


面试官​:
“最后一个问题:为什么说协程适合Android开发?”

候选人​:
“三个字——快、稳、省!

  1. ​:代码写起来像同步,维护效率高,再也不用和回调地狱搏斗。
  2. ​:结构化并发自动管理生命周期,结合viewModelScope/lifecycleScope,内存泄漏少一半。
  3. ​:一个线程跑几百个协程,用过的都说像‘开挂’,尤其低端机上效果明显。”

面试官​:
“回答得很到位。你还有什么问题吗?”

候选人​:
“咱们团队用协程遇到过印象深刻的坑吗?比如和LiveData、Room结合时的注意点?”

面试官​:
(假设回答)
“早期在Room里混用协程和RxJava时有过线程冲突,后来统一用协程+Flow就解决了。你提到的Dispatchers.IO限制并发,我们在大文件上传模块也用过类似优化!”


线程池实战

面试官​:
“你在项目中是怎么管理多线程任务的?比如同时处理多个网络请求。”

候选人​:
“我们用的是线程池。比如在APP首页要同时加载多个图片,如果每个请求都开一个新线程,手机扛不住,容易卡顿或者OOM。线程池就像个‘线程调度中心’,复用已有的线程,避免频繁创建销毁的开销。”

面试官追问​:
“那具体怎么配置线程池?比如核心线程数、队列这些参数怎么定?”

候选人​:
“得看任务类型。比如我们有个需求是批量上传图片,这种IO密集型任务,核心线程数可以设成CPU核心数的两倍左右,比如4核手机就设8个。队列用无界队列(比如LinkedBlockingQueue),保证任务不丢失。但如果是CPU密集型任务,比如视频转码,核心线程数应该和CPU数差不多,避免线程切换拖慢速度。”


面试官​:
“如果任务太多,线程池处理不过来了怎么办?”

候选人​:
“这时候就得看拒绝策略了。比如我们遇到过用户疯狂点击触发大量请求,队列满了之后默认策略是抛异常,结果直接崩溃。后来改成了CallerRunsPolicy,让提交任务的线程自己执行,这样至少不会崩,但得注意别阻塞主线程。”

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    4, // 核心线程
    8, // 最大线程
    30, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100), // 队列容量100
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

面试官​:
“提到线程池类型,你知道Executors.newCachedThreadPool()有什么坑吗?”

候选人​:
“这个坑踩过!CachedThreadPool的最大线程数设置的是Integer.MAX_VALUE,如果任务无限提交,线程数暴增,直接OOM。我们之前有个日志上报功能用了它,结果用户疯狂操作时内存爆炸。后来改成自定义线程池,限制最大线程数,问题才解决。”


面试官​:
“如果让你设计一个定时任务,比如每隔5秒检查一次消息,用线程池怎么实现?”

候选人​:
“可以用ScheduledThreadPool,比如这样——”

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
    checkNewMessage(); // 执行任务
}, 0, 5, TimeUnit.SECONDS);

“不过要注意任务执行时间别超过间隔,否则会堆积。比如任务要跑10秒,间隔设5秒,就会变成每10秒执行一次。”


面试官​:
“实际开发中,怎么确保线程池里的任务不被重复执行?”

候选人​:
“我们项目里遇到过!比如用户快速点击按钮触发多次提交。解决办法是给任务加唯一ID,用ConcurrentHashMap记录正在执行的任务,提交前先检查是否存在——”

ConcurrentHashMap<String, Boolean> taskMap = new ConcurrentHashMap<>();

void submitTask(String taskId, Runnable task) {
    if (taskMap.putIfAbsent(taskId, true) == null) {
        executor.execute(() -> {
            try {
                task.run();
            } finally {
                taskMap.remove(taskId);
            }
        });
    }
}

面试官​:
“最后一个问题:为什么推荐用ThreadPoolExecutor而不是Executors?”

候选人​:
Executors的方法虽然方便,但隐藏了参数细节,容易踩坑。比如newFixedThreadPool用的无界队列,任务堆积可能内存溢出。直接new ThreadPoolExecutor可以明确指定核心线程数、队列容量和拒绝策略,对资源控制更精细。”


面试官​:
“回答得很到位。你还有什么问题想问吗?”

候选人​:
“咱们项目里线程池一般用在哪些场景?有没有特别复杂的配置案例?”

面试官​:
(假设回答)
“比如首页的瀑布流图片加载用了IO密集型线程池,后台数据同步用了单线程池保证顺序。有个复杂案例是结合PriorityBlockingQueue实现任务优先级,高优先级任务插队执行。”

 Android第三次面试总结之activity和线程池篇(补充)_android线程池面试题-CSDN博客https://blog.csdn.net/2301_80329517/article/details/147700637

Android第三次面试总结(activity和线程池)_android线程池面试题-CSDN博客https://blog.csdn.net/2301_80329517/article/details/146325189?spm=1011.2415.3001.5331

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

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

相关文章

安全,稳定可靠的政企即时通讯数字化平台

在当今数字化时代&#xff0c;政企机构面临着复杂多变的业务需求和日益增长的沟通协作挑战。BeeWorks作为一款安全&#xff0c;稳定可靠的政企即时通讯数字化平台&#xff0c;凭借其安全可靠、功能强大的特性&#xff0c;为政企提供了高效、便捷的沟通协作解决方案&#xff0c;…

LiquiGen流体导入UE

导出ABC 导出贴图 ABC导入Houdini UE安装SideFX_Labs插件 C:\Users\Star\Documents\houdini20.5\SideFXLabs\unreal\5.5 参考: LiquiGenHoudiniUE血液流程_哔哩哔哩_bilibili

Ubuntu下编译mininim游戏全攻略

目录 一、安装mininim 软件所依赖的库&#xff08;重点是allegro游戏引擎库&#xff09;二、编译mininim 软件三、将mininim打包给另一个Ubuntu系统使用四、安卓手机运行mininim 一、安装mininim 软件所依赖的库&#xff08;重点是allegro游戏引擎库&#xff09; 1. 用apt-get…

uniapp uni-id Error: Invalid password secret

common文件夹下uni-config-center文件夹下新建uni-id,新建config.json文件 复制粘贴以下代码&#xff0c;不要自己改&#xff0c;格式容易错 {"passwordSecret": [{"type": "hmac-sha256","version": 1}], "passwordStrength&qu…

第十二节:第三部分:集合框架:List系列集合:特点、方法、遍历方式、ArrayList集合的底层原理

List系列集合特点 List集合的特有方法 List集合支持的遍历方式 ArrayList集合的底层原理 ArrayList集合适合的应用场景 代码&#xff1a;List系列集合遍历方式 package com.itheima.day19_Collection_List;import java.util.ArrayList; import java.util.Iterator; import jav…

【办公类-18-07】20250527屈光检查PDF文件拆分成多个pdf(两页一份,用幼儿班级姓名命名文件)

背景需求&#xff1a; 今天春游&#xff0c;上海海昌公园。路上保健老师收到前几天幼儿的屈光视力检查单PDF。 她说&#xff1a;所有孩子的通知都做在一个PDF里&#xff0c;我没法单独发给班主任。你有什么办法拆开来&#xff1f; 我说&#xff1a;“没问题&#xff0c;问deep…

AI Agent的“搜索大脑“进化史:从Google API到智能搜索生态的技术变革

AI Agent搜索革命的时代背景 2025年agent速度发展之快似乎正在验证"2025年是agent元年"的说法&#xff0c;而作为agent最主要的应用工具之一(另外一个是coding)&#xff0c;搜索工具也正在呈现快速的发展趋势。Google在2024年12月推出Gemini Deep Research&#xff0…

Arduino学习-跑马灯

1、效果 2、代码 /**** 2025-5-30 跑马灯的小程序 */ //时间间隔 int intervaltime200; //初始化函数 void setup() {// put your setup code here, to run once://设置第3-第7个引脚为输出模式for(int i3;i<8;i){pinMode(i,OUTPUT);} }//循环执行 void loop() {// put you…

2. 手写数字预测 gui版

2. 手写数字预测 gui版 背景1.界面绘制2.处理图片3. 加载模型4. 预测5.结果6.一点小问题 背景 做了手写数字预测的模型&#xff0c;但是老是跑模型太无聊了&#xff0c;就配合pyqt做了一个可视化界面出来玩一下 源代码可以去这里https://github.com/Leezed525/pytorch_toy拿 …

特别篇-产品经理(三)

一、市场与竞品分析—竞品分析 1. 课后总结 案例框架&#xff1a;通过"小新吃蛋糕"案例展示行业分析方法&#xff0c;包含四个关键步骤&#xff1a; 明确目标行业调研确定竞品分析竞争策略输出结论 1&#xff09;行业背景分析方法 PEST分析法&#xff1a;从四个…

【unity游戏开发——编辑器扩展】AssetDatabase公共类在编辑器环境中管理和操作项目中的资源

注意&#xff1a;考虑到编辑器扩展的内容比较多&#xff0c;我将编辑器扩展的内容分开&#xff0c;并全部整合放在【unity游戏开发——编辑器扩展】专栏里&#xff0c;感兴趣的小伙伴可以前往逐一查看学习。 文章目录 前言一、AssetDatabase常用API1、创建资源1.1 API1.2 示例 …

BLE协议全景图:从0开始理解低功耗蓝牙

BLE(Bluetooth Low Energy)作为一种针对低功耗场景优化的通信协议,已经广泛应用于智能穿戴、工业追踪、智能家居、医疗设备等领域。 本文是《BLE 协议实战详解》系列的第一篇,将从 BLE 的发展历史、协议栈结构、核心机制和应用领域出发,为后续工程实战打下全面认知基础。 …

【机器学习基础】机器学习入门核心算法:GBDT(Gradient Boosting Decision Tree)

机器学习入门核心算法&#xff1a;GBDT&#xff08;Gradient Boosting Decision Tree&#xff09; 1. 算法逻辑2. 算法原理与数学推导2.1 目标函数2.2 负梯度计算2.3 决策树拟合2.4 叶子权重计算2.5 模型更新 3. 模型评估评估指标防止过拟合 4. 应用案例4.1 金融风控4.2 推荐系…

基于开源AI大模型AI智能名片S2B2C商城小程序源码的销售环节数字化实现路径研究

摘要&#xff1a;在数字化浪潮下&#xff0c;企业销售环节的转型升级已成为提升竞争力的核心命题。本文基于清华大学全球产业研究院《中国企业数字化转型研究报告&#xff08;2020&#xff09;》提出的“提升销售率与利润率、打通客户数据、强化营销协同、构建全景用户画像、助…

Spring Cache核心原理与快速入门指南

文章目录 前言一、Spring Cache核心原理1.1 架构设计思想1.2 运行时执行流程1.3 核心组件协作1.4 关键机制详解1.5 扩展点设计1.6 与Spring事务的协同 二、快速入门实战三、局限性3.1 多级缓存一致性缺陷3.2 分布式锁能力缺失3.3 事务集成陷阱 总结 前言 在当今高并发、低延迟…

Redisson学习专栏(四):实战应用(分布式会话管理,延迟队列)

文章目录 前言一、为什么需要分布式会话管理&#xff1f;1.1 使用 Redisson 实现 Session 共享 二、订单超时未支付&#xff1f;用延迟队列精准处理2.1 RDelayedQueue 核心机制2.2 订单超时处理实战 总结 前言 在现代分布式系统中&#xff0c;会话管理和延迟任务处理是两个核心…

java程序从服务器端到Lambda函数的迁移与优化

source&#xff1a;https://www.jfokus.se/jfokus24-preso/From-Serverful-to-Serverless-Java.pdf 从传统的服务器端Java应用&#xff0c;到如今的无服务器架构。这不仅仅是技术名词的改变&#xff0c;更是开发模式和运维理念的一次深刻变革。先快速回顾一下我们熟悉的“服务…

使用yocto搭建qemuarm64环境

环境 yocto下载 # 源码下载 git clone git://git.yoctoproject.org/poky git reset --hard b223b6d533a6d617134c1c5bec8ed31657dd1268 构建 # 编译镜像 export MACHINE"qemuarm64" . oe-init-build-env bitbake core-image-full-cmdline 运行 # 跑虚拟机 export …

Linux系统下安装配置 Nginx

Windows Nginx https://nginx.org/en/download.htmlLinux Nginx https://nginx.org/download/nginx-1.24.0.tar.gz解压 tar -zxvf tar -zxvf nginx-1.18.0.tar.gz #解压安装依赖&#xff08;如未安装&#xff09; yum groupinstall "Development Tools" -y yum…

LiveGBS作为下级平台GB28181国标级联2016|2022对接海康大华宇视华为政务公安内网等GB28181国标平台查看级联状态及会话

LiveGBS作为下级平台GB28181国标级联2016|2022对接海康大华宇视华为政务公安内网等GB28181国标平台查看级联状态及会话 1、GB/T28181级联概述2、搭建GB28181国标流媒体平台3、获取上级平台接入信息3.1、向下级提供信息3.2、上级国标平台添加下级域3.3、接入LiveGBS示例 4、配置…