Android studio消息同步机制:消息本地存储,服务器交互减压

news2025/4/18 0:11:46

文章目录

      • 后端(Flask)代码
      • 前端(Android Studio Java)代码
      • 同步机制
        • 1. 放在 Activity 中
        • 2. 放在 Service 中
        • 3. 放在 DataManager 类中
      • 放在Service中的具体实现
        • 1. 后台执行
        • 2. 独立于活动
        • 3. 系统管理
        • 4. 绑定服务
        • 5. 进程间通信(IPC)
        • 6. 可配置的启动模式
        • 7. 可重用性
        • 8. 维护状态
      • 示例:将 syncMessagesFromServer方法放在 Service中
      • 总结


消息同步机制:
手机端可以将消息存储在本地数据库中,减少与服务器的交互压力。同时,通过序列号机制,手机端可以与服务器同步消息,确保消息列表的一致性。

我们将使用 SQLite 数据库 在手机端存储消息,并通过 序列号机制 实现消息的同步。

后端(Flask)代码

我们为每条消息添加一个序列号字段,并在发送消息时返回当前的最大序列号。

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_socketio import SocketIO, emit
import uuid

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///messages.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
socketio = SocketIO(app)

# 数据库模型
class Message(db.Model):
    id = db.Column(db.String(36), primary_key=True)  # 使用 UUID 作为唯一标识
    sender = db.Column(db.String(50), nullable=False)
    content = db.Column(db.String(255), nullable=False)
    timestamp = db.Column(db.DateTime, server_default=db.func.now())
    sequence = db.Column(db.Integer, unique=True, nullable=False)  # 添加序列号字段

# 初始化序列号
current_sequence = 0

@app.route('/send_message', methods=['POST'])
def send_message():
    global current_sequence
    data = request.json
    sender = data['sender']
    content = data['content']
    message_id = str(uuid.uuid4())  # 生成唯一标识

    # 更新序列号
    current_sequence += 1

    new_message = Message(id=message_id, sender=sender, content=content, sequence=current_sequence)
    db.session.add(new_message)
    db.session.commit()

    # 通过 WebSocket 发送消息到所有客户端
    socketio.emit('new_message', {'id': message_id, 'sender': sender, 'content': content, 'sequence': current_sequence})

    return jsonify({'status': 'success', 'message_id': message_id, 'sequence': current_sequence})

@app.route('/get_messages', methods=['GET'])
def get_messages():
    last_sequence = request.args.get('last_sequence', type=int, default=0)
    messages = Message.query.filter(Message.sequence > last_sequence).all()
    result = []
    for message in messages:
        result.append({
            'id': message.id,
            'sender': message.sender,
            'content': message.content,
            'timestamp': message.timestamp,
            'sequence': message.sequence
        })
    return jsonify(result)

if __name__ == '__main__':
    db.create_all()  # 创建数据库和表
    socketio.run(app, debug=True)

前端(Android Studio Java)代码

