From 230038f6f3c54fb79734e27a2b03dcb50a6bea65 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Wed, 19 Nov 2025 21:24:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=8B=E8=BD=BD=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E9=A2=84=E8=A7=88=E6=A8=A1=E5=9D=97=EF=BC=88=E6=9C=AA?= =?UTF-8?q?=E8=B0=83=E8=AF=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- powerbell/build.properties | 4 +- .../dialogs/NetworkBackgroundDialog.java | 115 +++++-- .../powerbell/utils/ImageDownloader.java | 294 ++++++++++++++++++ .../res/layout/dialog_networkbackground.xml | 135 +++++--- 4 files changed, 474 insertions(+), 74 deletions(-) create mode 100644 powerbell/src/main/java/cc/winboll/studio/powerbell/utils/ImageDownloader.java diff --git a/powerbell/build.properties b/powerbell/build.properties index a1cddcaf..d0c5cdab 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Nov 19 12:22:35 GMT 2025 +#Wed Nov 19 13:23:38 GMT 2025 stageCount=4 libraryProject= baseVersion=15.11 publishVersion=15.11.3 -buildCount=15 +buildCount=19 baseBetaVersion=15.11.4 diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/dialogs/NetworkBackgroundDialog.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/dialogs/NetworkBackgroundDialog.java index f5a53474..8a93a390 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/dialogs/NetworkBackgroundDialog.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/dialogs/NetworkBackgroundDialog.java @@ -1,14 +1,21 @@ package cc.winboll.studio.powerbell.dialogs; import android.content.Context; +import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; // AndroidX 对话框 -import cc.winboll.studio.powerbell.R; +import androidx.appcompat.app.AlertDialog; import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.powerbell.R; +import cc.winboll.studio.powerbell.utils.ImageDownloader; +import java.io.FileInputStream; +import java.io.IOException; /** * @Author ZhanGSKen&豆包大模型 @@ -17,12 +24,18 @@ import cc.winboll.studio.libappbase.LogUtils; * 继承 AndroidX AlertDialog,绑定自定义布局 dialog_networkbackground.xml */ public class NetworkBackgroundDialog extends AlertDialog { + public static final String TAG = "NetworkBackgroundDialog"; // 控件引用 private TextView tvTitle; private TextView tvContent; private Button btnCancel; private Button btnConfirm; + private Button btnPreview; + private EditText etURL; + //private WebView webViewPreview; + ImageView ivBackgroundPreview; + Context mContext; // 按钮点击回调接口(Java7 接口实现) public interface OnDialogClickListener { @@ -49,9 +62,10 @@ public class NetworkBackgroundDialog extends AlertDialog { * 初始化布局和控件 */ private void initView() { + mContext = this.getContext(); // 加载自定义布局 View dialogView = LayoutInflater.from(getContext()) - .inflate(R.layout.dialog_networkbackground, null); + .inflate(R.layout.dialog_networkbackground, null); // 设置对话框内容视图 setView(dialogView); @@ -60,6 +74,10 @@ public class NetworkBackgroundDialog extends AlertDialog { tvContent = (TextView) dialogView.findViewById(R.id.tv_dialog_content); btnCancel = (Button) dialogView.findViewById(R.id.btn_cancel); btnConfirm = (Button) dialogView.findViewById(R.id.btn_confirm); + btnPreview = (Button) dialogView.findViewById(R.id.btn_preview); + etURL = (EditText) dialogView.findViewById(R.id.et_url); + //webViewPreview = (WebView) dialogView.findViewById(R.id.webview_preview); + ivBackgroundPreview = (ImageView) findViewById(R.id.iv_background_preview); // 设置按钮点击事件 setButtonClickListeners(); @@ -71,27 +89,37 @@ public class NetworkBackgroundDialog extends AlertDialog { private void setButtonClickListeners() { // 取消按钮:关闭对话框 + 回调外部 btnCancel.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - LogUtils.d("NetworkBackgroundDialog", "取消按钮点击"); - dismiss(); // 关闭对话框 - if (listener != null) { - listener.onCancel(); - } - } - }); + @Override + public void onClick(View v) { + LogUtils.d("NetworkBackgroundDialog", "取消按钮点击"); + dismiss(); // 关闭对话框 + if (listener != null) { + listener.onCancel(); + } + } + }); // 确认按钮:关闭对话框 + 回调外部 btnConfirm.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - LogUtils.d("NetworkBackgroundDialog", "确认按钮点击"); - dismiss(); // 关闭对话框 - if (listener != null) { - listener.onConfirm(); - } - } - }); + @Override + public void onClick(View v) { + LogUtils.d("NetworkBackgroundDialog", "确认按钮点击"); + dismiss(); // 关闭对话框 + if (listener != null) { + listener.onConfirm(); + } + } + }); + + // 图片预览按钮:预览输入框地址图片 + btnPreview.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + LogUtils.d("NetworkBackgroundDialog", "确认预览点击"); + String url = etURL.getText().toString(); + ImageDownloader.getInstance(mContext).downloadImage(url, mDownloadCallback); + } + }); } /** @@ -118,5 +146,50 @@ public class NetworkBackgroundDialog extends AlertDialog { public void setOnDialogClickListener(OnDialogClickListener listener) { this.listener = listener; } + + ImageDownloader.DownloadCallback mDownloadCallback = new ImageDownloader.DownloadCallback(){ + + @Override + public void onSuccess(String filePath) { + ToastUtils.show(filePath); + LogUtils.d(TAG, filePath); + + String imagePath = "/storage/emulated/0/Android/data/cc.winboll.studio.powerbell.beta/files/mmexport1713182627221.png"; + //String imagePath = filePath; + + FileInputStream fis = null; + try { + fis = new FileInputStream(imagePath); + Drawable drawable = Drawable.createFromStream(fis, null); + // 设置背景 + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { + ivBackgroundPreview.setBackground(drawable); + } else { + ivBackgroundPreview.setBackgroundDrawable(drawable); + } + } catch (Exception e) { + e.printStackTrace(); + //ivBackgroundPreview.setBackgroundResource(R.drawable.default_error); // 异常时显示默认图 + ivBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher); // 异常时显示默认图 + } finally { + // Java7 手动关闭流,避免资源泄漏 + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + } + + @Override + public void onFailure(String errorMsg) { + ToastUtils.show(errorMsg); + LogUtils.d(TAG, errorMsg); + } + + }; } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/ImageDownloader.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/ImageDownloader.java new file mode 100644 index 00000000..2e1641de --- /dev/null +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/ImageDownloader.java @@ -0,0 +1,294 @@ +package cc.winboll.studio.powerbell.utils; + +import android.content.Context; +import android.os.Environment; +import android.text.TextUtils; +import android.util.Log; +import cc.winboll.studio.libappbase.LogUtils; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/11/19 20:52 + * @Describe 图片下载工具类(单例模式) + * 功能:下载网络图片到缓存目录、清理过期文件、获取最新下载文件 + */ +public class ImageDownloader { + public static final String TAG = "ImageDownloader"; + // 单例实例 + private static ImageDownloader sInstance; + // OkHttp 客户端(全局复用,提升性能) + private OkHttpClient mOkHttpClient; + // 缓存目录:/data/data/应用包名/cache/networkdownload + private File mCacheDir; + // 过期时间:7天(单位:毫秒),可按需调整 + private static final long EXPIRE_TIME = 7 * 24 * 3600 * 1000; + + /** + * 私有构造(单例模式禁止外部实例化) + * @param context 上下文(用于获取缓存目录) + */ + private ImageDownloader(Context context) { + // 初始化 OkHttp 客户端(设置超时时间) + mOkHttpClient = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .build(); + + // 初始化缓存目录:networkdownload + initCacheDir(context); + // 初始化时清理过期文件 + clearExpiredFiles(); + } + + /** + * 单例获取方法(线程安全) + * @param context 上下文(建议使用 Application 上下文避免内存泄漏) + * @return 单例实例 + */ + public static synchronized ImageDownloader getInstance(Context context) { + if (sInstance == null) { + // 使用 Application 上下文,防止 Activity 销毁导致的内存泄漏 + sInstance = new ImageDownloader(context.getApplicationContext()); + } + return sInstance; + } + + /** + * 初始化缓存目录:若不存在则创建 + */ + private void initCacheDir(Context context) { + // 获取应用内置缓存目录(无需权限) + File cacheRoot = context.getCacheDir(); + mCacheDir = new File(cacheRoot, "networkdownload"); + + // 若目录不存在则创建(包括父目录) + if (!mCacheDir.exists()) { + boolean isCreated = mCacheDir.mkdirs(); + if (isCreated) { + LogUtils.d("ImageDownloader", "networkdownload 缓存目录创建成功:" + mCacheDir.getAbsolutePath()); + } else { + LogUtils.e("ImageDownloader", "networkdownload 缓存目录创建失败"); + } + } else { + LogUtils.d("ImageDownloader", "networkdownload 缓存目录已存在:" + mCacheDir.getAbsolutePath()); + } + } + + /** + * 清理过期文件(最后修改时间超过 EXPIRE_TIME 的文件) + */ + private void clearExpiredFiles() { + if (mCacheDir == null || !mCacheDir.exists()) { + return; + } + + File[] files = mCacheDir.listFiles(); + if (files == null || files.length == 0) { + LogUtils.d("ImageDownloader", "缓存目录无文件,无需清理"); + return; + } + + long currentTime = System.currentTimeMillis(); + int deleteCount = 0; + + // 遍历所有文件,删除过期文件 + for (File file : files) { + long lastModifyTime = file.lastModified(); + if (currentTime - lastModifyTime > EXPIRE_TIME) { + if (file.delete()) { + deleteCount++; + LogUtils.d("ImageDownloader", "删除过期文件:" + file.getName()); + } else { + LogUtils.e("ImageDownloader", "删除过期文件失败:" + file.getName()); + } + } + } + + LogUtils.d("ImageDownloader", "过期文件清理完成,共删除 " + deleteCount + " 个文件"); + } + + /** + * 下载网络图片到缓存目录 + * @param imageUrl 图片网络链接 + * @param callback 下载结果回调(成功/失败) + */ + public void downloadImage(final String imageUrl, final DownloadCallback callback) { + // 校验参数 + if (TextUtils.isEmpty(imageUrl)) { + if (callback != null) { + callback.onFailure("图片链接为空"); + } + return; + } + + if (mCacheDir == null || !mCacheDir.exists()) { + if (callback != null) { + callback.onFailure("缓存目录不存在"); + } + return; + } + + // 构建 OkHttp 请求 + Request request = new Request.Builder() + .url(imageUrl) + .build(); + + // 异步下载(避免阻塞主线程) + mOkHttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + // 下载失败,回调主线程 + if (callback != null) { + callback.onFailure("下载失败:" + e.getMessage()); + } + LogUtils.e("ImageDownloader", "图片下载失败:" + e.getMessage()); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (!response.isSuccessful()) { + // 响应失败(如 404、500) + if (callback != null) { + callback.onFailure("响应失败:" + response.code()); + } + LogUtils.e("ImageDownloader", "图片响应失败,状态码:" + response.code()); + return; + } + + // 响应成功,写入文件 + InputStream inputStream = null; + FileOutputStream outputStream = null; + try { + inputStream = response.body().byteStream(); + // 生成 UUID 唯一文件名(保留原文件后缀) + String fileExtension = getFileExtension(imageUrl); + String fileName = UUID.randomUUID().toString() + fileExtension; + File imageFile = new File(mCacheDir, fileName); + + // 写入文件 + outputStream = new FileOutputStream(imageFile); + byte[] buffer = new byte[1024]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + } + outputStream.flush(); + + // 下载成功,回调主线程并返回文件路径 + if (callback != null) { + callback.onSuccess(imageFile.getAbsolutePath()); + } + LogUtils.d("ImageDownloader", "图片下载成功:" + imageFile.getAbsolutePath()); + + } catch (IOException e) { + if (callback != null) { + callback.onFailure("文件写入失败:" + e.getMessage()); + } + LogUtils.e("ImageDownloader", "图片写入失败:" + e.getMessage()); + } finally { + // 关闭流(Java7 手动关闭,避免资源泄漏) + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + // 关闭响应体 + if (response.body() != null) { + response.body().close(); + } + } + } + }); + } + + /** + * 获取 networkdownload 目录中最后下载的文件(按修改时间排序) + * @return 最后下载的文件路径(null 表示无文件) + */ + public String getLastDownloadedFile() { + if (mCacheDir == null || !mCacheDir.exists()) { + LogUtils.e("ImageDownloader", "缓存目录不存在"); + return null; + } + + File[] files = mCacheDir.listFiles(); + if (files == null || files.length == 0) { + LogUtils.d("ImageDownloader", "缓存目录无文件"); + return null; + } + + // 按最后修改时间降序排序,取第一个即为最新文件 + File lastFile = files[0]; + for (File file : files) { + if (file.lastModified() > lastFile.lastModified()) { + lastFile = file; + } + } + + LogUtils.d("ImageDownloader", "最后下载的文件:" + lastFile.getAbsolutePath()); + return lastFile.getAbsolutePath(); + } + + /** + * 工具方法:从图片链接中提取文件后缀(如 .png、.jpg) + * @param imageUrl 图片链接 + * @return 文件后缀(含点号,若无法提取则返回 .jpg) + */ + private String getFileExtension(String imageUrl) { + if (TextUtils.isEmpty(imageUrl)) { + return ".jpg"; + } + + int lastDotIndex = imageUrl.lastIndexOf("."); + int lastSlashIndex = imageUrl.lastIndexOf("/"); + // 确保后缀在最后一个斜杠之后,且长度合理(1-5 个字符) + if (lastDotIndex > lastSlashIndex && lastDotIndex < imageUrl.length() - 1) { + String extension = imageUrl.substring(lastDotIndex); + if (extension.length() <= 5) { + return extension.toLowerCase(); // 统一转为小写 + } + } + + // 无法提取后缀时,默认使用 .jpg + return ".jpg"; + } + + /** + * 下载结果回调接口(Java7 接口实现) + */ + public interface DownloadCallback { + /** + * 下载成功 + * @param filePath 图片保存路径 + */ + void onSuccess(String filePath); + + /** + * 下载失败 + * @param errorMsg 失败原因 + */ + void onFailure(String errorMsg); + } +} + diff --git a/powerbell/src/main/res/layout/dialog_networkbackground.xml b/powerbell/src/main/res/layout/dialog_networkbackground.xml index 635b6292..76469d35 100644 --- a/powerbell/src/main/res/layout/dialog_networkbackground.xml +++ b/powerbell/src/main/res/layout/dialog_networkbackground.xml @@ -1,60 +1,93 @@ - + - - + - - + - - + - -