移除通用FTP服务应用数据备份功能

This commit is contained in:
2026-03-25 19:47:06 +08:00
parent 17d1c2f321
commit 5419fad1cf
8 changed files with 5 additions and 881 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Jan 31 13:02:06 GMT 2026
#Wed Mar 25 11:45:40 GMT 2026
stageCount=12
libraryProject=libappbase
baseVersion=15.15
publishVersion=15.15.11
buildCount=29
buildCount=35
baseBetaVersion=15.15.12

View File

@@ -10,16 +10,10 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.Toolbar;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.appbase.model.TestBean;
import cc.winboll.studio.libappbase.LogActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.libappbase.activities.FTPBackupsActivity;
import cc.winboll.studio.libappbase.dialogs.SFTPBackupsSettingsDialog;
import cc.winboll.studio.libappbase.models.SFTPAuthModel;
import java.util.HashMap;
import android.os.Environment;
import java.io.File;
import cc.winboll.studio.appbase.model.TestBean;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
@@ -164,36 +158,5 @@ public class MainActivity extends Activity {
Intent aboutIntent = new Intent(getApplicationContext(), AboutActivity.class);
startActivity(aboutIntent);
}
public void onFTPBackupsActivity(View view) {
LogUtils.d(TAG, "onFTPBackupsActivity() 调用");
SFTPBackupsSettingsDialog dlg = new SFTPBackupsSettingsDialog(this);
SFTPAuthModel authModel = SFTPBackupsSettingsDialog.getSFTPAuthModelFromSP(this);
if (authModel == null) {
dlg.show();
} else {
// 1. 构建应用Data目录待备份文件Map
HashMap<String, String> dataFileMap = new HashMap<>();
// 存入文件key=唯一标识value=应用Data目录下的相对路径
dataFileMap.put(TestBean.class.getName() + ".json",
getTestBeanRelativePath());
// 构建SDCard目录待备份文件Map与BackupUtils的SdcardMap泛型一致String-String
HashMap<String, String> sdcardFileMap = new HashMap<>();
// 存入文件key=唯一标识value=应用专属外部文件目录下的相对路径
sdcardFileMap.put(TestBean.class.getName() + ".json",
getTestBeanRelativePath());
// 2. 构建Intent指定跳转到FTPBackupsActivity
Intent ftpBackupsIntent = new Intent(getApplicationContext(), FTPBackupsActivity.class);
// 3. 序列化传递Map参数使用FTPBackupsActivity中定义的常量避免硬编码
ftpBackupsIntent.putExtra(FTPBackupsActivity.EXTRA_DATA_DIR_FILE_MAP, dataFileMap);
ftpBackupsIntent.putExtra(FTPBackupsActivity.EXTRA_SDCARD_DIR_FILE_MAP, sdcardFileMap);
// 传递FTP上传目标目录参数路径为/WinBoLLStudio/APPBackups/WinBoLL
ftpBackupsIntent.putExtra(FTPBackupsActivity.EXTRA_FTP_TARGET_DIR, "/WinBoLLStudio/APPBackups/WinBoLL");
// 4. 启动Activity参数自动透传
startActivity(ftpBackupsIntent);
}
}
}

View File

@@ -59,18 +59,6 @@
android:onClick="onToastUtilsTest"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="FTP应用备份"
android:textSize="16sp"
android:textColor="@android:color/white"
android:background="#81C7F5"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onFTPBackupsActivity"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Jan 31 13:02:06 GMT 2026
#Wed Mar 25 11:45:40 GMT 2026
stageCount=12
libraryProject=libappbase
baseVersion=15.15
publishVersion=15.15.11
buildCount=29
buildCount=35
baseBetaVersion=15.15.12

View File