在手机端使用 SQLite 数据库存储消息,并通过序列号机制同步消息。

  1. 添加 SQLite 数据库支持
    build.gradle 文件中添加 SQLite 依赖:

    implementation 'androidx.sqlite:sqlite:2.3.1'
    implementation 'com.squareup.okhttp3:okhttp:4.9.3'
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'org.java-websocket:Java-WebSocket:1.5.2'
    
  2. 创建 SQLite 数据库和表
    创建一个 MessageDatabaseHelper 类来管理数据库和表。

    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    
    public class MessageDatabaseHelper extends SQLiteOpenHelper {
        private static final String DATABASE_NAME = "messages.db";
        private static final int DATABASE_VERSION = 1;
        private static final String TABLE_MESSAGES = "messages";
        private static final String COLUMN_ID = "id";
        private static final String COLUMN_SENDER = "sender";
        private static final String COLUMN_CONTENT = "content";
        private static final String COLUMN_SEQUENCE = "sequence";
    
        public MessageDatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
            String CREATE_TABLE = "CREATE TABLE " + TABLE_MESSAGES + "("
                    + COLUMN_ID + " TEXT PRIMARY KEY, "
                    + COLUMN_SENDER + " TEXT, "
                    + COLUMN_CONTENT + " TEXT, "
                    + COLUMN_SEQUENCE + " INTEGER)";
            db.execSQL(CREATE_TABLE);
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL("DROP TABLE IF EXISTS " + TABLE_MESSAGES);
            onCreate(db);
        }
    }
    
  3. MainActivity
    MainActivity 中添加消息存储和同步逻辑。

    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.ArrayAdapter;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.ListView;
    import androidx.appcompat.app.AppCompatActivity;
    import okhttp3.*;
    import org.java_websocket.client.WebSocketClient;
    import org.java_websocket.handshake.ServerHandshake;
    import java.net.URI;
    import java.util.ArrayList;
    
    public class MainActivity extends AppCompatActivity {
        private EditText messageInput;
        private Button sendButton;
        private ListView messageList;
        private ArrayAdapter<String> adapter;
        private ArrayList<String> messages = new ArrayList<>();
        private WebSocketClient webSocketClient;
        private MessageDatabaseHelper dbHelper;
        private SQLiteDatabase db;
        private int lastSequence = 0;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            messageInput = findViewById(R.id.messageInput);
            sendButton = findViewById(R.id.sendButton);
            messageList = findViewById(R.id.messageList);
    
            adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, messages);
            messageList.setAdapter(adapter);
    
            dbHelper = new MessageDatabaseHelper(this);
            db = dbHelper.getWritableDatabase();
    
            sendButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    sendMessage();
                }
            });
    
            connectWebSocket();
            loadMessagesFromDatabase();
        }
    
        private void sendMessage() {
            String message = messageInput.getText().toString();
            if (!message.isEmpty()) {
                // 发送消息到后端
                OkHttpClient client = new OkHttpClient();
                RequestBody body = new FormBody.Builder()
                        .add("sender", "User")
                        .add("content", message)
                        .build();
                Request request = new Request.Builder()
                        .url("http://192.168.1.2:5000/send_message") // 替换为你的后端地址
                        .post(body)
                        .build();
    
                client.newCall(request).enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                        e.printStackTrace();
                    }
    
                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        if (response.isSuccessful()) {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    messages.add("Me: " + message);
                                    adapter.notifyDataSetChanged();
                                    messageInput.setText("");
                                    saveMessageToDatabase("User", message, lastSequence + 1);
                                }
                            });
                        }
                    }
                });
            }
        }
    
        private void connectWebSocket() {
            webSocketClient = new WebSocketClient(URI.create("ws://192.168.1.2:5000/")) {
                @Override
                public void onOpen(ServerHandshake handshakedata) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            messages.add("Connected to server");
                            adapter.notifyDataSetChanged();
                        }
                    });
                }
    
                @Override
                public void onMessage(String message) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            messages.add("Server: " + message);
                            adapter.notifyDataSetChanged();
                            saveMessageToDatabase("Server", message, lastSequence + 1);
                        }
                    });
                }
    
                @Override
                public void onClose(int code, String reason, boolean remote) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            messages.add("Disconnected from server");
                            adapter.notifyDataSetChanged();
                        }
                    });
                }
    
                @Override
                public void onError(Exception ex) {
                    ex.printStackTrace();
                }
            };
    
            webSocketClient.connect();
        }
    
        private void saveMessageToDatabase(String sender, String content, int sequence) {
            ContentValues values = new ContentValues();
            values.put(MessageDatabaseHelper.COLUMN_ID, UUID.randomUUID().toString());
            values.put(MessageDatabaseHelper.COLUMN_SENDER, sender);
            values.put(MessageDatabaseHelper.COLUMN_CONTENT, content);
            values.put(MessageDatabaseHelper.COLUMN_SEQUENCE, sequence);
            db.insert(MessageDatabaseHelper.TABLE_MESSAGES, null, values);
        }
    
        private void loadMessagesFromDatabase() {
            Cursor cursor = db.query(MessageDatabaseHelper.TABLE_MESSAGES, null, null, null, null, null, null);
            while (cursor.moveToNext()) {
                String sender = cursor.getString(cursor.getColumnIndexOrThrow(MessageDatabaseHelper.COLUMN_SENDER));
                String content = cursor.getString(cursor.getColumnIndexOrThrow(MessageDatabaseHelper.COLUMN_CONTENT));
                int sequence = cursor.getInt(cursor.getColumnIndexOrThrow(MessageDatabaseHelper.COLUMN_SEQUENCE));
                messages.add(sender + ": " + content);
                if (sequence > lastSequence) {
                    lastSequence = sequence;
                }
            }
            cursor.close();
            adapter.notifyDataSetChanged();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (webSocketClient != null) {
                webSocketClient.close();
            }
            db.close();
        }
    }
    
  4. 设计布局

