Algorithm_String: 字符串与EditText_02

本文介绍了EditText的小技巧,包括设置TextChangedListener事件、删除指定字符、与TextUtils类的方法使用。主要内容涉及为EditText设置监听事件,阻止用户输入特定字符,以及使用TextUtils类的isEmpty方法检查文本输入情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于EditText的小技巧,在这几篇博客中均有提到,总结如下:

UI效果(6): EditText与软键盘

FAQ_10_EditText与parseInt方法

Algorithm_String: 字符串与EditText_01


这篇博客在 Algorithm_String: 字符串与EditText_01 基础之上,增加的知识点如下:

1.  为EditText设置TextChangedListener事件

2.  在TextChangedListener事件中,删除指定字符,即禁止用户输入指定的字符

3.  EditText与TextUtils类的方法

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

源码如下:

import android.app.Activity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;


public class EditTextAndString extends Activity {
private static final char SPACE = 0x20;


private EditText user = null;
private EditText result = null;
private Button button = null;


private final TextWatcher mTextWatcher = new TextWatcher() {


@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}


@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
}


@Override
public void afterTextChanged(Editable s) {
Log.d("mark", "afterTextChanged()");
// 如果输入#,EditText不接受其输入,可以改为其它非法字符
// deleteChar(s, '#');
deleteChar(s, new char[] { '@', '$', '%' });
}
};


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);


user = (EditText) findViewById(R.id.user);
result = (EditText) findViewById(R.id.result);
button = (Button) findViewById(R.id.button);
// 注册TextChangedListener
user.addTextChangedListener(mTextWatcher);


button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
String name = user.getText().toString();


String res = removeAllSpace(name);
result.append("去除所有空格后:" + res + "\n");


if (isChinese(name)) {
// ToDo
}
}
});
}


/**
* 删除指定字符

* @param s
* @param ch
*/
public void deleteChar(Editable s, char ch) {
if (s.length() > 0) {
int pos = s.length() - 1;
char c = s.charAt(pos);
if (c == ch) {
Log.d("mark", "c= " + c);
s.delete(pos, pos + 1);
Toast.makeText(EditTextAndString.this, "Error letter.",
Toast.LENGTH_SHORT).show();
}
}
}


/**
* 删除指定字符 重载方法,可以指定多个需要删除的字符

* @param s
* @param ch
*/
public void deleteChar(Editable s, char[] ch) {
if (s.length() > 0) {
int pos = s.length() - 1;
char c = s.charAt(pos);
int len = ch.length;
for (int i = 0; i < len; i++) {
if (c == ch[i]) {
Log.d("mark", "c= " + c);
s.delete(pos, pos + 1);
Toast.makeText(EditTextAndString.this, "Error letter.",
Toast.LENGTH_SHORT).show();
}
}
}
}


/**
* 该字符是否包含中文字符

* @param c
*            字符
* @return 如果包含中文返回true,否则false
*/
public boolean isChinese(char c) {
Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
|| ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
|| ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) {
return true;
}
return false;
}


/**
* 该字符串是否包含中文字符

* @param str
*            字符串
* @return 如果包含中文返回true,否则返回false
*/
public boolean isChinese(String str) {
if ((str != null) && (!"".equals(str))) {
char[] ch = str.toCharArray();
int len = ch.length;
for (int i = 0; i < len; i++) {
// Log.d("mark", "<---> " + ch[i]);
if (isChinese(ch[i])) {
result.append("该字符串中第一个中文:" + ch[i] + "\n");
return true;
}
}
}
return false;
}


/**
* 去除字符串中所有空格

* @param s
*            字符串
* @return 字符串

*         注意:String类的trim()只可以去除两端的空格
*/
public String removeAllSpace(String s) {
String endString = "";
StringBuilder builder = new StringBuilder(endString);
int len = s.length();
for (int i = 0; i < len; i++) {
// 获得字符
char c = s.charAt(i);
// 如果该字符不是空格
if (c != SPACE) {
builder.append(String.valueOf(c));
}
}
return builder.toString();
}
}

关于代码,很简单,没什么可以细说的。自己运行看看即可,主要提供一下思路。下面说说 TextUtils 这个类的 isEmpty() 方法。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

获取EditText内容的方法,如下(大家都知道!!!!!):

EditText对象.getText().toString()


可以获得String对象,为了防止用户输入空格,可以采用:

EditText对象.getText().toString().trim()
不过这种方法只可以去除该字符串左右的空格,关于如何去除所有空格的方法,参照上面代码实例。

