Xamarin.Android:定时刷新ListView中ImageView,无需重启Activity
解决方案:定时刷新ListView状态(无需重启Activity)
你的问题核心在于只在ListView初始化时获取一次状态,而且同步网络请求还会阻塞UI。下面是完整的修改方案,兼顾性能和实时性:
1. 先重构网络请求为异步(避免UI卡顿)
原来的Get_Status是同步方法,会卡住主线程,改成异步版本,用HttpClient替代HttpWebRequest(更适合现代异步编程):
class ListAdapter : ArrayAdapter<Geraet> { public List<Geraet> List { get; private set; } private string Serverip { get; } public ListAdapter(Context context, int listId, List<Geraet> list, string serverip) : base(context, listId, list) { List = list; Serverip = serverip; } // 改为公开的异步方法,方便外部调用 public async Task<int> Get_StatusAsync(Geraet geraet) { try { using (var httpClient = new HttpClient()) { var url = string.Format(Constants.StatusPath, Serverip, Constants.WebservicePort, geraet.Ip); var content = await httpClient.GetStringAsync(url); var result = System.Text.RegularExpressions.Regex.Replace(content, @"[^0-9]", ""); return string.IsNullOrEmpty(result) ? -2 : int.Parse(result); } } catch (Exception ex) { // 捕获网络异常,返回错误标记 Console.WriteLine($"获取设备状态失败: {ex.Message}"); return -1; } } // ... 其他代码 }
2. 给Geraet类添加状态属性(缓存状态,避免重复请求)
给你的Geraet实体类加一个Status字段,用来保存最新的状态值,这样不用每次刷新UI都去发网络请求:
public class Geraet { // 你原来的属性,比如Ip public string Ip { get; set; } // 新增状态属性,用来缓存最新状态 public int Status { get; set; } = -2; // 默认初始状态 }
3. 修改ListAdapter的GetView方法(复用View+读取缓存状态)
现在GetView直接读取Geraet的缓存状态,不用再发起网络请求,提升性能:
public override View GetView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { LayoutInflater inflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService); v = inflater.Inflate(Resource.Layout.List, parent, false); } var geraet = List[position]; var statusImageView = v.FindViewById<ImageView>(Resource.Id.ImageView); // 根据缓存的状态更新图片 if (geraet.Status == 1) { statusImageView.SetImageResource(Resource.Drawable.green); } else { statusImageView.SetImageResource(Resource.Drawable.red); } return v; }
4. 在Activity中添加定时任务(定期刷新状态)
用Xamarin的Device.StartTimer来实现定时刷新,它会自动在主线程回调,方便更新UI:
public class YourActivity : Activity { private ListAdapter _listAdapter; private CancellationTokenSource _refreshCts; // 用来取消定时任务 protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Activity); var lv = FindViewById<ListView>(Resource.Id.ListView); _listAdapter = new ListAdapter(this, Resource.Layout.List, list.CurrentList, Intent.GetStringExtra("ServerIP")); lv.Adapter = _listAdapter; // 启动定时刷新,比如每5秒一次(可根据需求调整) StartStatusRefresh(TimeSpan.FromSeconds(5)); } private void StartStatusRefresh(TimeSpan interval) { _refreshCts = new CancellationTokenSource(); var token = _refreshCts.Token; Device.StartTimer(interval, () => { // 如果任务被取消,停止定时 if (token.IsCancellationRequested) return false; // 异步刷新所有设备状态 _ = RefreshAllDeviceStatusesAsync(); return true; // 返回true继续定时,返回false停止 }); } private async Task RefreshAllDeviceStatusesAsync() { try { // 遍历所有设备,更新状态 foreach (var geraet in _listAdapter.List) { geraet.Status = await _listAdapter.Get_StatusAsync(geraet); } // 必须在主线程通知适配器刷新UI RunOnUiThread(() => { _listAdapter.NotifyDataSetChanged(); }); } catch (Exception ex) { Console.WriteLine($"批量刷新状态失败: {ex.Message}"); } } // 页面销毁时取消定时任务,防止内存泄漏 protected override void OnDestroy() { base.OnDestroy(); _refreshCts?.Cancel(); _refreshCts?.Dispose(); } }
关键注意点
- 异步网络请求:必须用
async/await,否则会阻塞主线程导致UI卡顿甚至ANR。 - 定时任务清理:在
OnDestroy中取消并释放CancellationTokenSource,避免Activity销毁后任务还在运行,造成内存泄漏。 - 状态缓存:把状态存在
Geraet对象里,避免GetView每次都发起网络请求,提升ListView的滚动流畅度。 - 主线程更新UI:
NotifyDataSetChanged必须在主线程调用,这里用RunOnUiThread保证线程安全。
内容的提问来源于stack exchange,提问作者Sam




