AndroidApp/LoginActivity.java

853 lines
35 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.example.myapplication;
import static android.content.ContentValues.TAG;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Filter;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.example.myapplication.DataBase.DatabaseHelper;
import com.example.myapplication.Tool.AuthInterceptor;
import com.example.myapplication.Tool.RetrofitClient;
import com.example.myapplication.Tool.ShowError;
import com.example.myapplication.api.AuthApi;
import com.example.myapplication.api.ProjectApi;
import com.example.myapplication.api.UserApi;
import com.example.myapplication.model.ApiResponse;
import com.example.myapplication.model.LoginEntity;
import com.example.myapplication.model.LoginRequest;
import com.example.myapplication.model.PageResult;
import com.example.myapplication.model.Project;
import com.example.myapplication.model.UserInfo;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.PUT;
public class LoginActivity extends AppCompatActivity {
private EditText etUsername, etPassword;
private CheckBox cbRemember;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private final ExecutorService executor = Executors.newFixedThreadPool(7);
private Button btnClearProject;
private Button btnLogin;
private DatabaseHelper dbHelper;
private SharedPreferences sharedPreferences;
private final String AUTH_TOKEN_KEY = "auth_token";
private static final String Name = "name";
private static final String BASE_URL = "http://pms.dtyx.net:9158/";
private String authToken = "";
private static final String PREFS_NAME = "LoginPrefs";
private static final String PREF_USERNAME = "username";
private static final String PREF_PASSWORD = "password";
private static final String PREF_REMEMBER = "remember";
private static final String PREF_PROJECT = "project"; // 新增项目键名
private AutoCompleteTextView actvProject;
private ArrayAdapter<Project> projectAdapter;
private TextView tvSelectedProjectName;
private TextView tvSelectedProjectId;
private List<Project> projectList = new ArrayList<>();
ShowError errorShower = new ShowError(this);
@SuppressLint("SuspiciousIndentation")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
dbHelper = new DatabaseHelper(this);
// 使用 Context.MODE_PRIVATE 替代 MODE_PRIVATE 常量
sharedPreferences = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
initViews();
setupListeners();
loadSavedPreferences();
loadProjects();
}
private void initViews() {
etUsername = findViewById(R.id.etUsername);
etPassword = findViewById(R.id.etPassword);
actvProject = findViewById(R.id.etProject);
cbRemember = findViewById(R.id.cbRemember);
btnLogin = findViewById(R.id.btnLogin);
btnClearProject = findViewById(R.id.btnClearProject);
tvSelectedProjectName = findViewById(R.id.tvSelectedProjectName);
tvSelectedProjectId = findViewById(R.id.tvSelectedProjectId);
// 初始化项目下拉框
initProjectDropdown();
}
private void setupListeners() {
// 清空项目按钮点击事件
btnClearProject.setOnClickListener(v -> {
actvProject.setText("");
tvSelectedProjectName.setText("项目名称:未选择");
tvSelectedProjectId.setText("项目ID未选择");
loadProjects();
projectAdapter.getFilter().filter("");
actvProject.showDropDown();
});
btnLogin.setOnClickListener(v -> attemptLogin());
}
private boolean isNetworkAvailable() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) return false;
NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork());
return capabilities != null &&
(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET));
}
private void loadSavedPreferences() {
boolean remember = sharedPreferences.getBoolean(PREF_REMEMBER, false);
if (remember) {
String savedUsername = sharedPreferences.getString(PREF_USERNAME, "");
String savedPassword = sharedPreferences.getString(PREF_PASSWORD, "");
String savedProject = sharedPreferences.getString(PREF_PROJECT, "");
etUsername.setText(savedUsername);
etPassword.setText(savedPassword);
actvProject.setText(savedProject);
if (!savedProject.isEmpty()) {
try {
int lastBracketIndex = savedProject.lastIndexOf("(");
int closingBracketIndex = savedProject.lastIndexOf(")");
if (lastBracketIndex != -1 && closingBracketIndex != -1 && closingBracketIndex > lastBracketIndex) {
String projectName = savedProject.substring(0, lastBracketIndex).trim();
String projectId = savedProject.substring(lastBracketIndex + 1, closingBracketIndex).trim();
tvSelectedProjectName.setText("项目名称:" + projectName);
tvSelectedProjectId.setText("项目ID:" + projectId);
}
} catch (Exception e) {
e.printStackTrace();
tvSelectedProjectName.setText("解析错误");
tvSelectedProjectId.setText("");
}
}
cbRemember.setChecked(true);
}
}
private void attemptLogin() {
String username = etUsername.getText().toString().trim();
String password = etPassword.getText().toString().trim();
String projectInput = actvProject.getText().toString().trim();
if (username.isEmpty() || password.isEmpty() || projectInput.isEmpty()) {
Toast.makeText(LoginActivity.this, "请填写所有字段", Toast.LENGTH_SHORT).show();
return;
}
// 在后台线程执行数据库操作
// 先进行认证
if(isNetworkAvailable())
authenticateUser(username, password, projectInput);
else continueLoginProcess(username,password,projectInput);
}
private void authenticateUser(String username, String password, String projectInput) {
executor.execute(() -> {
try {
// 加密密码
String encryptedPassword2 = encryptPassword(username, password);
// 创建认证请求体
LoginRequest loginRequest = new LoginRequest(username, encryptedPassword2);
String token = sharedPreferences.getString(AUTH_TOKEN_KEY, "");
// 初始化Retrofit
Retrofit retrofit = RetrofitClient.getClient(token);
// 创建认证API服务
AuthApi authApi = retrofit.create(AuthApi.class);
// 执行认证请求
Call<ApiResponse<LoginEntity>> call = authApi.login(loginRequest);
Response<ApiResponse<LoginEntity>> response = call.execute();
if (response.isSuccessful() && response.body() != null) {
if (response.body().getCode() == 500200) {
//修改密码操作
runOnUiThread(() -> showChangePasswordDialog(username));
} else if (response.body().getCode() == 200) {
authToken = response.body().getData().getTokenValue();
Log.e("令牌:",authToken);
sharedPreferences.edit().putString(AUTH_TOKEN_KEY, authToken).apply();
Retrofit retrofit2 = RetrofitClient.getClient(authToken);
// 创建UserApi服务
UserApi userApi = retrofit2.create(UserApi.class);
// 执行查询请求只根据account查询
Call<PageResult<UserInfo>> userCall = userApi.getUserList(
authToken, // 注意token前缀根据你的API要求调整
username, // account参数
null, // deptId
null, // mobile
null, // name
null, // userCode
null, // userStatus
null // userType
);
try {
Response<PageResult<UserInfo>> userResponse = userCall.execute();
if (userResponse.isSuccessful() && userResponse.body() != null
&& userResponse.body().getCode() == 200
&& !userResponse.body().getRows().isEmpty()) {
// 获取第一个匹配用户的name
String name = userResponse.body().getRows().get(0).getName();
sharedPreferences.edit().putString(Name, name).apply();
executor.execute(()-> {
continueLoginProcess(username, password, projectInput);
});
} else {
// 查询失败或没有结果使用username作为name
errorShower.showErrorDialog("查询姓名失败" , userResponse.body().getMsg());
sharedPreferences.edit().putString(Name, "").apply();
executor.execute(()-> {
continueLoginProcess(username, password, projectInput);
});
}
} catch (IOException e) {
e.printStackTrace();
// 发生异常使用username作为name
errorShower.showErrorDialog("出现异常:", e.getMessage());
executor.execute(()-> {
continueLoginProcess(username, password, projectInput);
});
}
}
else {
showToast(password+"密码出错"+response.body().toString());
}
} else {
runOnUiThread(() -> {
String errorMsg = "认证失败: ";
if (response.errorBody() != null) {
try {
errorMsg += response.errorBody().string();
} catch (IOException e) {
errorMsg += "无法读取错误详情";
}
}
showToast(errorMsg);
});
}
} catch (Exception e) {
showToast("认证出错:"+e.getMessage());
}
});
}
// 封装 Toast 显示
private void showToast(String message) {
mainHandler.post(() -> Toast.makeText(LoginActivity.this, message, Toast.LENGTH_SHORT).show());
}
private void showChangePasswordDialog(String username) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("请修改密码");
View view = getLayoutInflater().inflate(R.layout.dialog_change_password, null);
EditText etOldPassword = view.findViewById(R.id.etOldPassword);
EditText etNewPassword = view.findViewById(R.id.etNewPassword);
EditText etConfirmPassword = view.findViewById(R.id.etConfirmPassword);
builder.setView(view);
builder.setPositiveButton("确认", (dialog, which) -> {
String oldPassword = etOldPassword.getText().toString().trim();
String newPassword = etNewPassword.getText().toString().trim();
String confirmPassword = etConfirmPassword.getText().toString().trim();
if (newPassword.equals(confirmPassword)) {
changePassword(username, oldPassword, newPassword);
} else {
Toast.makeText(this, "新密码与确认密码不匹配", Toast.LENGTH_SHORT).show();
}
});
builder.setNegativeButton("取消", null);
builder.setCancelable(false);
builder.show();
}
private void changePassword(String username, String oldPassword, String newPassword) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
try {
// 加密旧密码和新密码
String encryptedOldPassword = encryptPassword(username, oldPassword);
String encryptedNewPassword = encryptPassword(username, newPassword);
// 创建修改密码请求体
ChangePasswordRequest request = new ChangePasswordRequest(
username,
encryptedNewPassword,
encryptedOldPassword
);
// 获取token
String token = sharedPreferences.getString(AUTH_TOKEN_KEY, "");
// 初始化Retrofit
Retrofit retrofit = RetrofitClient.getClient(token);
// 创建API服务
AuthApi authApi = retrofit.create(AuthApi.class);
// 执行修改密码请求
Call<ApiResponse<Void>> call = authApi.modifyPassword(request);
Response<ApiResponse<Void>> response = call.execute();
runOnUiThread(() -> {
if (response.isSuccessful() && response.body() != null) {
if (response.body().isSuccess()) {
Toast.makeText(LoginActivity.this, "密码修改成功,请重新登录", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(LoginActivity.this, "密码修改失败: " + response.body().toString(), Toast.LENGTH_SHORT).show();
}
} else {
String errorMsg = "修改密码失败: ";
if (response.errorBody() != null) {
try {
errorMsg += response.errorBody().string();
} catch (IOException e) {
errorMsg += "无法读取错误详情";
}
}
Toast.makeText(LoginActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
}
});
} catch (Exception e) {
runOnUiThread(() ->
Toast.makeText(LoginActivity.this, "修改密码出错: " + e.getMessage(), Toast.LENGTH_SHORT).show());
}
});
}
// 新增修改密码请求体
public static class ChangePasswordRequest {
private String account;
private String newPassword;
private String oldPassword;
public ChangePasswordRequest(String account, String newPassword, String oldPassword) {
this.account = account;
this.newPassword = newPassword;
this.oldPassword = oldPassword;
}
// getters and setters
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
public String getOldPassword() {
return oldPassword;
}
public void setOldPassword(String oldPassword) {
this.oldPassword = oldPassword;
}
}
private void continueLoginProcess(String username, String password, String projectInput) {
// 在后台线程执行数据库操作
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
try {
// 解析项目ID和名称
Pair<String, String> projectInfo = parseProjectInfo(projectInput);
String projectId = projectInfo.first;
String projectName = projectInfo.second;
// 保存登录信息
saveLoginPreferences(username, password, projectName, projectId);
// 检查用户是否存在
boolean userExists = dbHelper.userExists(username);
runOnUiThread(() -> {
if (userExists) {
// 用户存在,验证密码
if (dbHelper.checkUser(username, password)) {
handleLoginSuccess(username, projectId, projectName,password);
} else {
Toast.makeText(LoginActivity.this, "密码错误", Toast.LENGTH_SHORT).show();
}
} else {
// 用户不存在,自动注册
if (dbHelper.addUser(username, password, projectId, projectName)) {
handleLoginSuccess(username, projectId, projectName,password);
} else {
Toast.makeText(LoginActivity.this, "注册失败", Toast.LENGTH_SHORT).show();
}
}
});
} catch (Exception e) {
runOnUiThread(() ->
Toast.makeText(LoginActivity.this, "登录出错: " + e.getMessage(), Toast.LENGTH_SHORT).show());
}
});
}
private String encryptPassword(String username, String password) {
try {
// 1. 对账号做MD5计算
String md5Hash = md5(username);
// 2. 取8-24位作为密钥
String key = md5Hash.substring(8, 24); // 8-24位索引从0开始
Log.e("key:",key);
// 3. AES加密
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // PKCS5Padding等同于PKCS7Padding在Java中
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = cipher.doFinal(password.getBytes());
return Base64.encodeToString(encryptedBytes, Base64.DEFAULT).trim();
} catch (Exception e) {
e.printStackTrace();
showToast("加密密码错误:"+e.getMessage());
return "";
}
}
private String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private Pair<String, String> parseProjectInfo(String projectInput) {
String projectId = "";
String projectName = "";
Matcher matcher = Pattern.compile(".*\\((.*)\\)").matcher(projectInput);
if (matcher.find()) {
projectId = matcher.group(1).trim();
projectName = projectInput.replace("(" + projectId + ")", "").trim();
} else {
for (Project project : projectList) {
if (project.getProjectName().equals(projectInput) ||
project.getProjectId().equals(projectInput)) {
projectId = project.getProjectId();
projectName = project.getProjectName();
break;
}
}
if (projectId.isEmpty()) {
projectName = projectInput;
}
}
return new Pair<>(projectId, projectName);
}
private void saveLoginPreferences(String username, String password, String projectName, String projectId) {
SharedPreferences.Editor editor = sharedPreferences.edit();
if (cbRemember.isChecked()) {
editor.putString(PREF_USERNAME, username);
editor.putString(PREF_PASSWORD, password);
String projectDisplayText = projectId.isEmpty() ? projectName :
(projectName + " (" + projectId + ")");
editor.putString(PREF_PROJECT, projectDisplayText);
editor.putBoolean(PREF_REMEMBER, true);
} else {
editor.clear();
}
editor.apply();
}
private void handleLoginSuccess(String username, String projectId, String projectName,String password) {
dbHelper.updateUserProject(username, projectId, projectName);
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.putExtra("username", username);
intent.putExtra("password",password);
intent.putExtra("projectId", projectId);
intent.putExtra("projectName", projectName);
startActivity(intent);
finish();
}
private void initProjectDropdown() {
projectAdapter = new ArrayAdapter<Project>(this,
R.layout.custom_dropdown_item,
projectList) {
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view = super.getView(position, convertView, parent);
TextView textView = view.findViewById(android.R.id.text1);
Project project = getItem(position);
if (project != null) {
textView.setText(project.getProjectName() + " (" + project.getProjectId() + ")");
textView.setSingleLine(false);
textView.setMaxLines(3);
textView.setEllipsize(null);
}
return view;
}
@Override
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
return getView(position, convertView, parent);
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
List<Project> filteredList = new ArrayList<>();
if (constraint != null && constraint.length() > 0) {
String filterPattern = constraint.toString().toLowerCase().trim();
DatabaseHelper dbHelper = new DatabaseHelper(LoginActivity.this);
filteredList = dbHelper.searchProjects(filterPattern);
} else {
filteredList.addAll(projectList);
}
results.values = filteredList;
results.count = filteredList.size();
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
clear();
if (results.values != null) {
addAll((List<Project>) results.values);
}
notifyDataSetChanged();
}
};
}
};
actvProject.setAdapter(projectAdapter);
actvProject.setThreshold(0);
actvProject.setDropDownBackgroundResource(android.R.color.white);
actvProject.setOnItemClickListener((parent, view, position, id) -> {
Object item = parent.getItemAtPosition(position);
if (item instanceof Project) {
Project selectedProject = (Project) item;
tvSelectedProjectName.setText("项目名称:" + selectedProject.getProjectName());
tvSelectedProjectId.setText("项目ID" + selectedProject.getProjectId());
actvProject.setText(selectedProject.getProjectName() + " (" + selectedProject.getProjectId() + ")");
} else {
Log.e("TYPE_ERROR", "Expected Project but got: " + item.getClass());
Toast.makeText(this, "数据格式错误", Toast.LENGTH_SHORT).show();
}
});
}
private void loadProjects() {
try {
// 1. 验证接口定义
if (!validateApiDefinition()) {
return;
}
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS);
okHttpClientBuilder.addInterceptor(new AuthInterceptor(authToken));
// 2. 初始化Retrofit
Retrofit retrofit = RetrofitClient.getClient(authToken);
// 3. 创建API服务
ProjectApi projectApi = retrofit.create(ProjectApi.class);
// 4. 执行网络请求
executeProjectRequest(projectApi);
} catch (Exception e) {
showErrorOnUI("初始化失败", getErrorMessage(e));
logErrorToFile(e);
}
}
private boolean validateApiDefinition() {
try {
Method method = ProjectApi.class.getMethod("getProjectList");
Type returnType = method.getGenericReturnType();
// 预期的完整类型签名
String expectedType = "retrofit2.Call<com.example.myapplication.model.ApiResponse<java.util.List<com.example.myapplication.model.Project>>>";
if (!returnType.toString().equals(expectedType)) {
String errorMsg = "接口定义错误!\n\n当前定义:\n" + returnType +
"\n\n应修改为:\n@GET(\"project/list\")\nCall<ApiResponse<List<Project>>> getProjectList();";
new Handler(Looper.getMainLooper()).post(() -> {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("API接口定义不合法")
.setMessage(errorMsg)
.setPositiveButton("复制定义", (d, w) -> copyToClipboard(errorMsg))
.setNegativeButton("关闭", null)
.show();
});
return false;
}
return true;
} catch (Exception e) {
showErrorOnUI("接口验证失败", e.getMessage());
return false;
}
}
private void executeProjectRequest(ProjectApi projectApi) {
Call<ApiResponse<List<Project>>> call = projectApi.getProjectList();
call.enqueue(new Callback<ApiResponse<List<Project>>>() {
@Override
public void onResponse(Call<ApiResponse<List<Project>>> call, Response<ApiResponse<List<Project>>> response) {
if (response.isSuccessful() && response.body() != null) {
handleSuccessResponse(response.body().getData());
} else {
handleApiError(response);
}
}
@Override
public void onFailure(Call<ApiResponse<List<Project>>> call, Throwable t) {
handleNetworkError(t);
}
});
}
// === 错误处理方法 ===
private void handleSuccessResponse(List<Project> projects) {
runOnUiThread(() -> {
try {
dbHelper.saveProjects(projects);
projectList.clear();
projectList.addAll(projects);
projectAdapter.notifyDataSetChanged();
} catch (Exception e) {
showErrorOnUI("数据处理错误", e.getMessage());
}
});
}
private void handleApiError(Response<?> response) {
String errorMsg = "服务器响应错误: " + response.code();
try {
if (response.errorBody() != null) {
errorMsg += "\n" + response.errorBody().string();
}
} catch (IOException e) {
errorMsg += "\n无法读取错误详情";
}
showErrorOnUI("API请求失败", errorMsg);
loadCachedProjects();
}
private void handleNetworkError(Throwable t) {
String errorMsg = "网络错误: " + t.getMessage();
if (t instanceof SocketTimeoutException) {
errorMsg = "连接超时,请检查网络";
} else if (t instanceof UnknownHostException) {
errorMsg = "无法解析主机请检查URL";
}
showErrorOnUI("网络连接失败", errorMsg);
loadCachedProjects();
}
// === 工具方法 ===
private void showErrorOnUI(String title, String message) {
runOnUiThread(() -> {
// 在界面底部显示错误面板
ViewGroup rootView = findViewById(android.R.id.content);
LinearLayout errorPanel = new LinearLayout(this);
errorPanel.setOrientation(LinearLayout.VERTICAL);
errorPanel.setBackgroundColor(0x33FF0000);
errorPanel.setPadding(32, 16, 32, 16);
TextView tvError = new TextView(this);
tvError.setTextColor(Color.RED);
tvError.setText(title);
TextView tvMsg = new TextView(this);
tvMsg.setText(message);
errorPanel.addView(tvError);
errorPanel.addView(tvMsg);
rootView.addView(errorPanel);
// 5秒后自动消失
new Handler().postDelayed(() -> rootView.removeView(errorPanel), 5000);
});
}
private void copyToClipboard(String text) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(ClipData.newPlainText("错误定义", text));
Toast.makeText(this, "定义已复制", Toast.LENGTH_SHORT).show();
}
private String getErrorMessage(Exception e) {
if (e instanceof IllegalArgumentException) {
return "参数错误: " + e.getMessage();
} else if (e instanceof NullPointerException) {
return "空指针异常: " + e.getMessage();
} else {
return "未知错误: " + e.getClass().getSimpleName();
}
}
private void logErrorToFile(Exception e) {
String log = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) +
"\nError: " + e.getClass().getName() +
"\nMessage: " + e.getMessage() +
"\nStack Trace:\n" + Log.getStackTraceString(e);
try {
File file = new File(getExternalFilesDir(null), "error_log.txt");
FileWriter writer = new FileWriter(file, true);
writer.append(log).append("\n\n");
writer.close();
} catch (IOException ioException) {
Log.e("FileLog", "无法写入错误日志", ioException);
}
}
private void loadCachedProjects() {
new Thread(() -> {
List<Project> cachedProjects = dbHelper.getAllProjects();
runOnUiThread(() -> {
projectList.clear();
projectList.addAll(cachedProjects);
projectAdapter.notifyDataSetChanged();
Toast.makeText(this, "已加载本地缓存数据", Toast.LENGTH_LONG).show();
});
}).start();
}
}