@@ -21,18 +21,19 @@ import cc.winboll.studio.libappbase.dialogs.SFTPBackupsSettingsDialog;
/**
* BackupUtils 调用实例
* 支持Intent传入双Map参数, 初始化BackupUtils的待备份文件列表
* 支持Intent传入双Map参数+FTP上传目标目录 , 初始化BackupUtils的待备份文件列表和上传路径
* 强制校验FTP上传目录参数, 未传参时直接提示不执行备份
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/30 20:55
* @LastEditTime 2026/02/01 04 :00
* @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 FTP_TARGET_DIR = " /WinBoLLStudio/APPBackups/WinBoLL/ " ;
// FTP服务器上传目标目录-默认值(移除,改为强制传参,无默认值 )
private static final String DEFAULT_ FTP_TARGET_DIR = null ;
// ==================== Intent传参常量( 规范外部调用) ====================
/**
@@ -45,10 +46,16 @@ public class FTPBackupsActivity extends Activity {
* 类型: 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 " ;
// 解析后的双Map 参数( 用于初始化BackupUtils)
// 解析后的参数( 用于初始化BackupUtils)
private Map < String , String > mDataDirFileMap ;
private Map < String , String > mSdcardDirFileMap ;
private String mFtpTargetDir ; // 解析后的FTP上传目标目录( 强制非空)
@Override
protected void onCreate ( Bundle savedInstanceState ) {
@@ -56,38 +63,60 @@ public class FTPBackupsActivity extends Activity {
setContentView ( R . layout . activity_ftp_backups ) ;
// 初始化主线程Handler, 避免子线程更新UI崩溃
mMainHandler = new Handler ( Looper . getMainLooper ( ) ) ;
// 解析Intent中的双Map参数
// 解析Intent中的所有参数( 双Map+FTP上传目录)
parseIntentParams ( ) ;
}
/**
* 解析Intent传入的序列化Map 参数
* 兜底处理:参数为空/类型错误时, 赋值为 null( 兼容BackupUtils初始化逻辑)
* 解析Intent传入的所有 序列化参数
* 兜底处理:双Map解析失败初始化空Map, FTP目录**未传/空则直接 赋值null,不兜底默认值**
*/
private void parseIntentParams ( ) {
Intent intent = getIntent ( ) ;
if ( intent ! = null ) {
// 解析Data目录Map: 强转为HashMap( 实现Serializable, 可序列化传递)
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 = null ;
LogUtils . d ( TAG , " Intent未解析到有效Data目录Map " ) ;
}
// 解析SDCard目录Map: 强转为HashMap( 实现Serializable, 可序列化传递)
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 = null ;
LogUtils . d ( TAG , " Intent未解析到有效SDCard目录Map " ) ;
}
if ( intent = = null ) {
LogUtils . w ( TAG , " parseIntentParams: Intent为null, 参数解析失败 " ) ;
initEmptyParams ( ) ;
return ;
}
// 未解析到参数时, 保持null即可, BackupUtils内部会默认初始化空Map
// 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 ) {
@@ -97,57 +126,68 @@ public class FTPBackupsActivity extends Activity {
/**
* 点击事件: 执行FTP备份( 核心调用方法)
* 主线程仅做UI触发, 所有核心逻辑在子线程执行
* 每次点击都重新解析Intent参数, 保证获取最新的文件列表
* 【强制校验】未传递FTP_TARGET_DIR参数时, 仅吐司提示, 不执行任何操作
* 每次点击都重新解析Intent参数, 保证参数最新
*/
public void onBackups ( View view ) {
ToastUtils . show ( " 开始执行FTP备份, 请勿退出页面... " ) ;
LogUtils . d ( TAG , " 触发FTP备份操作, 开启子线程执行核心逻辑 " ) ;
// 每次点击都重新解析Intent参数, 避免Activity复用导致参数失效
// 1. 重新解析参数,保证最新
parseIntentParams ( ) ;
SFTPBackupsSettingsDialog dlg = new SFTPBackupsSettingsDialog ( this ) ;
SFTPAuthModel authModel = dlg . getSFTPAuthModelFromSP ( this ) ;
if ( authModel = = null ) {
dlg . show ( ) ;
} else {
// 传入解析后的双Map参数
doBackups ( authModel , mDataDirFileMap , mSdcardDirFileMap ) ;
// 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 ) {
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单例, 透传解析后的双Map参数
// 初始化BackupUtils单例, 透传动态解析的FTP上传目录
BackupUtils backupUtils = BackupUtils . getInstance (
getApplicationContext ( ) ,
authModel ,
FTP_TARGET_DIR ,
dataDirFileMap ,
sdcardDirFileMap
ftpTargetDir
) ;
LogUtils . d ( TAG , " 待备份文件初始化完成 → Data目录: " + backupUtils . getAllDataDirFiles ( ) . size ( ) + " 个 | SDCard目录: " + backupUtils . getAllSdcardFiles ( ) . size ( ) + " 个 " ) ;
// 向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上传至服务器: " + FTP_TARGET_DIR ) ;
LogUtils . i ( TAG , " FTP备份全流程执行成功" ) ;
updateUi ( " FTP备份成功! 文件已打包为ZIP上传至服务器: " + ftpTargetDir ) ;
LogUtils . i ( TAG , " FTP备份全流程执行成功,上传目录: " + ftpTargetDir ) ;
} else {
updateUi ( " FTP备份失败! 请检查服务器配置或文件路径 " ) ;
LogUtils . e ( TAG , " FTP备份全流程执行失败" ) ;
LogUtils . e ( TAG , " FTP备份全流程执行失败,上传目录: " + ftpTargetDir ) ;
}
} catch ( IllegalArgumentException e ) {
@@ -168,6 +208,26 @@ public class FTPBackupsActivity extends Activity {
} ) . 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 提示信息
@@ -192,9 +252,10 @@ public class FTPBackupsActivity extends Activity {
if ( mMainHandler ! = null ) {
mMainHandler . removeCallbacksAndMessages ( null ) ;
}
// 清空本地Map 引用, 协助GC( 不影响BackupUtils内部逻辑)
// 清空本地引用, 协助GC
mDataDirFileMap = null ;
mSdcardDirFileMap = null ;
mFtpTargetDir = null ;
LogUtils . d ( TAG , " FTPBackupsActivity销毁: 已释放本地资源, 不影响二次备份初始化 " ) ;
}
}