在 res/layout/activity_main.xml 中设计界面,包含输入框、发送按钮和显示消息的列表。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/messageInput"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Enter message" />

    <Button
        android:id="@+id/sendButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send" />

    <ListView
        android:id="@+id/messageList"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
</LinearLayout>

同步机制

为了保持前端和后端存储的消息数据一致,可以在客户端实现一个同步机制。每当从服务器获取新消息时,根据序列号来确定是否需要更新本地消息列表。如果本地序列号小于服务器序列号,则从服务器获取缺失的消息并更新本地数据库。

以下是一个简单的同步机制示例:

private void syncMessagesFromServer() {
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
            .url("http://192.168.1.2:5000/get_messages?last_sequence= " + lastSequence)
            .get()
            .build();

    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            e.printStackTrace();
        }

        @Override
 public        void onResponse(Call call, Response response) throws IOException {
            if (response.isSuccessful()) {
                final List<Message> newMessages = new ArrayList<>();
                JsonArray jsonArray = new JsonArray(response.body().string());
                for (int i = 0; i < jsonArray.length(); i++) {
                    JsonObject jsonObject = jsonArray.getJsonObject(i);
                    String id = jsonObject.getString("id");
                    String sender = jsonObject.getString("sender");
                    String content = jsonObject.getString("content");
                    int sequence = jsonObject.getInt("sequence");
                    newMessages.add(new Message(id, sender, content, sequence));
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        for (Message newMessage : newMessages) {
                            if (newMessage.sequence > lastSequence) {
                                messages.add(newMessage.sender + ": " + newMessage.content);
                                adapter.notifyDataSetChanged();
                                saveMessageToDatabase(newMessage.sender, newMessage.content, newMessage.sequence);
                                lastSequence = newMessage.sequence;
                            }
                        }
                    }
                });
            }
        }
    });
}

在这个同步机制中,客户端通过 HTTP 请求从服务器获取新消息,并根据序列号来确定是否需要更新本地消息列表。如果本地序列号小于服务器序列号,则从服务器获取缺失的消息并更新本地数据库和 UI。

syncMessagesFromServer 方法应该放在你的 Android 客户端应用中负责处理网络请求和数据同步的类中。通常,这个方法可以放在与 UI 活动(Activity)相关的类中,或者放在一个专门处理数据同步的辅助类中,例如一个服务(Service)或一个数据管理器(DataManager)类。

以下是几种可能的方式:

1. 放在 Activity 中

如果同步操作与特定 UI 活动紧密相关,你可以将这个方法直接放在对应的 Activity 中。例如,如果你有一个 ChatActivity 用于显示聊天界面,你可以在这里调用 syncMessagesFromServer 方法来同步消息。

示例:MainActivity.java

public class MainActivity extends AppCompatActivity {
    // ... 其他代码 ...

    private void syncMessagesFromServer() {
        // 同步消息的代码
    }

    // ... 其他代码 ...
}
2. 放在 Service 中

如果你需要在后台线程中执行同步操作,或者同步操作需要独立于 UI 活动进行,你可以创建一个服务(Service)来处理这些任务。

示例:MessageSyncService.java

public class MessageSyncService extends Service {
    // ... 其他代码 ...