@@ -1,262 +0,0 @@
package cc.winboll.studio.libappbase.activities;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.R;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.libappbase.models.SFTPAuthModel;
import cc.winboll.studio.libappbase.utils.BackupUtils;
import cc.winboll.studio.libappbase.utils.FTPUtils;
import cc.winboll.studio.libappbase.dialogs.SFTPBackupsSettingsDialog;
/**
* BackupUtils 调用实例
* 支持Intent传入双Map参数+FTP上传目标目录初始化BackupUtils的待备份文件列表和上传路径
* 强制校验FTP上传目录参数未传参时直接提示不执行备份
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/30 20:55
* @LastEditTime 2026/02/01 11:00
*/
public class FTPBackupsActivity extends Activity {
public static final String TAG = "FTPBackupsActivity";
// 主线程Handler子线程更新UI专用
private Handler mMainHandler;
// FTP服务器上传目标目录-默认值(移除,改为强制传参,无默认值)
private static final String DEFAULT_FTP_TARGET_DIR = null;
// ==================== Intent传参常量规范外部调用====================
/**
* Intent传入参数-Data目录待备份文件Map
* 类型HashMap<String,String> 实现Serializable接口
*/
public static final String EXTRA_DATA_DIR_FILE_MAP = "extra_data_dir_file_map";
/**
* Intent传入参数-SDCard目录待备份文件Map
* 类型HashMap<String,String> 实现Serializable接口
*/
public static final String EXTRA_SDCARD_DIR_FILE_MAP = "extra_sdcard_dir_file_map";
/**
* Intent传入参数-FTP/SFTP服务器上传目标目录【强制传参】
* 类型String 示例:/backup/app/ ,必须传参否则不执行备份
*/
public static final String EXTRA_FTP_TARGET_DIR = "extra_ftp_target_dir";
// 解析后的参数用于初始化BackupUtils
private Map<String, String> mDataDirFileMap;
private Map<String, String> mSdcardDirFileMap;
private String mFtpTargetDir; // 解析后的FTP上传目标目录强制非空
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ftp_backups);
// 初始化主线程Handler避免子线程更新UI崩溃
mMainHandler = new Handler(Looper.getMainLooper());
// 解析Intent中的所有参数双Map+FTP上传目录
parseIntentParams();
}
/**
* 解析Intent传入的所有序列化参数
* 兜底处理双Map解析失败初始化空MapFTP目录**未传/空则直接赋值null不兜底默认值**
*/
private void parseIntentParams() {
Intent intent = getIntent();
if (intent == null) {
LogUtils.w(TAG, "parseIntentParamsIntent为null参数解析失败");
initEmptyParams();
return;
}
// 1. 解析Data目录Map强转为HashMap失败则初始化空Map
Serializable dataMapSer = intent.getSerializableExtra(EXTRA_DATA_DIR_FILE_MAP);
if (dataMapSer instanceof HashMap) {
mDataDirFileMap = (HashMap<String, String>) dataMapSer;
LogUtils.d(TAG, "Intent解析Data目录Map成功" + mDataDirFileMap.size() + "个文件");
} else {
mDataDirFileMap = new HashMap<>();
LogUtils.d(TAG, "Intent未解析到有效Data目录Map初始化空Map");
}
// 2. 解析SDCard目录Map强转为HashMap失败则初始化空Map
Serializable sdcardMapSer = intent.getSerializableExtra(EXTRA_SDCARD_DIR_FILE_MAP);
if (sdcardMapSer instanceof HashMap) {
mSdcardDirFileMap = (HashMap<String, String>) sdcardMapSer;
LogUtils.d(TAG, "Intent解析SDCard目录Map成功" + mSdcardDirFileMap.size() + "个文件");
} else {
mSdcardDirFileMap = new HashMap<>();
LogUtils.d(TAG, "Intent未解析到有效SDCard目录Map初始化空Map");
}
// 3. 解析FTP上传目标目录**强制校验,未传/空/空格则直接赋值null不兜底**
String inputFtpDir = intent.getStringExtra(EXTRA_FTP_TARGET_DIR);
if (inputFtpDir != null && !inputFtpDir.trim().isEmpty()) {
mFtpTargetDir = inputFtpDir.trim().endsWith("/") ? inputFtpDir.trim() : inputFtpDir.trim() + "/";
LogUtils.d(TAG, "Intent解析FTP上传目录成功" + mFtpTargetDir);
} else {
mFtpTargetDir = null;
LogUtils.w(TAG, "Intent未传递/传递空的FTP上传目录参数禁止执行备份");
}
}
/**
* 初始化空参数Intent为null时使用FTP目录直接赋值null
*/
private void initEmptyParams() {
mDataDirFileMap = new HashMap<>();
mSdcardDirFileMap = new HashMap<>();
mFtpTargetDir = null;
}
public void onSFTPSettings(View view) {
SFTPBackupsSettingsDialog dlg = new SFTPBackupsSettingsDialog(this);
dlg.show();
}
/**
* 点击事件执行FTP备份核心调用方法
* 【强制校验】未传递FTP_TARGET_DIR参数时仅吐司提示不执行任何操作
* 每次点击都重新解析Intent参数保证参数最新
*/
public void onBackups(View view) {
// 1. 重新解析参数,保证最新
parseIntentParams();
// 2. 强制核心校验FTP上传目录未传参直接吐司提示不执行后续操作
if (mFtpTargetDir == null) {
ToastUtils.show("备份失败未设置FTP上传目标目录参数");
LogUtils.e(TAG, "触发备份操作失败Intent未传递EXTRA_FTP_TARGET_DIR参数");
return;
}
// 3. 校验SFTP配置未配置则弹框
SFTPAuthModel authModel = SFTPBackupsSettingsDialog.getSFTPAuthModelFromSP(this);
if (authModel == null) {
ToastUtils.show("备份失败请先配置SFTP服务器信息");
new SFTPBackupsSettingsDialog(this).show();
LogUtils.w(TAG, "触发备份操作失败未配置SFTP服务器信息");
return;
}
// 4. 所有校验通过,提示并执行备份
ToastUtils.show("开始执行FTP备份请勿退出页面...");
LogUtils.d(TAG, "所有校验通过,开启子线程执行备份核心逻辑");
doBackups(authModel, mDataDirFileMap, mSdcardDirFileMap, mFtpTargetDir);
}
/**
* 核心备份逻辑:子线程执行(仅在校验通过后调用)
* @param authModel SFTP认证模型
* @param dataDirFileMap Intent解析的Data目录文件Map
* @param sdcardDirFileMap Intent解析的SDCard目录文件Map
* @param ftpTargetDir Intent解析的FTP上传目标目录已标准化、非空
*/
void doBackups(final SFTPAuthModel authModel, final Map<String, String> dataDirFileMap,
final Map<String, String> sdcardDirFileMap, final String ftpTargetDir) {
// 所有BackupUtils操作放入子线程规避网络/IO主线程异常
new Thread(new Runnable() {
@Override
public void run() {
try {
// 初始化BackupUtils单例透传动态解析的FTP上传目录
BackupUtils backupUtils = BackupUtils.getInstance(
getApplicationContext(),
authModel,
ftpTargetDir
);
// 向BackupUtils添加解析后的待备份文件双Map
addBackupFilesToUtils(backupUtils, dataDirFileMap, sdcardDirFileMap);
LogUtils.d(TAG, "待备份文件初始化完成 → Data目录" + backupUtils.getAllDataDirFiles().size() + "个 | SDCard目录" + backupUtils.getAllSdcardFiles().size() + "个 | 上传目录:" + ftpTargetDir);
// 核心执行打包为ZIP + SFTP上传内部已实现登录→打包→上传→登出
boolean isSuccess = backupUtils.packAndUploadByFtp();
// 主线程反馈执行结果
if (isSuccess) {
updateUi("FTP备份成功文件已打包为ZIP上传至服务器" + ftpTargetDir);
LogUtils.i(TAG, "FTP备份全流程执行成功上传目录" + ftpTargetDir);
} else {
updateUi("FTP备份失败请检查服务器配置或文件路径");
LogUtils.e(TAG, "FTP备份全流程执行失败上传目录" + ftpTargetDir);
}
} catch (IllegalArgumentException e) {
// 捕获初始化参数异常
LogUtils.e(TAG, "BackupUtils初始化失败" + e.getMessage(), e);
updateUi("备份初始化失败:" + e.getMessage());
} catch (Exception e) {
// 捕获所有执行异常
LogUtils.e(TAG, "FTP备份执行异常" + e.getMessage(), e);
updateUi("备份执行失败:" + e.getMessage());
} finally {
// 仅保留FTP断连BackupUtils内部已兜底登出/断连,此处做双重保障
FTPUtils.getInstance().disconnect();
// 关键日志明确仅删除临时ZIP包不修改/删除源文件
LogUtils.d(TAG, "备份流程结束仅删除本地临时ZIP打包文件待备份源文件未做任何修改/删除");
}
}
}).start();
}
/**
* 向BackupUtils添加待备份文件遍历双Map调用add方法逐个添加
* 先清空原有文件避免Activity复用导致文件重复
* @param backupUtils BackupUtils实例
* @param dataMap Data目录待备份文件Map
* @param sdcardMap SDCard目录待备份文件Map
*/
private void addBackupFilesToUtils(BackupUtils backupUtils, Map<String, String> dataMap, Map<String, String> sdcardMap) {
backupUtils.clearDataDirFiles();
backupUtils.clearSdcardFiles();
// 添加Data目录文件
for (Map.Entry<String, String> entry : dataMap.entrySet()) {
backupUtils.addDataDirFile(entry.getKey(), entry.getValue());
}
// 添加SDCard目录文件
for (Map.Entry<String, String> entry : sdcardMap.entrySet()) {
backupUtils.addSdcardFile(entry.getKey(), entry.getValue());
}
}
/**
* 子线程更新UI工具方法Toast提示
* @param msg 提示信息
*/
private void updateUi(final String msg) {
mMainHandler.post(new Runnable() {
@Override
public void run() {
ToastUtils.show(msg);
}
});
}
/**
* 页面销毁:释放资源,避免内存泄漏
* 仅释放本地资源不清空Map/重复断连FTP
*/
@Override
protected void onDestroy() {
super.onDestroy();
// 移除Handler未执行的任务
if (mMainHandler != null) {
mMainHandler.removeCallbacksAndMessages(null);
}
// 清空本地引用协助GC
mDataDirFileMap = null;
mSdcardDirFileMap = null;
mFtpTargetDir = null;
LogUtils.d(TAG, "FTPBackupsActivity销毁已释放本地资源不影响二次备份初始化");
}
}

