子线程控制ProgressDialog的可见性

本文通过一个具体的Android应用实例,展示了如何使用Looper机制配合ProgressDialog组件实现异步加载效果。主要涉及Activity布局设置、按钮点击监听及子线程操作。
这个代码让我有点迷惑:

package com.example;

import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

public class Looper_07 extends Activity implements OnClickListener {
private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;
private final int FP = LinearLayout.LayoutParams.FILL_PARENT;
private ProgressDialog progressDialog = null;
public TextView tv;
private Button btn, btn2;
Thread th1;

public void onCreate(Bundle icicle) {
super.onCreate(icicle);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);

btn = new Button(this);
btn.setId(101);
btn.setBackgroundResource(R.drawable.icon);
btn.setText("test looper");
btn.setOnClickListener(this);
LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(100, 50);
param.topMargin = 10;
layout.addView(btn, param);

btn2 = new Button(this);
btn2.setId(102);
btn2.setBackgroundResource(R.drawable.icon);
btn2.setText("exit");
btn2.setOnClickListener(this);
layout.addView(btn2, param);

tv = new TextView(this);
tv.setTextColor(Color.WHITE);
tv.setText("");
LinearLayout.LayoutParams param2 = new LinearLayout.LayoutParams(FP, WC);
param2.topMargin = 10;
layout.addView(tv, param2);
setContentView(layout);
// ------------------------
}

public void onClick(View v) {
switch (v.getId()) {
case 101:
progressDialog = ProgressDialog.show(this,
"please wait…","Loading",true);
th1 = new myThread();
th1.start();
setTitle("mainThread....");
break;
case 102:
finish();
break;
}
}
class myThread extends Thread {
@Override
public void run() {
try{
sleep(3000); //故意延遲
}
catch(Exception e)
{
e.printStackTrace();
}
// 为什么这里可以控制Dialog的可见性????????????
//子线程来执行
progressDialog.dismiss();
}
}
}
/*
上述程式又可寫為:
public class ac01 extends Activity
implements OnClickListener, Runnable{
//………………(省略)
public void onClick(View v) {
switch(v.getId()){
case 101:
progressDialog = ProgressDialog.show(this,
"please wait…","Loading",true);
th1 = new Thread(this);
th1.start();
setTitle("mainThread....");
break;
case 102:
finish();
break;
}
}
public void run() {
try{
Thread.sleep(6000); //故意延遲
}
catch(Exception e)
{
e.printStackTrace();
}
progressDialog.dismiss();
}
}
*/