    public int onStartCommand(Intent intent, int flags, int startId) {
        syncMessagesFromServer();
        return START_STICKY;
    }

    private void syncMessagesFromServer() {
        // 同步消息的代码
    }

    // ... 其他代码 ...
}
3. 放在 DataManager 类中

为了更好地组织代码,你可以创建一个单独的数据管理器类来处理所有与数据相关的操作,包括同步、存储和检索。

示例:DataManager.java

public class DataManager {
    private Context context;
    private OkHttpClient client;
    private MessageDatabaseHelper dbHelper;

    public DataManager(Context context) {
        this.context = context;
        this.client = new OkHttpClient();
        this.dbHelper = new MessageDatabaseHelper(context);
    }

    public void syncMessagesFromServer() {
        // 同步消息的代码
    }

    // ... 其他数据管理方法 ...
}

然后在你的 Activity 中使用这个 DataManager 类来同步消息:

示例:MainActivity.java

public class MainActivity extends AppCompatActivity {
    private DataManager dataManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        dataManager = new DataManager(this);
        dataManager.syncMessagesFromServer();
    }

    // ... 其他代码 ...
}

放在Service中的具体实现

syncMessagesFromServer 方法放在一个 Service 中有以下几个好处:

1. 后台执行

服务(Service)可以在后台执行长时间运行的操作,而不会干扰到用户界面的响应性。这对于执行网络请求或数据库操作等耗时任务特别有用。

2. 独立于活动

服务独立于任何用户界面,这意味着它们可以在任何时候运行,即使用户切换到另一个应用或设备锁屏。这使得服务非常适合执行需要持续运行的任务,如同步数据、播放音乐或跟踪位置。

3. 系统管理

Android 系统对服务的生命周期进行了管理,可以根据设备的资源状况(如内存不足)自动停止服务。这有助于优化设备性能和电池使用。

4. 绑定服务

其他组件(如活动或片段)可以绑定到服务,以发送请求、接收回调或管理服务的生命周期。这提供了一种组件间通信的机制。

5. 进程间通信(IPC)

服务可以通过进程间通信(IPC)机制与其他应用组件进行通信。这使得服务可以被不同的应用组件或甚至其他应用使用。

6. 可配置的启动模式

服务可以通过不同的启动模式进行配置,例如 START_STICKYSTART_NOT_STICKYSTART_REDELIVER_INTENT,以满足不同的需求。

7. 可重用性

将服务作为独立的组件,可以在应用的不同部分或不同的应用中重用,从而提高代码的可重用性。

8. 维护状态

服务可以在其生命周期内维护状态,即使在设备重启后也能恢复状态。

示例:将 syncMessagesFromServer方法放在 Service中

public class MessageSyncService extends Service {
    private OkHttpClient client;

    @Override
    public void onCreate() {
        super.onCreate();
        client = new OkHttpClient();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        syncMessagesFromServer();
        return START_STICKY;
    }

    private void syncMessagesFromServer() {
        // 同步消息的代码
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (client != null) {
            client.dispatcher().executorService().shutdown();
        }
    }
}

AndroidManifest.xml 中注册服务:

<service android:name=".MessageSyncService" />

通过这种方式,你可以确保消息同步操作在后台执行,不会阻塞用户界面,并且可以在任何时候运行,即使用户切换到其他应用。

总结

选择哪种方式取决于你的应用架构和同步操作的具体需求。通常,将数据同步逻辑放在一个单独的服务或数据管理器类中可以提高代码的可维护性和可重用性。然而,如果同步操作与特定 UI 活动紧密相关,直接在 Activity 中处理也是一个简单直接的选择。

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

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

相关文章

01_JDBC

文章目录 一、概述1.1、什么是JDBC1.2、JDBC原理 二、JDBC入门2.1、准备工作2.1.1、建库建表2.1.2、新建项目 2.2、建立连接2.2.1、准备四大参数2.2.2、加载驱动2.2.3、准备SQL语句2.2.4、建立连接2.2.5、常见问题 2.3、获取发送SQL的对象2.4、执行SQL语句2.5、处理结果2.6、释…