View File

@@ -1,372 +0,0 @@
package cc.winboll.studio.libappbase.dialogs;
import android.app.AlertDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.R;
import cc.winboll.studio.libappbase.models.SFTPAuthModel;
/**
* SFTP备份设置对话框
* 功能1.布局式UI展示SFTP配置项 2.SP持久化存储SFTPAuthModel模型数据 3.提供公开静态方法供外部读取/清空SP配置
* 存储模式SharedPreferences私有模式仅本应用可访问
* UI实现通过XML布局文件自定义控件支持账号密码/秘钥双登录方式配置
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/31 12:36:00
* @LastEditTime 2026/01/31 21:58:00
*/
public class SFTPBackupsSettingsDialog {
// ************************ 常量属性 ************************
public static final String TAG = "SFTPBackupsSettingsDialog";
// SP存储核心常量
private static final String SP_NAME = "sftp_backup_settings_sp";
private static final String SP_KEY_PREFIX = "sftp_backup_";
// SP各字段存储Key
private static final String KEY_SERVER = SP_KEY_PREFIX + "server";
private static final String KEY_PORT = SP_KEY_PREFIX + "port";
private static final String KEY_USERNAME = SP_KEY_PREFIX + "username";
private static final String KEY_PASSWORD = SP_KEY_PREFIX + "password";
private static final String KEY_KEY_PATH = SP_KEY_PREFIX + "key_path";
private static final String KEY_KEY_PWD = SP_KEY_PREFIX + "key_pwd";
private static final String KEY_ACTIVE_MODE = SP_KEY_PREFIX + "active_mode";
private static final String KEY_CHARSET = SP_KEY_PREFIX + "charset";
// SFTP默认配置常量抽离便于维护
private static final int SFTP_DEFAULT_PORT = 22;
private static final String SFTP_DEFAULT_CHARSET = "UTF-8";
// ************************ 成员属性 ************************
// 上下文与对话框核心实例
private final Context mContext;
private AlertDialog mDialog;
// 布局控件实例(按配置模块排序,便于查找)
private EditText etServer;
private EditText etPort;
private EditText etUsername;
private EditText etPwd;
private EditText etKeyPath;
private EditText etKeyPwd;
private EditText etCharset;
private CheckBox cbActiveMode;
private Button btnSave;
private Button btnCancel;
private Button btnClear;
// ************************ 构造方法 ************************
/**
* 构造方法:初始化对话框核心依赖与流程
* @param context 上下文推荐Application/Activity避免内存泄漏
*/
public SFTPBackupsSettingsDialog(Context context) {
LogUtils.d(TAG, "SFTPBackupsSettingsDialog构造方法调用传入上下文" + context.getClass().getSimpleName());
this.mContext = context;
initDialog();
initView();
initData();
initListener();
LogUtils.d(TAG, "SFTPBackupsSettingsDialog初始化全流程执行完成");
}
// ************************ 公共方法 ************************
/**
* 显示SFTP配置对话框做非空/非显示校验,避免重复显示)
*/
public void show() {
LogUtils.d(TAG, "show()方法调用,对话框当前状态:" + (mDialog == null ? "未初始化" : mDialog.isShowing() ? "已显示" : "未显示"));
if (mDialog != null && !mDialog.isShowing()) {
mDialog.show();
LogUtils.i(TAG, "SFTP配置对话框显示成功");
}
}
/**
* 【公开静态方法】从SP读取SFTPAuthModel配置模型
* 外部可直接调用无有效配置服务器地址空返回null
* @param context 上下文
* @return 已存储的配置模型/无有效配置返回null
*/
public static SFTPAuthModel getSFTPAuthModelFromSP(Context context) {
LogUtils.d(TAG, "getSFTPAuthModelFromSP()静态方法调用,传入上下文:" + (context == null ? "null" : context.getClass().getSimpleName()));
try {
// 前置非空校验,避免空指针
if (context == null) {
LogUtils.e(TAG, "getSFTPAuthModelFromSP()执行失败传入上下文为null");
return null;
}
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
SFTPAuthModel model = new SFTPAuthModel();
// 读取基础配置
model.setFtpServer(sp.getString(KEY_SERVER, null));
model.setFtpPort(sp.getInt(KEY_PORT, SFTP_DEFAULT_PORT));
model.setFtpUsername(sp.getString(KEY_USERNAME, null));
model.setFtpPassword(sp.getString(KEY_PASSWORD, null));
// 读取秘钥配置
model.setFtpKeyPath(sp.getString(KEY_KEY_PATH, null));
model.setFtpKeyPwd(sp.getString(KEY_KEY_PWD, null));
// 读取高级配置
model.setFtpCharset(sp.getString(KEY_CHARSET, SFTP_DEFAULT_CHARSET));
// 核心有效校验:服务器地址为空则判定为无有效配置
if (TextUtils.isEmpty(model.getFtpServer())) {
LogUtils.w(TAG, "getSFTPAuthModelFromSP()执行完成SP中无有效SFTP配置服务器地址为空");
return null;
}
LogUtils.i(TAG, "getSFTPAuthModelFromSP()执行成功读取到SFTP配置" + model.getFtpServer() + ":" + model.getFtpPort());
return model;
} catch (Exception e) {
LogUtils.e(TAG, "getSFTPAuthModelFromSP()执行异常:" + e.getMessage(), e);
return null;
}
}
/**
* 【公开静态方法】清空SP中所有SFTP配置数据
* 外部可直接调用,做前置非空校验避免崩溃
* @param context 上下文
*/
public static void clearSFTPAuthModelFromSP(Context context) {
LogUtils.d(TAG, "clearSFTPAuthModelFromSP()静态方法调用,传入上下文:" + (context == null ? "null" : context.getClass().getSimpleName()));
try {
if (context == null) {
LogUtils.e(TAG, "clearSFTPAuthModelFromSP()执行失败传入上下文为null");
return;
}
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.clear();
editor.commit();
LogUtils.i(TAG, "clearSFTPAuthModelFromSP()执行成功SP中SFTP配置已全部清空");
} catch (Exception e) {
LogUtils.e(TAG, "clearSFTPAuthModelFromSP()执行异常:" + e.getMessage(), e);
}
}
// ************************ 私有初始化方法 ************************
/**
* 初始化对话框基础样式与布局绑定
*/
private void initDialog() {
LogUtils.d(TAG, "initDialog()方法调用,开始初始化对话框基础样式");
View dialogView = LayoutInflater.from(mContext).inflate(R.layout.dialog_sftp_backup_settings, null);
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setView(dialogView);
builder.setCancelable(false); // 禁止外部点击/返回键关闭
mDialog = builder.create();
// 对话框样式适配,解决全屏/背景异常问题
if (mDialog.getWindow() != null) {
mDialog.getWindow().setBackgroundDrawableResource(android.R.color.white);
}
LogUtils.d(TAG, "initDialog()执行完成,对话框基础实例创建成功");
}
/**
* 初始化布局控件绑定XML控件ID与Java实例一一对应
*/
private void initView() {
LogUtils.d(TAG, "initView()方法调用,开始绑定布局控件实例");
View rootView = mDialog.getLayoutInflater().inflate(R.layout.dialog_sftp_backup_settings, null);
// 基础配置控件
etServer = rootView.findViewById(R.id.et_sftp_server);
etPort = rootView.findViewById(R.id.et_sftp_port);
etUsername = rootView.findViewById(R.id.et_sftp_username);
etPwd = rootView.findViewById(R.id.et_sftp_pwd);
// 秘钥配置控件
etKeyPath = rootView.findViewById(R.id.et_sftp_key_path);
etKeyPwd = rootView.findViewById(R.id.et_sftp_key_pwd);
// 高级配置控件
etCharset = rootView.findViewById(R.id.et_sftp_charset);
// 操作按钮控件
btnSave = rootView.findViewById(R.id.btn_sftp_save);
btnCancel = rootView.findViewById(R.id.btn_sftp_cancel);
btnClear = rootView.findViewById(R.id.btn_sftp_clear);
// 重新绑定对话框View确保控件生效
mDialog.setView(rootView);
LogUtils.d(TAG, "initView()执行完成,所有布局控件绑定成功");
}
/**
* 初始化控件数据从SP读取配置并回显到控件无配置则设默认值
*/
private void initData() {
LogUtils.d(TAG, "initData()方法调用开始回显SP配置到控件");
SFTPAuthModel model = getSFTPAuthModelFromSP(mContext);
if (model == null) {
LogUtils.w(TAG, "initData()执行提示SP中无有效SFTP配置控件设置默认值");
// 无配置时设置默认值,提升用户体验
etPort.setText(String.valueOf(SFTP_DEFAULT_PORT));
etCharset.setText(SFTP_DEFAULT_CHARSET);
return;
}
// 回显基础配置(做非空校验,避免空指针)
etServer.setText(TextUtils.isEmpty(model.getFtpServer()) ? "" : model.getFtpServer());
etPort.setText(model.getFtpPort() <= 0 ? String.valueOf(SFTP_DEFAULT_PORT) : String.valueOf(model.getFtpPort()));
etUsername.setText(TextUtils.isEmpty(model.getFtpUsername()) ? "" : model.getFtpUsername());
etPwd.setText(TextUtils.isEmpty(model.getFtpPassword()) ? "" : model.getFtpPassword());
// 回显秘钥配置
etKeyPath.setText(TextUtils.isEmpty(model.getFtpKeyPath()) ? "" : model.getFtpKeyPath());
etKeyPwd.setText(TextUtils.isEmpty(model.getFtpKeyPwd()) ? "" : model.getFtpKeyPwd());
// 回显高级配置
etCharset.setText(TextUtils.isEmpty(model.getFtpCharset()) ? SFTP_DEFAULT_CHARSET : model.getFtpCharset());
LogUtils.d(TAG, "initData()执行完成SP配置已成功回显到所有控件");
}
/**
* 初始化控件监听为所有按钮设置Java7原生点击事件匿名内部类
*/
private void initListener() {
LogUtils.d(TAG, "initListener()方法调用,开始设置控件点击事件");
// 保存按钮点击事件
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onSaveClick();
}
});
// 取消按钮点击事件
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onCancelClick();
}
});
// 清空按钮点击事件
btnClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onClearClick();
}
});
LogUtils.d(TAG, "initListener()执行完成,所有按钮点击事件设置成功");
}
// ************************ 私有事件处理方法 ************************
/**
* 保存按钮点击事件处理控件数据→模型封装→有效性校验→SP持久化
*/
private void onSaveClick() {
LogUtils.d(TAG, "onSaveClick()方法调用,开始处理配置保存逻辑");
SFTPAuthModel model = getModelFromView();
if (checkModelValid(model)) {
saveSFTPAuthModelToSP(mContext, model);
Toast.makeText(mContext, "SFTP配置保存成功", Toast.LENGTH_SHORT).show();
mDialog.dismiss();
LogUtils.i(TAG, "SFTP配置保存成功服务器配置" + model.getFtpServer() + ":" + model.getFtpPort());
}
}
/**
* 取消按钮点击事件处理:直接关闭对话框,不做任何数据修改
*/
private void onCancelClick() {
LogUtils.d(TAG, "onCancelClick()方法调用,开始处理取消配置逻辑");
mDialog.dismiss();
LogUtils.i(TAG, "SFTP配置对话框已关闭用户主动取消");
}
/**
* 清空按钮点击事件处理清空所有控件输入数据恢复默认值不修改SP
*/
private void onClearClick() {
LogUtils.d(TAG, "onClearClick()方法调用,开始处理控件数据清空逻辑");
// 清空基础配置控件
etServer.setText("");
etPort.setText(String.valueOf(SFTP_DEFAULT_PORT));
etUsername.setText("");
etPwd.setText("");
// 清空秘钥配置控件
etKeyPath.setText("");
etKeyPwd.setText("");
// 清空高级配置控件
cbActiveMode.setChecked(false);
etCharset.setText(SFTP_DEFAULT_CHARSET);
// 提示用户
Toast.makeText(mContext, "控件数据已清空", Toast.LENGTH_SHORT).show();
LogUtils.d(TAG, "onClearClick()执行完成,所有控件数据已恢复默认值");
}
// ************************ 私有业务方法 ************************
/**
* 从控件读取输入数据封装为SFTPAuthModel模型
* 做基础非空处理避免空字符串存入SP
* @return 封装后的SFTP配置模型
*/
private SFTPAuthModel getModelFromView() {
LogUtils.d(TAG, "getModelFromView()方法调用,开始从控件读取数据封装模型");
SFTPAuthModel model = new SFTPAuthModel();
// 读取基础配置trim去空格避免无效空格
model.setFtpServer(etServer.getText().toString().trim());
String portStr = etPort.getText().toString().trim();
model.setFtpPort(TextUtils.isEmpty(portStr) ? SFTP_DEFAULT_PORT : Integer.parseInt(portStr));
model.setFtpUsername(etUsername.getText().toString().trim());
model.setFtpPassword(etPwd.getText().toString().trim());
// 读取秘钥配置
model.setFtpKeyPath(etKeyPath.getText().toString().trim());
model.setFtpKeyPwd(etKeyPwd.getText().toString().trim());
// 读取高级配置
model.setFtpCharset(TextUtils.isEmpty(etCharset.getText().toString().trim()) ? SFTP_DEFAULT_CHARSET : etCharset.getText().toString().trim());
LogUtils.d(TAG, "getModelFromView()执行完成控件数据已成功封装为SFTPAuthModel");
return model;
}
/**
* 校验SFTP配置模型的有效性核心必选参数校验
* 仅校验服务器地址和端口号,其他参数为可选
* @param model 待校验的SFTP配置模型
* @return true=配置有效 false=配置无效
*/
private boolean checkModelValid(SFTPAuthModel model) {
LogUtils.d(TAG, "checkModelValid()方法调用,开始校验配置模型有效性");
// 校验服务器地址(必选)
if (TextUtils.isEmpty(model.getFtpServer())) {
Toast.makeText(mContext, "请输入SFTP服务器地址", Toast.LENGTH_SHORT).show();
LogUtils.w(TAG, "配置校验失败SFTP服务器地址为空");
return false;
}
// 校验端口号必选1-65535合法范围
if (model.getFtpPort() <= 0 || model.getFtpPort() > 65535) {
Toast.makeText(mContext, "请输入有效的端口号1-65535", Toast.LENGTH_SHORT).show();
LogUtils.w(TAG, "配置校验失败SFTP端口号无效当前值" + model.getFtpPort());
return false;
}
LogUtils.d(TAG, "checkModelValid()执行完成SFTP配置模型核心参数校验通过");
return true;
}
/**
* 将SFTPAuthModel模型数据存入SP私有方法仅内部调用
* 使用commit同步提交保证配置即时生效
* @param context 上下文
* @param model 待存储的SFTP配置模型
*/
private static void saveSFTPAuthModelToSP(Context context, SFTPAuthModel model) {
LogUtils.d(TAG, "saveSFTPAuthModelToSP()方法调用准备存储SFTP配置" + model.getFtpServer() + ":" + model.getFtpPort());
try {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
// 存储基础配置
editor.putString(KEY_SERVER, model.getFtpServer());
editor.putInt(KEY_PORT, model.getFtpPort());
editor.putString(KEY_USERNAME, model.getFtpUsername());
editor.putString(KEY_PASSWORD, model.getFtpPassword());
// 存储秘钥配置
editor.putString(KEY_KEY_PATH, model.getFtpKeyPath());
editor.putString(KEY_KEY_PWD, model.getFtpKeyPwd());
// 存储高级配置
editor.putString(KEY_CHARSET, model.getFtpCharset());
// 同步提交保证配置即时生效区别于apply异步
editor.commit();
LogUtils.d(TAG, "saveSFTPAuthModelToSP()执行完成SFTP配置已成功存入SP");
} catch (Exception e) {
LogUtils.e(TAG, "saveSFTPAuthModelToSP()执行异常:" + e.getMessage(), e);
}
}
}

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<Button
android:layout_width="48dp"
android:layout_height="48dp"
android:text="⚙️"
android:onClick="onSFTPSettings"/>
<Button
android:layout_width="wrap_content"
android:layout_height="48dp"
android:text="Backups"
android:onClick="onBackups"/>
</LinearLayout>
</LinearLayout>