flowchart TD A[用户在Activity点击按钮] --> B[主线程启动线程(避免UI阻塞,防止ANR)] B --> C[主线程显示ProgressDialog:“软件正在生成中 请稍等...”] C --> D[线程调用FileUtils.copyAssetsToFile: - 输入:当前Activity、Assets内模板文件“dc.zip” - 输出:复到ROOT_DIR(外部存储根目录)/dc.zip - 校验:判断目标文件是否已存在,存在则覆盖] D -->|抛出IOException(如Assets文件不存在/存储权限被拒)| Z[线程捕获异常→主线程显示错误对话框:“生成失败:{IO异常信息}”] D --> E[线程显示ProgressDialog:“验证传入图片合法性...”] E --> F[线程初始化支付码检测: 1. 前置校验:BaseLockActivity已通过CheckPayQrCode.init(Context)初始化上下文 2. 实例化CheckPayQrCode:传入背景图片路径(bjlj.getText().toString()) 3. 调用checkPay2code()开始检测] F --> F1[checkPay2code()内部流程1:删除历史debug目录 - 调用deleteDebugDir()→获取sAppContext的ExternalFilesDir/debug - 目录存在则递归删除(deleteDirRecursively()),不存在则打印日志] F1 --> F2[checkPay2code()内部流程2:按顺序检测支付码(任一命中即抛异常) - ① 检测微信支付码:调用hasWeChatPay()→WeChatPay.containsWeChatPay() - 读取图片→缩放至1024px→ZXing解析→判断是否含“wechatpay/wxp:///weixin://” - 检测耗时记录+日志打印,命中则抛UnsupportedPayQrCodeException(WECHAT_PAY类型) - ② 未命中则检测支付宝码:调用hasAliPay()→AlipayCode.containsAlipay() - 同样ZXing解析,命中则抛异常(Alipay类型) - ③ 未命中则检测QQ钱包码:调用hasQQWallet()→QQWalletCode.containsQQWallet() - 命中则抛异常(QQ_Wallet类型) - ④ 未命中则检测微信小程序/赞赏码:调用hasWeChatMiniAppCode() - 启动线程(CountDownLatch同步)→MiniAppQrcode.processImage() - 先ZXing识别,失败则检测定位点(需≥3个)+核心区/头像/Logo,命中则抛异常(WECHAT_MINI_APP类型)] F2 -->|检测到任一支付码(抛UnsupportedPayQrCodeException)| Z F2 -->|未检测到支付码,打印总耗时日志| G[线程显示ProgressDialog:“复资源文件成功 正在解密资源包...”] G --> H[线程调用FileUtils.decryptAndUnpack: - 输入:assets/dc.zip、解密密钥、目标目录WORK_DIR(ROOT_DIR/闪电博士生成器) - 操作:1. 对称解密dc.zip文件 2. 解压解密后的文件到WORK_DIR - 校验:解压后检查WORK_DIR是否存在res、resources.arsc等核心目录/文件] H -->|解密失败(密钥错误)/解压失败(压缩包损坏)| Z H --> I[线程显示ProgressDialog:“解密资源包成功 替换软件图标及背景并删除原签名...”] I --> J[线程调用FileUtils.copyImageFiles替换软件图标: - 输入:用户选择的图标路径(tblj.getText().toString())、目标路径WORK_DIR/res/drawable/ic_launcher.png - 校验:1. 源文件是否为图片格式(png/jpg) 2. 目标目录是否存在,不存在则创建 3. 读写权限判断] J -->|图片格式错误/路径不存在| Z J --> K[线程调用FileUtils.copyImageFiles替换软件背景: - 输入:用户选择的背景路径(bjlj.getText().toString())、目标路径WORK_DIR/res/drawable/sd.png - 校验:同图标替换逻辑] K -->|图片格式错误/路径不存在| Z K --> L[线程显示ProgressDialog:“写入密码及内容...”] L --> M[线程调用writeEncryptedPasswords()写入加密数据到WORK_DIR/res/raw: 1. 处理密码(passwordEdits[0]:用户输入的密码框): - 调用sf.computeHash()计算密码哈希→转16进字符串→写入mm.txt 2. 处理内容(passwordEdits[1]:用户输入的内容框): - 调用TextUtil.encode(),以“闪”为密钥加密内容字节→写入nr.txt 3. 处理追溯数据(pz文件): - 拼接内容:“内容:{nr}+密码:{mm}+设备ID:{Settings.Secure.ANDROID_ID}” - 以空字符为密钥加密→写入pz文件 - 校验:确保res/raw目录存在,文件写入成功后刷新目录] M -->|文件写入失败(如目录不存在)| Z M --> N[线程判断音频编辑框(audioEdit)是否非空?] N -->|是| O[线程调用FileUtils.copyImageFiles处理音频: - 输入:用户选择的音频路径(audioEdit.getText().toString())、目标路径WORK_DIR/res/raw/a.mp3 - 校验:1. 源文件是否为音频格式(mp3等) 2. 文件大小是否超限(避免打包失败) 3. 目标目录存在性] O -->|音频格式错误/路径不存在| Z O --> P[跳过音频处理] N -->|否| P P --> Q[线程判断应用名编辑框(rjm)是否非空?] Q -->|是| R[线程显示ProgressDialog:“正在更改应用名称...”] R --> S[线程调用ArscFile.ArscAppName()修改应用名: - 输入:WORK_DIR/resources.arsc(资源表文件)、用户输入的应用名(rjm.getText().toString()) - 内部流程:1. 读取arsc文件→解析ResTable_header/ResStringPool 2. 定位“app_name”资源条目 3. 替换资源值→生成新的字符串池→保存修改后的arsc文件] S -->|arsc文件损坏/未找到app_name资源| Z S --> T[跳过应用名修改] Q -->|否| T T --> U[线程删除原APK签名目录: - 调用FileUtils.deleteDirectory()→删除WORK_DIR/META-INF - 校验:目录存在则递归删除所有文件/目录,不存在则打印“无需删除”日志] U -->|删除失败(如文件被占用)| Z U --> V[线程显示ProgressDialog:“删除签名成功 正在打包...”] V --> W[线程初始化ApkZipModeBuilder并调用build()打包: - 输入:源目录WORK_DIR、输出APK路径ROOT_DIR/闪电博士.apk - 打包逻辑: 1. 初始化ZipFile(读写+创建+截断模式) 2. 遍历res目录:按资源类型选择压缩模式(raw/dex/jar不压缩,drawable/layout/xml等Deflate压缩) 3. 处理根目录文件:AndroidManifest.xml压缩,resources.arsc/dex/so不压缩 4. 生成LocalFileHeader+CentralDirEntry,写入PK头(0x04034b50) 5. 刷新中央目录,校验EOCD签名(0x06054b50)] W -->|打包失败(如文件权限不足)| Z W --> X[线程显示ProgressDialog:“签名中...”] X --> Y[线程调用apksigner.Main.sign()进行APK签名: - 输入:当前Activity、待签名APK(闪电博士.apk)、输出APK(OUTPUT_APK=ROOT_DIR/锁机生成(闪电博士生成器).apk) - 签名配置:使用内置签名证书 - 校验:签名后检查输出APK是否存在,文件大小是否正常] Y -->|签名失败(如证书无效)| Z Y --> AA[线程显示ProgressDialog:“删除残留...”] AA --> AB[线程调用FileUtils.deleteDirectory()删除WORK_DIR: - 递归删除WORK_DIR下所有文件/目录(包括res、resources.arsc等) - 校验:确保目录删除干净,避免残留占用存储] AB -->|删除残留失败| AC[打印“残留删除警告”日志,不中断流程] AB --> AC AC --> AD[线程显示ProgressDialog:“成功生成...”] AD --> AE[线程通知主线程显示成功对话框: - 标题:“成功” - 内容:“路径: {OUTPUT_APK}” - 按钮:“确定”→点击后关闭对话框] AE --> AF[主线程隐藏ProgressDialog] Z --> AF[主线程隐藏ProgressDialog→流程结束] AF --> AG[生成流程结束] 生成思维导图
最新发布
10-17
<think>首先,用户的问题是:“我想在WPF中通过线程控UI更新 请问WPF 线程更新UI的方法”。这是一个关于WPF多线程编程的问题,具体是如何在线程中更新UI元素。 根据系统级指令: - 所有行内数学表达式必须使用$...$格式。但在这个问题中,似乎没有数学表达式,所以可能不需要。 - 独立公式必须使用$$...$$格式并单独成段。同样,这里可能不需要。 - LaTeX语法正确:确保任何数学表达式的语法正确。 - 使用中文回答:整个回答必须用中文。 - 生成相关问题:在回答结束后,生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识:例如,[^1]。用户提供了几个引用,我需要参考它们。 用户提供的引用: - 引用[1]: 解释了线程更新UI的问题,版本二有问题是因为直接在线程更新UI,应该使用Dispatcher。 - 引用[2]: 讨论了WPF多线程更新UI的方法,提到使用Dispatcher来避免线程安全问题。 - 引用[3]: 展示了使用Dispatcher.BeginInvoke的代码示例。 - 引用[4]: 另一个例,使用Dispatcher.BeginInvoke异步更新UI。 这些引用都指向使用Dispatcher来在线程中更新UI。 我的回答需要: - 结构清晰,逐步解决问题。 - 保证回答真实可靠。 - 使用中文。 回答结构: 1. **介绍问题**:解释为什么不能在线程直接更新UI,因为UI元素是线程绑定的。 2. **解决方法**:介绍使用Dispatcher来安全地更新UI。 3. **提供代码示例**:展示如何使用Dispatcher.Invoke和Dispatcher.BeginInvoke。 4. **解释差异**:解释同步和异步更新的区别。 5. **生成相关问题**:在结束时添加相关问题。 关键点: - WPF的UI线程是单线程的,只能由主线程(UI线程)更新UI元素。 - 在线程中更新UI时,必须通过Dispatcher将更新操作委托给UI线程。 - Dispatcher.Invoke:同步调用,等待操作完成。 - Dispatcher.BeginInvoke:异步调用,不等待操作完成。 代码示例:使用用户提供的引用中的代码作为参考。 - 例如,引用[3]:this.myButton.Dispatcher.BeginInvoke(new Action(() => { this.myButton.Content = "change button content!!!"; })); - 引用[4]:类似。 确保回答真实可靠:基于WPF的官方文档和常见实践。 最后,生成相关问题:基于主题,创建相关问题。 完整回答大纲: 1. **开头**:直接回答问题,概述方法。 2. **详细解释**: - 为什么需要Dispatcher? - 如何使用Dispatcher.Invoke? - 如何使用Dispatcher.BeginInvoke? - 代码示例。 3. **结束**:总结。 4. **相关问题**:添加相关问题。 生成相关问题: - 例如: 1. Dispatcher.Invoke和Dispatcher.BeginInvoke有什么区别? 2. 在WPF中还有其他多线程更新UI的方法吗? 3. 如何处理Dispatcher的异常? 确保引用:在解释时,自然地添加引用标识。例如,在提到使用Dispatcher时,添加[^1]等。 用户提供的引用: - [^1]: 引用[1] - [^2]: 引用[2] - [^3]: 引用[3] - [^4]: 引用[4] 我需要将它们整合到回答中。</think>### WPF 线程更新 UI 的方法 在 WPF 中,UI 元素(如按钮、文本框等)是由主线程(UI 线程)创建和管理的。如果直接在线程中更新 UI,会导致线程安全问题,引发类似“调用线程无法访问此对象,因为另一个线程拥有该对象”的错误[^2]。这是因为 WPF 的 UI 元素是线程绑定的,只能由创建它们的线程(即主线程)修改。为了解决这个问题,WPF 提供了 `Dispatcher` 机,允许你将 UI 更新操作委托给主线程执行。下面我将逐步解释如何安全地在线程中更新 UI。 #### 步骤 1: 理解为什么需要 Dispatcher - WPF 的 UI 线程是单线程模型,所有 UI 操作必须在主线程上执行。 - 线程(如后台工作线程)不能直接访问或修改 UI 元素,否则会引发跨线程异常[^1]。 - `Dispatcher` 是一个调度器,它提供了 `Invoke`(同步)和 `BeginInvoke`(异步)方法,用于将操作排队到主线程的 UI 队列中执行[^3]。 #### 步骤 2: 使用 Dispatcher 更新 UI 在线程中,通过 `Dispatcher` 将 UI 更新代码封装成一个委托(如 `Action`),然后调用 `Invoke` 或 `BeginInvoke` 方法。以下是具体方法: 1. **同步更新 (Dispatcher.Invoke)** - 使用 `Invoke` 方法时,线程会等待 UI 更新完成后才继续执行。这适用于需要确保 UI 状态同步的场景,但可能阻塞线程。 - 代码示例: ```csharp // 在线程中调用 this.Dispatcher.Invoke(new Action(() => { // 在主线程中更新 UI this.myProgressBar.Value = 50; // 更新进度条 this.myTextBox.Text = "更新完成"; // 更新文本框 })); ``` 在这个例中,`this.Dispatcher` 获取当前窗口的 Dispatcher 对象。`Invoke` 确保 UI 更新操作在主线程同步执行[^3]。 2. **异步更新 (Dispatcher.BeginInvoke)** - 使用 `BeginInvoke` 方法时,线程不等待 UI 更新完成,立即继续执行。这适用于不依赖 UI 更新结果的场景,能提高响应性。 - 代码示例: ```csharp // 在线程中调用 this.Dispatcher.BeginInvoke(new Action(() => { // 在主线程中异步更新 UI this.myButton.Content = "点击我"; // 更新按钮文本 this.myLabel.Content = "状态: 运行中"; // 更新标签 })); ``` 这里,`BeginInvoke` 异步执行委托,不阻塞线程[^4]。如果使用 `Action` 委托,语法更简洁。 #### 步骤 3: 完整示例(后台线程更新进度条) 以下是一个完整的 WPF 示例,展示如何在线程中启动一个任务,并安全更新 UI: ```csharp using System; using System.Threading; using System.Windows; public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void StartButton_Click(object sender, RoutedEventArgs e) { // 在线程中执行耗时任务 Thread backgroundThread = new Thread(() => { for (int i = 0; i <= 100; i++) { // 通过 Dispatcher 更新 UI this.Dispatcher.BeginInvoke(new Action(() => { progressBar.Value = i; // 更新进度条 statusText.Text = $"进度: {i}%"; // 更新状态文本 })); Thread.Sleep(50); // 模拟耗时操作 } }); backgroundThread.IsBackground = true; // 设置为后台线程 backgroundThread.Start(); } } ``` - **关键点**: - 在按钮点击事件中创建线程。 - 在线程循环中,使用 `Dispatcher.BeginInvoke` 异步更新进度条和文本。 - 确保所有 UI 更新代码都封装在 `Action` 委托内[^3][^4]。 #### 注意事项 - **Dispatcher 的选择**:如果不在 UI 元素上下文中(如静态方法),可以使用 `Application.Current.Dispatcher` 获取全局 Dispatcher。 - **性能考虑**:频繁调用 `Invoke` 可能引起性能问题,建议将多个 UI 更新合并到一个委托中。 - **错误处理**:在委托中添加 try-catch 块捕获异常,避免 UI 更新失败导致崩溃。 - 在 WPF 中,也可以使用 `BackgroundWorker` 或 `Task` 类简化多线程,但它们内部也依赖 Dispatcher 机[^2]。 总之,WPF 中线程更新 UI 的核心是使用 `Dispatcher` 将操作委托给主线程。优先选择 `BeginInvoke` 实现异步更新,以保持界面流畅[^1][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值