Spring Boot 热部署详解,包含详细的配置项说明

Spring Boot 热部署详解 1. 热部署简介 热部署&#xff08;Hot Deployment&#xff09;允许在应用运行时修改代码或配置文件&#xff0c;无需重启应用即可使更改生效。Spring Boot 通过 spring-boot-devtools 模块实现这一功能&#xff0c;其核心依赖于 LiveReload 技术和自动…

剑指Offer(数据结构与算法面试题精讲)C++版——day12

剑指Offer&#xff08;数据结构与算法面试题精讲&#xff09;C版——day12 题目一&#xff1a;小行星碰撞题目二&#xff1a;每日温度题目三&#xff1a;直方图最大矩形面积附录&#xff1a;源码gitee仓库 题目一&#xff1a;小行星碰撞 题目&#xff1a;输入一个表示小行星的数…

Docker学习笔记-docker安装、删除

一、在centOS 7中docker的默认安装目录 # Docker 主配置文件目录 ls /etc/docker# Docker 数据目录&#xff08;镜像、容器、卷等&#xff09; ls /var/lib/docker# Docker 可执行文件路径 which docker # 输出类似 /usr/bin/docker 二、docker文件目录说明 目录/文件用途/…

【Python 开源】你的 Windows 关机助手——PyQt5 版定时关机工具

&#x1f5a5;️ 你的 Windows 关机助手——PyQt5 版定时关机工具 相关资源文件已经打包成EXE文件&#xff0c;可双击直接运行程序&#xff0c;且文章末尾已附上相关源码&#xff0c;以供大家学习交流&#xff0c;博主主页还有更多Python相关程序案例&#xff0c;秉着开源精神的…

【Python爬虫】简单介绍

目录 一、基本概念 1.1 什么是爬虫 1.2 Python为什么适合爬虫 1.3 Python爬虫应用领域 &#xff08;1&#xff09;数据采集与分析 市场调研 学术研究 &#xff08;2&#xff09;内容聚合与推荐 新闻聚合 视频内容聚合 &#xff08;3&#xff09;金融领域 股票数据获…

使用MCP服务通过自然语言操作数据库(vscode+cline版本)

使用MCP服务操纵数据库(vscodecline版本) 本文主要介绍&#xff0c;在vscode中使用cline插件调用deepseek模型&#xff0c;通过MCP服务器 使用自然语言去操作指定数据库。本文使用的是以己经创建号的珠海航展数据库。 理解MCP服务&#xff1a; MCP&#xff08;Model Context…

Vue 3 + TypeScript 实现一个多语言国际化组件(支持语言切换与内容加载)

文章目录 一、项目背景与功能概览二、项目技术架构与依赖安装2.1 技术栈2.2 安装依赖 三、国际化组件实现3.1 创建 i18n 实例3.2 配置 i18n 到 Vue 应用3.3 在组件中使用国际化内容3.4 支持语言切换 四、支持类型安全4.1 添加类型支持4.2 自动加载语言文件 一、项目背景与功能概…

PhalApi 2.x:让PHP接口开发从“简单”到“极简”的开源框架

—— 专为高效开发而生&#xff0c;助你轻松构建高可用API接口 一、为什么选择PhalApi 2.x&#xff1f; 1.轻量高效&#xff0c;性能卓越 PhalApi 2.x 是一款专为接口开发设计的轻量级PHP框架&#xff0c;其核心代码精简但功能强大。根据开发者实测&#xff0c;在2核2G服务器…

Java 企业级应用:SOA 与微服务的对比与选择

企业级应用开发中&#xff0c;架构设计是决定系统可扩展性、可维护性和性能的关键因素。SOA&#xff08;面向服务的架构&#xff09;和微服务架构是两种主流的架构模式&#xff0c;它们各自有着独特的和设计理念适用场景。本文将深入探讨 SOA 和微服务架构的对比&#xff0c;并…

Zookeeper的典型应用场景?