View File

@@ -1,163 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp"
android:gravity="center_horizontal">
<!-- 标题 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SFTP备份配置"
android:textSize="18sp"
android:textColor="#333333"
android:layout_marginBottom="20dp"/>
<!-- 基础配置区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="基础配置"
android:textSize="16sp"
android:textColor="#666666"
android:layout_marginBottom="10dp"/>
<EditText
android:id="@+id/et_sftp_server"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="SFTP服务器地址IP/域名)"
android:paddingHorizontal="15dp"
android:background="@drawable/shape_edittext_bg"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/et_sftp_port"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="端口号默认22"
android:inputType="number"
android:paddingHorizontal="15dp"
android:background="@drawable/shape_edittext_bg"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/et_sftp_username"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="登录用户名"
android:paddingHorizontal="15dp"
android:background="@drawable/shape_edittext_bg"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/et_sftp_pwd"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="登录密码"
android:inputType="textPassword"
android:paddingHorizontal="15dp"
android:background="@drawable/shape_edittext_bg"/>
</LinearLayout>
<!-- 秘钥配置区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="秘钥配置(可选,优先账号密码)"
android:textSize="16sp"
android:textColor="#666666"
android:layout_marginBottom="10dp"/>
<EditText
android:id="@+id/et_sftp_key_path"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="秘钥本地路径(如/sdcard/key.pem"
android:paddingHorizontal="15dp"
android:background="@drawable/shape_edittext_bg"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/et_sftp_key_pwd"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="秘钥密码(无则留空)"
android:inputType="textPassword"
android:paddingHorizontal="15dp"
android:background="@drawable/shape_edittext_bg"/>
</LinearLayout>
<!-- 高级配置区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="高级配置"
android:textSize="16sp"
android:textColor="#666666"
android:layout_marginBottom="10dp"/>
<EditText
android:id="@+id/et_sftp_charset"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="编码默认UTF-8"
android:paddingHorizontal="15dp"
android:background="@drawable/shape_edittext_bg"
android:text="UTF-8"/>
</LinearLayout>
<!-- 操作按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:spacing="10dp">
<Button
android:id="@+id/btn_sftp_clear"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:text="清空"
android:backgroundTint="#FF9500"
android:textColor="#FFFFFF"/>
<Button
android:id="@+id/btn_sftp_cancel"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:text="取消"
android:backgroundTint="#999999"
android:textColor="#FFFFFF"/>
<Button
android:id="@+id/btn_sftp_save"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:text="保存"
android:backgroundTint="#007AFF"
android:textColor="#FFFFFF"/>
</LinearLayout>
</LinearLayout>