如登录对话框中,获取用户名和密码,我们首先需要判断用户是否输入:

if(TextUtils.isEmpty(str)) {
    	Toast.makeText(this, "输入为空!", Toast.LENGTH_LONG).show();
}
这里主要用到 TextUtils 这个类的 isEmpty() 方法,其源码:

 /**
     * Returns true if the string is null or 0-length.
     * @param str the string to be examined
     * @return true if str is null or zero length
     */
    public static boolean isEmpty(CharSequence str) {
        if (str == null || str.length() == 0)
            return true;
        else
            return false;
    }

可以看出,如果字符串为 Null 或者长度为 0 时,返回 true。

这里需要注意两个问题:

<1> 在 EditText 中输入空格算一个字符

<2>  在 EditText 中输入回车算一个字符


所以,在开发中,一般这样书写代码(个人觉得):

String str = EditText对象.getText().toString();//.trim();
    	if(TextUtils.isEmpty(str)) {
    	// ToDo
}
如果不想用户输入回车键,就可以设置 EditText 为单行显示。

android:singleLine="true"
对应 Java 代码:

setSingleLine(true);
或者(调用 setSingleLine(true)):

setSingleLine();




#include <windows.h> #include <cstdlib> #include <ctime> #include <string> #include <algorithm> // For std::min and std::max if needed // 控件ID定义 #define ID_BUTTON_GUESS 101 #define ID_EDIT_INPUT 102 #define ID_STATIC_HINT 103 #define ID_STATIC_ATTEMPTS 104 #define ID_BUTTON_SET_RANGE 105 // 新增按钮:设置范围 #define ID_EDIT_MAX_VALUE 106 // 新增编辑框:输入最大值 // 全局变量 int secretNumber; int attempts = 0; const int DEFAULT_MAX = 100; // 默认最大值为100 int currentMax = DEFAULT_MAX; // 当前最大值,可被用户修改 const int MAX_ATTEMPTS = 10; // 生成随机数,基于当前的currentMax void GenerateSecretNumber() { srand(time(0)); secretNumber = rand() % currentMax + 1; // 1到currentMax之间的随机数 } // 窗口过程函数 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hEditInput, hHint, hAttempts, hEditMax, hButtonSetRange; switch (message) { case WM_CREATE: { // 创建主标题静态文本,显示当前范围 std::wstring titleText = L"猜数字游戏 (1-"; titleText += std::to_wstring(currentMax); titleText += L")"; CreateWindow("STATIC", titleText.c_str(), WS_VISIBLE | WS_CHILD, 20, 20, 200, 25, hWnd, (HMENU)ID_STATIC_HINT, NULL, NULL); // 输入猜测数字的编辑框 hEditInput = CreateWindow("EDIT", "", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_NUMBER, 20, 50, 100, 25, hWnd, (HMENU)ID_EDIT_INPUT, NULL, NULL); // “猜”按钮 CreateWindow("BUTTON", L"猜", WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, 130, 50, 80, 25, hWnd, (HMENU)ID_BUTTON_GUESS, NULL, NULL); // 提示信息的静态文本 hHint = CreateWindow("STATIC", L"输入数字后点击按钮", WS_VISIBLE | WS_CHILD, 20, 90, 300, 25, hWnd, (HMENU)ID_STATIC_HINT, NULL, NULL); // 剩余次数显示 hAttempts = CreateWindow("STATIC", (L"剩余次数: " + std::to_wstring(MAX_ATTEMPTS)).c_str(), WS_VISIBLE | WS_CHILD, 20, 120, 200, 25, hWnd, (HMENU)ID_STATIC_ATTEMPTS, NULL, NULL); // 新增:“设置范围”按钮 hButtonSetRange = CreateWindow("BUTTON", L"设置范围", WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, 20, 150, 100, 25, hWnd, (HMENU)ID_BUTTON_SET_RANGE, NULL, NULL); // 新增:输入最大值的编辑框,初始值为当前最大值 std::wstring initMaxStr = std::to_wstring(currentMax); hEditMax = CreateWindow("EDIT", initMaxStr.c_str(), WS_VISIBLE | WS_CHILD | WS_BORDER | ES_NUMBER, 130, 150, 100, 25, hWnd, (HMENU)ID_EDIT_MAX_VALUE, NULL, NULL); // 生成第一个秘密数字 GenerateSecretNumber(); break; } case WM_COMMAND: { int id = LOWORD(wParam); if (id == ID_BUTTON_GUESS) { // “猜”按钮被点击 char buffer[10]; GetWindowTextA(hEditInput, buffer, sizeof(buffer)); int guess = atoi(buffer); attempts++; int remaining = MAX_ATTEMPTS - attempts; SetWindowTextA(hAttempts, (std::string("剩余次数: ") + std::to_string(remaining)).c_str()); if (guess == secretNumber) { MessageBoxA(hWnd, "恭喜你猜对了!", "游戏胜利", MB_OK); GenerateSecretNumber(); attempts = 0; SetWindowTextA(hAttempts, (std::string("剩余次数: ") + std::to_string(MAX_ATTEMPTS)).c_str()); } else if (guess < secretNumber) { SetWindowTextA(hHint, "太小了!再试一次"); } else { SetWindowTextA(hHint, "太大了!再试一次"); } if (attempts >= MAX_ATTEMPTS) { MessageBoxA(hWnd, (std::string("游戏结束!数字是 ") + std::to_string(secretNumber)).c_str(), "游戏结束", MB_OK); GenerateSecretNumber(); attempts = 0; SetWindowTextA(hAttempts, (std::string("剩余次数: ") + std::to_string(MAX_ATTEMPTS)).c_str()); } SetWindowTextA(hEditInput, ""); // 清空输入框 } else if (id == ID_BUTTON_SET_RANGE) { // “设置范围”按钮被点击 char maxBuffer[10]; GetWindowTextA(hEditMax, maxBuffer, sizeof(maxBuffer)); int newMax = atoi(maxBuffer); // 验证输入是否有效 if (newMax < 2 || newMax > 200) { // 最小允许2,最大限制为200 MessageBoxA(hWnd, "请输入一个有效的最大值(2-200)", "无效输入", MB_ICONERROR); } else { currentMax = newMax; // 更新窗口标题以反映新的范围 std::wstring newTitle = L"猜数字游戏 (1-"; newTitle += std::to_wstring(currentMax); newTitle += L")"; SetWindowTextW(GetDlgItem(hWnd, 0), newTitle.c_str()); // 假设第一个子控件是标题 // 重新生成秘密数字 GenerateSecretNumber(); // 重置尝试次数 attempts = 0; SetWindowTextA(hAttempts, (std::string("剩余次数: ") + std::to_string(MAX_ATTEMPTS)).c_str()); // 清空猜测输入框并给出提示 SetWindowTextA(hEditInput, ""); SetWindowTextA(hHint, "已更新范围,请重新猜测"); } break; } break; } case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // WinMain入口函数 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // 注册窗口类 WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInstance, NULL, LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW+1), NULL, L"GuessNumberWindowClass", NULL }; RegisterClassEx(&wc); // 创建窗口 HWND hWnd = CreateWindow(L"GuessNumberWindowClass", L"猜数字游戏", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 250, NULL, NULL, hInstance, NULL); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // 消息循环 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }加多一个功能:例如输入30,1-100会变成1-30
最新发布
08-02
package com.example.diary03; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; /** * 用户登录界面 */ public class LoginActivity extends AppCompatActivity { private EditText etUsername, etPassword; private CheckBox cbRemember; private Button btnLogin; private TextView tvRegister; private DBHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); // 初始化数据库辅助类 dbHelper = new DBHelper(this); // 获取UI组件 etUsername = findViewById(R.id.et_username); etPassword = findViewById(R.id.et_password); cbRemember = findViewById(R.id.cb_remember); btnLogin = findViewById(R.id.btn_login); tvRegister = findViewById(R.id.tv_register); // 如果勾选了"记住用户名",则从SharedPreferences加载用户名 loadRememberedUsername(); // 设置登录按钮点击事件 btnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String username = etUsername.getText().toString().trim(); String password = etPassword.getText().toString().trim(); // 验证输入 if (username.isEmpty() || password.isEmpty()) { Toast.makeText(LoginActivity.this, "用户名密码不能为空", Toast.LENGTH_SHORT).show(); return; } // 验证用户凭据 if (dbHelper.checkUserCredentials(username, password)) { // 登录成功,保存登录状态 saveLoginStatus(username, cbRemember.isChecked()); // 跳转到主界面 startActivity(new Intent(LoginActivity.this, MainActivity.class)); finish(); } else { Toast.makeText(LoginActivity.this, "用户名或密码错误", Toast.LENGTH_SHORT).show(); } } }); // 注册链接点击事件 tvRegister.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(LoginActivity.this, RegisterActivity.class)); } }); } /** * 加载已记住的用户名 */ private void loadRememberedUsername() { SharedPreferences pref = getSharedPreferences("login_pref", Context.MODE_PRIVATE); if (pref.getBoolean("remember", false)) { String username = pref.getString("username", ""); etUsername.setText(username); cbRemember.setChecked(true); } } /** * 保存登录状态 * @param username 用户名 * @param remember 是否记住用户名 */ private void saveLoginStatus(String username, boolean remember) { SharedPreferences pref = getSharedPreferences("login_pref", Context.MODE_PRIVATE); SharedPreferences.Editor editor = pref.edit(); editor.putString("username", username); editor.putBoolean("remember", remember); editor.putBoolean("isLoggedIn", true); editor.apply(); } }将页面的记住用户名选项修改为记住密码选项
06-08
package com.example.kucun2.entity.data; import android.os.Handler; import android.os.Looper; import android.util.Log; import com.example.kucun2.entity.Information; import com.example.kucun2.entity.User; import com.example.kucun2.function.MyAppFunction; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import okhttp3.*; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Map; /** * 重构版API客户端 - 更灵活的类型处理 * 主要改进: * 1. 分离请求参数类型响应类型 * 2. 支持多种请求方法(GET/POST/PUT/DELETE) * 3. 支持表单JSON两种请求格式 * 4. 自动推导响应类型 * 5. 统一的请求执行流程 */ public class ApiClient { private static final Gson gson = GsonFactory.createGson(); private static final String TAG = "ApiClient"; private static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); private static final int MAX_RETRY = 3; private static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper()); // ====================== 核心请求方法 ====================== /** * 执行API请求(核心方法) * @param request 构建好的OkHttp请求 * @param responseType 期望的响应类型 * @param callback 回调接口 * @param <R> 响应数据类型 */ public static <R> void executeRequest(Request request, Type responseType, ApiCallback<R> callback) { OkHttpClient client = MyAppFunction.getClient(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleFailure(call, e, callback, 0); } @Override public void onResponse(Call call, Response response) throws IOException { handleResponse(response, responseType, callback); } }); } // ====================== 请求构建方法 ====================== /** * 构建JSON请求 * @param url API地址 * @param method 请求方法("POST", "PUT", "DELETE") * @param requestData 请求数据对象 * @return 构建好的Request对象 */ public static Request buildJsonRequest(String url, String method, Object requestData) { String jsonRequest = ReflectionJsonUtils.toJson(requestData); Log.d(TAG, method + " URL: " + url); Log.d(TAG, "请求数据: " + jsonRequest); RequestBody body = RequestBody.create(JSON, jsonRequest); return new Request.Builder() .url(url) .method(method, body) .build(); } /** * 构建表单请求 * @param url API地址 * @param method 请求方法 * @param formData 表单数据 * @return 构建好的Request对象 */ public static Request buildFormRequest(String url, String method, Map<String, String> formData) { FormBody.Builder builder = new FormBody.Builder(); for (Map.Entry<String, String> entry : formData.entrySet()) { builder.add(entry.getKey(), entry.getValue()); } Log.d(TAG, method + " URL: " + url); Log.d(TAG, "表单数据: " + formData); return new Request.Builder() .url(url) .method(method, builder.build()) .build(); } // ====================== 响应处理方法 ====================== private static <R> void handleResponse(Response response, Type responseType, ApiCallback<R> callback) throws IOException { try (ResponseBody responseBody = response.body()) { if (!response.isSuccessful()) { String error = "HTTP " + response.code() + ": " + response.message(); Log.e(TAG, error); notifyError(callback, response.code(), error); return; } String jsonResponse = responseBody.string(); Log.d(TAG, "服务器响应: " + jsonResponse); // 解析服务端的Information包装 Information<R> wrapper = gson.fromJson(jsonResponse, responseType); if (wrapper != null && wrapper.getStatus() == 200) { notifySuccess(callback, wrapper.getData()); } else { String errorMsg = wrapper != null ? "服务端错误: " + wrapper.getStatus() + " - " + wrapper.getText() : "无效的响应格式"; Log.e(TAG, errorMsg); notifyError(callback, wrapper != null ? wrapper.getStatus() : -1, errorMsg); } } catch (Exception e) { Log.e(TAG, "响应处理异常: " + e.getMessage()); notifyError(callback, -2, "数据处理异常: " + e.getMessage()); } } // ====================== 失败处理重试 ====================== private static <R> void handleFailure(Call call, IOException e, ApiCallback<R> callback, int retryCount) { if (retryCount < MAX_RETRY) { Log.w(TAG, "请求失败,第" + (retryCount + 1) + "次重试: " + e.getMessage()); MAIN_HANDLER.postDelayed(() -> { call.clone().enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleFailure(call, e, callback, retryCount + 1); } @Override public void onResponse(Call call, Response response) throws IOException { handleResponse(response, getResponseType(callback), callback); } }); }, 2000); } else { Log.e(TAG, "最终请求失败: " + e.getMessage()); notifyError(callback, -1, "网络请求失败: " + e.getMessage()); } } // ====================== 类型处理工具 ====================== /** * 获取响应类型(通过回调接口的泛型参数) */ private static <R> Type getResponseType(ApiCallback<R> callback) { if (callback == null) { return new TypeToken<Information<Object>>(){}.getType(); } // 尝试获取泛型类型 Type[] genericInterfaces = callback.getClass().getGenericInterfaces(); for (Type type : genericInterfaces) { if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) type; if (pType.getRawType().equals(ApiCallback.class)) { Type dataType = pType.getActualTypeArguments()[0]; return TypeToken.getParameterized(Information.class, dataType).getType(); } } } // 默认返回Object类型 Log.w(TAG, "无法确定响应类型,使用默认Object类型"); return new TypeToken<Information<Object>>(){}.getType(); } // ====================== 回调通知方法 ====================== private static <R> void notifySuccess(ApiCallback<R> callback, R data) { if (callback != null) { MAIN_HANDLER.post(() -> callback.onSuccess(data)); } } private static <R> void notifyError(ApiCallback<R> callback, int code, String error) { if (callback != null) { MAIN_HANDLER.post(() -> callback.onError(code, error)); } } // ====================== 专用API方法 ====================== /** * 执行JSON API请求 * @param url API地址 * @param method 请求方法 * @param requestData 请求数据 * @param callback 回调接口 * @param <T> 请求数据类型 * @param <R> 响应数据类型 */ public static <T, R> void jsonRequest(String url, String method, T requestData, ApiCallback<R> callback) { Request request = buildJsonRequest(url, method, requestData); executeRequest(request, getResponseType(callback), callback); } /** * 执行表单API请求 * @param url API地址 * @param method 请求方法 * @param formData 表单数据 * @param callback 回调接口 * @param <R> 响应数据类型 */ public static <R> void formRequest(String url, String method, Map<String, String> formData, ApiCallback<R> callback) { Request request = buildFormRequest(url, method, formData); executeRequest(request, getResponseType(callback), callback); } // ====================== 便捷方法 ====================== public static <T, R> void postJson(String url, T data, ApiCallback<R> callback) { jsonRequest(url, "POST", data, callback); } public static <T, R> void putJson(String url, T data, ApiCallback<R> callback) { jsonRequest(url, "PUT", data, callback); } public static <T, R> void deleteJson(String url, T data, ApiCallback<R> callback) { jsonRequest(url, "DELETE", data, callback); } public static <R> void get(String url, ApiCallback<R> callback) { Request request = new Request.Builder().url(url).get().build(); executeRequest(request, getResponseType(callback), callback); } // ====================== 登录专用方法 ====================== public static void login(String username, String password, LoginCallback callback) { String url = MyAppFunction.getApiUrl("url_login"); Log.d(TAG, "login: " + url); formRequest(url, "POST", Map.of( "andy", username, "pass", password ), new ApiCallback<User>() { @Override public void onSuccess(User user) { if (callback != null) callback.onSuccess(user); } @Override public void onError(int statusCode, String error) { if (callback != null) callback.onFailure(error); } }); } // ====================== 回调接口定义 ====================== public interface ApiCallback<T> { void onSuccess(T data); void onError(int statusCode, String error); } public interface LoginCallback { void onSuccess(User user); void onFailure(String error); } } package com.example.kucun2.entity.data; import android.util.Log; import com.example.kucun2.function.MyAppFunction; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 可同步实体基类 * 提供实体到服务端的同步功能,包含自动重试机制线程管理 */ public abstract class SynchronizableEntity implements EntityClassGrassrootsid { private static final String TAG = "SynchronizableEntity"; // 双重缓存结构:外层缓存类名,内层缓存字段名->Field对象 private static final Map<Class<?>, Map<String, Field>> CLASS_FIELD_CACHE = new HashMap<>(); // 网络请求线程池(静态共享) private static final ExecutorService NETWORK_EXECUTOR = Executors.newFixedThreadPool(4); /** * 获取指定类型操作的端点URL * * @param type 操作类型(如"create", "update", "delete"等) * @return 完整的端点URL路径 * * @apiNote 该方法通过资源键名拼接规则查找对应的URL资源 * @example 对于Product类的create操作,查找键为:"url_create_product" */ public String getEndpoint(String type) { // 构建资源键名:url_操作类型_类名小写 String key = "url_" + type + "_" + this.getClass().getSimpleName().toLowerCase(); return MyAppFunction.getApiUrl(key); } //================ 核心同步方法 ================// /** * 同步实体到服务端(公开接口) * * @param type 操作类型(如"add", "update","select","detect","all"等) * @param callback 同步结果回调接口 * * @implNote 内部调用私有方法实现带重试机制的同步 * @see #sync(String, SyncCallback, int) */ public void sync(String type, SyncCallback callback) { sync(type, callback, 0); // 初始重试次数为0 } private <T extends SynchronizableEntity> ApiClient.ApiCallback<T> getApiCallback(String type,SyncCallback callback,int retryCount,T thistype){ return new ApiClient.ApiCallback<T>() { @Override public void onSuccess(T responseData) { handleSyncSuccess(responseData, callback); } @Override public void onError(int statusCode, String error) { Log.d(TAG, "onError: "+thistype); handleSyncError(type, statusCode, error, callback, retryCount); } }; } private <T extends SynchronizableEntity> ApiClient.ApiCallback<T> getApiCallbackList(String type,SyncCallback callback,int retryCount,T thistype){ return new ApiClient.ApiCallback<T>() { @Override public void onSuccess(T responseData) { handleSyncSuccess(responseData, callback); } @Override public void onError(int statusCode, String error) { handleSyncError(type, statusCode, error, callback, retryCount); } }; } /** * 带重试机制的同步实现(私有方法) * * @param type 操作类型 * @param callback 同步结果回调 * @param retryCount 当前重试次数 * * @implSpec 1. 构建完整端点URL * 2. 通过线程池提交网络请求 * 3. 处理成功/失败回调 */ private void sync(String type, SyncCallback callback, int retryCount) { // 构建完整端点URL String endpoint =getEndpoint(type); Log.d(TAG, "同步端点: " + endpoint + ", 重试次数: " + retryCount); // 提交到线程池执行网络请求 NETWORK_EXECUTOR.execute(() -> { ApiClient.postJson(endpoint, this, getApiCallback(type,callback,retryCount,this)); }); } /** * 处理同步成功结果 * * @param responseData 服务端返回的实体数据 * @param callback 同步结果回调 * * @implNote 1. 更新实体ID * 2. 记录成功日志 * 3. 触发成功回调 */ private void handleSyncSuccess(SynchronizableEntity responseData, SyncCallback callback) { // 更新实体ID(如果服务端返回了新ID) if (responseData != null) { setId(responseData.getId()); Log.i(TAG, "同步成功, 新ID: " + responseData.getId()); } // 触发成功回调 if (callback != null) callback.onSyncSuccess(this); } /** * 处理同步错误(含重试逻辑) * * @param type 操作类型 * @param statusCode HTTP状态码(-1表示网络错误) * @param error 错误信息 * @param callback 同步结果回调 * @param retryCount 当前重试次数 * * @implSpec 1. 判断错误是否可重试(网络错误或5xx服务错误) * 2. 满足条件时进行指数退避重试 * 3. 达到最大重试次数后触发失败回调 * * @algorithm 使用指数退避算法:延迟时间 = 1000ms * 2^重试次数 */ private void handleSyncError(String type, int statusCode, String error, SyncCallback callback, int retryCount) { Log.e(TAG, "同步失败: " + error + ", 状态码: " + statusCode); // 判断是否可重试(网络错误或服务端5xx错误) boolean canRetry = statusCode == -1 || (statusCode >= 500 && statusCode < 600); // 满足重试条件(可重试错误且未达到最大重试次数) if (canRetry && retryCount < 3) { // 计算指数退避延迟时间 long delay = (long) (1000 * Math.pow(2, retryCount)); Log.w(TAG, "将在 " + delay + "ms 后重试"); try { // 当前线程休眠指定时间 Thread.sleep(delay); } catch (InterruptedException e) { // 恢复中断状态 Thread.currentThread().interrupt(); } // 递归调用进行重试(重试次数+1) sync(type, callback, retryCount + 1); } else { // 不可重试或达到最大重试次数,触发失败回调 if (callback != null) { String finalError = "同步失败: " + error; if (canRetry) finalError += " (重试失败)"; callback.onSyncFailure(finalError); } } } // 线程安全的对象复制方法 public void updateFrom(Object source) { if ( source == null) return; Class<?> targetClass = this.getClass(); Class<?> sourceClass = source.getClass(); // 获取或创建字段缓存 Map<String, Field> targetFields = getOrCreateFieldCache(targetClass); Map<String, Field> sourceFields = getOrCreateFieldCache(sourceClass); // 遍历源对象字段 for (Map.Entry<String, Field> entry : sourceFields.entrySet()) { String fieldName = entry.getKey(); Field sourceField = entry.getValue(); // 查找目标对象对应字段 Field targetField = targetFields.get(fieldName); if (targetField != null) { // 类型兼容性检查 if (isCompatibleTypes(sourceField.getType(), targetField.getType())) { try { // 复制字段值 Object value = sourceField.get(source); targetField.set(this, value); } catch (IllegalAccessException e) { // 处理异常,记录日志 } } } } } // 获取或创建类的字段缓存 private static Map<String, Field> getOrCreateFieldCache(Class<?> clazz) { // 双重检查锁确保线程安全 if (!CLASS_FIELD_CACHE.containsKey(clazz)) { synchronized (clazz) { if (!CLASS_FIELD_CACHE.containsKey(clazz)) { Map<String, Field> fieldMap = new HashMap<>(); // 递归获取所有字段(包括父类) for (Class<?> current = clazz; current != null; current = current.getSuperclass()) { for (Field field : current.getDeclaredFields()) { field.setAccessible(true); // 突破访问限制 fieldMap.put(field.getName(), field); } } CLASS_FIELD_CACHE.put(clazz, fieldMap); } } } return CLASS_FIELD_CACHE.get(clazz); } // 类型兼容性检查(支持自动装箱/拆箱) private static boolean isCompatibleTypes(Class<?> sourceType, Class<?> targetType) { // 处理基本类型包装类的兼容性 if (sourceType.isPrimitive()) { sourceType = primitiveToWrapper(sourceType); } if (targetType.isPrimitive()) { targetType = primitiveToWrapper(targetType); } return targetType.isAssignableFrom(sourceType); } // 基本类型转包装类 private static Class<?> primitiveToWrapper(Class<?> primitiveType) { if (boolean.class.equals(primitiveType)) return Boolean.class; if (byte.class.equals(primitiveType)) return Byte.class; if (char.class.equals(primitiveType)) return Character.class; if (double.class.equals(primitiveType)) return Double.class; if (float.class.equals(primitiveType)) return Float.class; if (int.class.equals(primitiveType)) return Integer.class; if (long.class.equals(primitiveType)) return Long.class; if (short.class.equals(primitiveType)) return Short.class; if (void.class.equals(primitiveType)) return Void.class; return primitiveType; } //================ 回调接口 ================// /** * 同步操作回调接口 * * @implNote 使用方需实现此接口处理同步结果 */ public interface SyncCallback { /** * 同步成功回调 * * @param entity 同步后的实体对象(已更新ID) */ void onSyncSuccess(SynchronizableEntity entity); /** * 同步失败回调 * * @param error 失败原因描述 */ void onSyncFailure(String error); } } package com.example.kucun2.DataPreserver; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.util.Log; import com.example.kucun2.entity.*; import com.example.kucun2.entity.Information; import com.example.kucun2.entity.data.*; import com.example.kucun2.function.MyAppFunction; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; import java.util.List; /** * 重构后的数据加载器:使用ApiClient处理网络请求 */ public class DataLoader { private static final String TAG = "DataLoader"; private final DataStore dataStore; private final DataAssociator dataAssociator; // private final DataPreserver dataPreserver; public DataLoader(DataStore dataStore, DataAssociator dataAssociator ) { this.dataStore = dataStore; this.dataAssociator = dataAssociator; // this.dataPreserver = dataPreserver; } public void loadAllData(Context context, LoadDataCallback callback) { if (Looper.myLooper() != Looper.getMainLooper()) { throw new IllegalStateException("必须在主线程调用Data.loadAllData"); } // dataPreserver.ensurePreservedObjects(); String url = MyAppFunction.getApiUrl("url_all"); Log.d(TAG, "开始加载数据, URL: " + url); // 使用ApiClient的get方法发送请求 ApiClient.get(url, new ApiClient.ApiCallback<DataLoader.AllDataResponse>() { @Override public void onSuccess(AllDataResponse allData) { Log.d(TAG, "数据加载成功"); parseAndAssignData(allData, callback); } @Override public void onError(int statusCode, String error) { Log.e(TAG, "数据加载失败: " + error + ", 状态码: " + statusCode); new Handler(Looper.getMainLooper()).post(callback::onFailure); } }); } private void parseAndAssignData(AllDataResponse allData, LoadDataCallback callback) { try { mergeAllLists(allData); dataAssociator.automaticAssociation(); callback.onSuccess(); } catch (Exception e) { Log.e(TAG, "数据处理异常: " + e.getMessage()); callback.onFailure(); } } private void mergeAllLists(AllDataResponse allData) { mergeList(dataStore.bancais, allData.bancais); mergeList(dataStore.caizhis, allData.caizhis); mergeList(dataStore.mupis, allData.mupis); mergeList(dataStore.chanpins, allData.chanpins); mergeList(dataStore.chanpin_zujians, allData.chanpin_zujians); mergeList(dataStore.dingdans, allData.dingdans); mergeList(dataStore.dingdan_chanpins, allData.dingdan_chanpins); mergeList(dataStore.dingdan_bancais, allData.dingdan_bancais); mergeList(dataStore.kucuns, allData.kucuns); mergeList(dataStore.zujians, allData.zujians); mergeList(dataStore.users, allData.users); mergeList(dataStore.jinhuos, allData.jinhuos); } private <T extends SynchronizableEntity> void mergeList( SynchronizedList<T> targetList, List<T> newList) { if (newList == null) return; targetList.mergeList(newList); } public interface LoadDataCallback { void onSuccess(); void onFailure(); } public static class AllDataResponse { public List<Bancai> bancais; public List<Caizhi> caizhis; public List<Mupi> mupis; public List<Chanpin> chanpins; public List<Chanpin_Zujian> chanpin_zujians; public List<Dingdan> dingdans; public List<Dingdan_Chanpin> dingdan_chanpins; public List<Dingdan_bancai> dingdan_bancais; public List<Kucun> kucuns; public List<Zujian> zujians; public List<User> users; public List<Jinhuo> jinhuos; } } package com.example.kucun2.ui.login; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import com.example.kucun2.DataPreserver.Data; import com.example.kucun2.MainActivity; import com.example.kucun2.R; import com.example.kucun2.entity.User; import com.example.kucun2.entity.data.ApiClient; public class LoginActivity extends AppCompatActivity { private EditText etUsername, etPassword; private CheckBox cbRemember, cbAutoLogin; private Button btnLogin; private ProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_loading); // 初始化视图 etUsername = findViewById(R.id.et_username); etPassword = findViewById(R.id.et_password); cbRemember = findViewById(R.id.cb_remember); cbAutoLogin = findViewById(R.id.cb_auto_login); btnLogin = findViewById(R.id.btn_login); progressBar = findViewById(R.id.progress_bar); // 设置登录按钮点击事件 btnLogin.setOnClickListener(v -> attemptLogin()); } private void attemptLogin() { String username = etUsername.getText().toString().trim(); String password = etPassword.getText().toString().trim(); if (username.isEmpty() || password.isEmpty()) { Toast.makeText(this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show(); return; } // 显示进度条,禁用登录按钮 progressBar.setVisibility(View.VISIBLE); btnLogin.setEnabled(false); // 调用网络登录 ApiClient.login(username, password, new ApiClient.LoginCallback() { @Override public void onSuccess(User user) { runOnUiThread(() -> { progressBar.setVisibility(View.GONE); btnLogin.setEnabled(true); // 保存登录状态 saveLoginPreferences(username, password); // 设置当前用户 Data.setCurrentUser(user); // 跳转到主界面 startMainActivity(); }); } @Override public void onFailure(String error) { runOnUiThread(() -> { progressBar.setVisibility(View.GONE); btnLogin.setEnabled(true); Toast.makeText(LoginActivity.this, "登录失败: " + error, Toast.LENGTH_SHORT).show(); }); } }); } private void saveLoginPreferences(String username, String password) { // 在实际应用中,应使用加密方式存储密码 getSharedPreferences("login_prefs", MODE_PRIVATE).edit() .putString("username", username) .putString("password", password) .putBoolean("remember", cbRemember.isChecked()) .putBoolean("auto_login", cbAutoLogin.isChecked()) .apply(); } private void startMainActivity() { Intent intent = new Intent(this, MainActivity.class); startActivity(intent); finish(); // 关闭登录界面 } } ------------------------登录获取初始数据的时候一点问题没有,只有通过基类传递时出错了------------------------------------ java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.kucun2.entity.data.SynchronizableEntity at com.example.kucun2.entity.data.SynchronizableEntity$1.onSuccess(SynchronizableEntity.java:56)
06-28
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值