大家好&#xff0c;我是锋哥。今天分享关于【Zookeeper的典型应用场景?】面试题。希望对大家有帮助&#xff1b; Zookeeper的典型应用场景? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 ZooKeeper 是一个开源的分布式协调服务&#xff0c;主要用于管理和协调大…

数据分析不只是跑个SQL!

数据分析不只是跑个SQL&#xff01; 数据分析五大闭环&#xff0c;你做到哪一步了&#xff1f;闭环一&#xff1a;认识现状闭环二&#xff1a;原因分析闭环三&#xff1a;优化表现闭环四&#xff1a;预测走势闭环五&#xff1a;主动解读数据 数据思维&#xff1a;WHY-WHAT-HOW模…

Dify智能体平台源码二次开发笔记(4) - 多租户的SAAS版实现

前言 Dify 的多租户功能是其商业版的标准功能&#xff0c;我们应当尊重其盈利模式。只有保持良性的商业运作&#xff0c;Dify 才能持续发展&#xff0c;并为用户提供更优质的功能。因此&#xff0c;此功能仅限学习使用。 我们的需求是&#xff1a;实现类似 SaaS 版的账号隔离&a…

layui中transfer两个table展示不同的数据列

在项目的任务开发中需要达到transfer右侧table需要有下拉框可选择状态&#xff0c;左侧table不变 使用的layui版本为2.4.5&#xff0c;该版本没有对transfer可自定义数据列的配置&#xff0c;所以改动transfer.js中的源码 以下为transfer.js部分源码 也是transfer.js去render的…

【机器学习】机器学习笔记

1 机器学习定义 计算机程序从经验E中学习&#xff0c;解决某一任务T&#xff0c;进行某一性能P&#xff0c;通过P测定在T上的表现因经验E而提高。 eg&#xff1a;跳棋程序 E&#xff1a; 程序自身下的上万盘棋局 T&#xff1a; 下跳棋 P&#xff1a; 与新对手下跳棋时赢的概率…

STM32 BOOT设置,bootloader,死锁使用方法

目录 BOOT0 BOOT1的配置含义 bootloader使用方法 芯片死锁解决方法开发调试过程中&#xff0c;由于某种原因导致内部Flash锁死&#xff0c;无法连接SWD以及JTAG调试&#xff0c;无法读到设备&#xff0c;可以通过修改BOOT模式重新刷写代码。修改为BOOT01&#xff0c;BOOT10…

【Redis】string类型

目录 1、介绍2、底层实现【1】SDS【2】int编码【3】embstr编码【4】raw编码【5】embstr和raw的区别 3、常用指令【1】字符串基本操作&#xff1a;【2】批量操作【3】计数器【4】过期时间【5】不存在就插入 4、使用场景 1、介绍 string是redis中最简单的键值对形式&#xff0c;…

PostgreSQL全平台安装指南:从入门到生产环境部署

一、PostgreSQL核心特性全景解析 1.1 技术架构深度剖析 graph TDA[客户端] --> B(连接池)B --> C{查询解析器}C --> D[优化器]D --> E[执行引擎]E --> F[存储引擎]F --> G[物理存储]G --> H[WAL日志]H --> I[备份恢复] 1.2 特性优势对比矩阵 特性维度…

UE5 物理模拟 与 触发检测

文章目录 碰撞条件开启模拟关闭模拟 多层级的MeshUE的BUG 触发触发条件 碰撞 条件 1必须有网格体组件 2网格体组件必须有网格&#xff0c;没有网格虽然可以开启物理模拟&#xff0c;但是不会有任何效果 注意开启的模拟的网格体组件会计算自己和所有子网格的mesh范围 3只有网格…

做仪器UI用到的颜色工具网站

https://color.adobe.com/zh/create/color-wheel 1. 图片取颜色工具 2. 对比度工具&#xff0c;煤矿井下设备&#xff0c;光线暗&#xff0c;要求背景与文字有合适的对比度&#xff0c;可以用这个软件 3. 颜色生成ARGB的值工具&#xff0c;这三个工具&#xff0c;都在上面这…