文章目录
- 后端(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 数据库存储消息,并通过序列号机制同步消息。
-
添加 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'
-
创建 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); } }
-
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(); } }
-
设计布局
在 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_STICKY
、START_NOT_STICKY
或 START_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
中处理也是一个简单直接的选择。