关闭

[置顶] xamarin android异步更新UI线程

标签: xamarinandroid线程
7958人阅读 评论(2) 收藏 举报
分类:

UI线程简单了解

一些从事web开发的同学,可能对UI线程没有这个概念,没办法,毕竟“UI线程”这个概念只存在一些客户端(window客户端软件、app等)。下面就来简单地了解一下一下这几个概念,理解起来比用起来更简单。其实android在子线程中更新UI线程,一个方法就欧了,RunOnUiThread(System.Action action),这篇写给小白的,如果知道怎么使用这个方法,就没必要看下面的了。
主线程也叫UI线程
当一个程序启动的时候,系统自动创建一个主线程,在这个主线程中,你的应用(app、winform等客户端程序)和UI组件发生交互,负责处理UI组件的各种事件,所以主线程也叫UI线程。
UI组件的更新一定要在UI线程里
android为了线程安全,不允许在UI线程外的子线程操作UI,这个结论不仅仅是说android,这个概念同样适用于其他的客户端系统,它 的好处时提高客户端UI的用户体验和执行效率(稍后解释),防止线程阻塞。在java 原生的android中有两种方式更新UI线程
- handler消息传递机制更新UI线程,http://www.runoob.com/w3cnote/android-tutorial-handler-message.html
- AsyncTask异步任务更新UI线程http://www.runoob.com/w3cnote/android-tutorial-ansynctask.html
AsyncTask是Android提供的一个轻量级的用于处理异步任务的类,只能说有点类似于C#中的Task
android程序中使用异步避免ANR异常
刚刚说了UI组件的更新一定要在UI线程中,当我们在主线程中发起请求>请求的执行(http请求耗时)>请求完成填充数据,更新UI组件。这一过程一旦超过了10秒钟就会抛出ANR异常(Application Not Responding)应用程序员不响应,所以网络请求耗时的操作大多使用异步操作,早起异步Task相对麻烦,在.net 4.5中增加了新的特性await/async,使用await/async 就简化了很多。原则上的要求就是永远不要阻塞UI线程。

我们通过下面几个简单的示例逐步地学西和掌握如何在子线程中更新UI线程
1. 模拟ANR异常
2. 使用Timer对象开启子线程,在子线程使用RunOnUIThread更新UI线程
3. 异步加载图片,在子线程中更新UI线程

阻塞UI线程并输入事件-模拟ANR异常

下面我们创建一个简单的登录程序,登录的时候使用Thread.Sleep(10000模拟耗时10秒钟,在这10秒钟内程序没有任何响应(如果你做了输入事件比如:触摸屏幕,按返回键),通俗说法就是卡界面了。代码没难度,主要是了解ANR异常

    [Activity(Label = "LoginActivity", MainLauncher = true)]
    public class LoginActivity : Activity
    {
        private EditText et_name;
        private EditText et_pwd;
        private Button btn;
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            SetContentView(Resource.Layout.Login);
            et_name = FindViewById<EditText>(Resource.Id.et_name);
            et_pwd = FindViewById<EditText>(Resource.Id.et_pwd);
            btn = FindViewById<Button>(Resource.Id.btn);
            btn.Click += (s, e) =>
            {
                Client.Client_Login(et_name.Text, et_pwd.Text, () =>
                {
                    Toast.MakeText(this, "登录成功", ToastLength.Long).Show();
                }, error =>
                {
                    Toast.MakeText(this, error, ToastLength.Long).Show();
                });
            };
         }
    }
    public class Client
    {
        /// <param name="successAction">登录成功的回调</param>
        /// <param name="errorAction">登录失败的回调</param>
        public static void Client_Login(string name, string pwd,Action successAction,Action<string> errorAction)
        {
            Thread.Sleep(6000);
            if (name == "123" && pwd == "123")
            {
                successAction();
                return;
            }
            errorAction("密码不正确");
        }
    }

ANR异常
这是值得注意的一点,记得上学的时候用的是酷派,虽然是充话费送的,但其实那手机配置还是可以,但是用就久了,很卡,打开一些app,长时间没有反应,我便在屏幕上点、按返回键等,于是就经常报这个“**程序没有响应”“等待”“退出”的一个对话框,好吧,其实用酷派手机卡了这些无关紧要。重点还是要说说ANR异常是如何产生的吧

ANR:Application Not Responding的缩写,当程序爆出“应用程序无响应”,系统会向用户显示一个对话框,“等待”可以让程序继续运行,“强制关闭”直接kill掉了。
在android程序中,程序的响应时由Activity manager和WindowManager系统服务监听的,主要是由以下两种情况造成的,

  1. 在5秒外没有响应UI事件(点击屏幕,点击按钮,按返回键等),反之在5秒内比如Thread.sleep(5000)去点击屏幕,按返回键也不会报出ANR异常的。
  2. BroadcaseReceiver在10秒内没有执行完毕

产生上面两种情况原因比较多,要注意的是即时是在UI线程中做了耗时的事情(5秒以上),如果用户没有触发屏幕的任何的事件,这时虽然UI线程阻塞了,也不会产生ANR。其实避免ANR异常原则要求还是那句话”不要再UI线程上做耗时的事情

使用Timer对象开启子线程,在子线程使用RunOnUIThread更新UI线程

大家一定使用过Timer,可能有些人还不知道Timer对象会开启多个线程,但最少不止一个。下面这个例子,将演示两个timer,1秒钟更新一次,对比一下两个TextView的显示的时间。
在.net 中timer主要有三种1.System.Threading.Timer 、2.System.Timers.Timer、3.System.Windows.Forms.Timer 前两者会创建一个新的线程(这个会在下面的代码证明),第三种的Timer实在主线程中创建的,也就是在UI线程中的。

    [Activity(Label = "Xamarin_android", MainLauncher = true, Icon = "@drawable/icon")]
    public class TimerActivity : Activity
    {
        private TextView tv_test;
        private TextView tv_test1;
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.Main);
            tv_test = FindViewById<TextView>(Resource.Id.tv_test);
            tv_test1 = FindViewById<TextView>(Resource.Id.tv_test1);
            tv_test.Text = "现在的时间是" + DateTime.Now.ToString("yyyy年MM月dd日 HH:mm:ss");
            tv_test1.Text = "现在的时间是" + DateTime.Now.ToString("yyyy年MM月dd日 HH:mm:ss");

            System.Diagnostics.Debug.Write("主线程"+Thread.CurrentThread.ManagedThreadId);
            System.Timers.Timer timer = new System.Timers.Timer(10000);
            timer.Elapsed += delegate
            {
                System.Diagnostics.Debug.Write("timer线程"+Thread.CurrentThread.ManagedThreadId);
                RunOnUiThread(()=> {
                    tv_test.Text = "现在的时间是" + DateTime.Now.ToString("yyyy年MM月dd日 HH:mm:ss");
                });
            };
            timer.Enabled = true;

            System.Diagnostics.Debug.Write("主线程" + Thread.CurrentThread.ManagedThreadId);
            System.Timers.Timer timer1 = new System.Timers.Timer(10000);
            timer1.Elapsed += delegate
            {
                System.Diagnostics.Debug.Write("timer1线程" + Thread.CurrentThread.ManagedThreadId);
                tv_test1.Text = "现在的时间是" + DateTime.Now.ToString("yyyy年MM月dd日 HH:mm:ss");
            };
            timer1.Enabled = true;
        }
    }

通过这段代码说明两个问题:1.timer会开启至少一个线程,2.tv_test的时间是1秒更新一次,tv_test1的时间不会更新,在子线程中无法直接更新UI。
xamarin android中子线程更新UI线程的方法就是RunOnUIThread,该方法参数是一个无参无返回值的委托。

异步加载图片,在子线程中更新UI线程

我们已经知道子线程中更新UI的使用方法是RunOnUIThread ,下面这个例子使用异步加载图片,异步的重点是开启子线程。
关于http请求的库,microsoft封装的库在命名空间System.Net.Http,这里演示的是第三方的http请求库RestSharp,你可以在nuget上添加引用。

    [Activity(Label = "Xamarin_android", MainLauncher = true, Icon = "@drawable/icon")]
    public class AsyncLoadImageActivity : Activity
    {
        private ImageView  img;
        private Button btn;
        private TextView tv_result;
        private Bitmap bitmap;
        private Button btn_test;
        private int noBlock_number;
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.AsyncLoadImage);
            btn = FindViewById<Button>(Resource.Id.btn);
            img = FindViewById<ImageView>(Resource.Id.img);
            tv_result = FindViewById<TextView>(Resource.Id.tv_result);
            btn_test = FindViewById<Button>(Resource.Id.btn_noBlockTest);
            btn_test.Click += (s, e) =>
            {
                noBlock_number++;
                btn_test.Text += "点击了" + noBlock_number+"次";
            };
            System.Diagnostics.Debug.Write("UI线程ID:"+Thread.CurrentThread.ManagedThreadId);
            const string url = "https://gss0.bdstatic.com/-4o3dSag_xI4khGkpoWK1HF6hhy/baike/w%3D268%3Bg%3D0/sign=0003b03088b1cb133e693b15e56f3173/0bd162d9f2d3572c257447038f13632763d0c35f.jpg";
            btn.Click += (s, e) =>
            {
                System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
                sw.Start();
                GetStreamAsync(url,()=>RunOnUiThread(()=> {
                    System.Diagnostics.Debug.Write("异步线程ID:" + Thread.CurrentThread.ManagedThreadId);
                    sw.Stop();
                    double seconds = sw.Elapsed.TotalSeconds;
                    tv_result.Text = "加载图片使用了" + seconds + "秒";
                    img.SetImageBitmap(bitmap);
                }),
                error =>RunOnUiThread(()=> {
                    tv_result.Text = error;
                }));
            };
        }
        /// <param name="successAction">获取图片成功回调方法</param>
        /// <param name="errorAction">获取失败回调方法</param>
        public  void  GetStreamAsync(string  url,Action successAction,Action<string> errorAction)
        {
            try
            {
                RestClient client = new RestClient(url);
                RestRequest request = new RestRequest();
                var result = client.GetAsync(request,(response,handler)=> {
                if (response.StatusCode == 0)
                {
                    errorAction("网络状况差,请稍后再试");
                    return;
                }
                if(response.StatusCode == System.Net.HttpStatusCode.OK)
                {
                        var bytes = response.RawBytes;
                        MemoryStream stream = new MemoryStream(bytes);
                        bitmap = BitmapFactory.DecodeStream(stream);
                        successAction();
                 }
              });
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.Write(ex.StackTrace);
                errorAction(ex.ToString());
            }
        }
    }

从运行的结果我们可以看到,UI线程ID和异步方法中的回调方法子线程ID不一样,使用异步方法不会阻塞UI线程,执行耗时请求图片方法时,任然可以点击按钮,输入其他的事件。

原文标题:xamarin android异步更新UI线程
作者:张林
原文链接:http://blog.csdn.net/kebi007/article/details/77645640

2
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

Xamarin之Mac上的安装

在Mac上使用Xamarin可以做Android和iOS的开发,而且iOS的打包也是在Mac上或者是说在安装苹果系统上进行,Mac上开发使用Xamarin Studio。 而对于没有Mac或者没有安装...
  • cdf881125
  • cdf881125
  • 2015-10-09 11:25
  • 6860

Xamarin Android安装教程(2016最新亲测安装版)

vs2015安装xamarin android开发环境主要流程安装好Java的开发环境,也就是装好JDK 安装Xamarin For Windows,通俗点讲就是装Xamarin 下载好Android...
  • kebi007
  • kebi007
  • 2016-10-14 01:41
  • 5177

xamarin 安卓开发通用Adapter

我们在安卓开发过程中,ListView进行数据绑定要通过Adapter,一般的做法是我们要新建一个ListViewActivity,再对应新建一个Adabper继承自BaseAdapter,并实现其中...
  • sqh001
  • sqh001
  • 2015-04-18 11:36
  • 1181

Xamarin.Android 非UI线程更新UI

  • 2015-02-09 11:24
  • 14KB
  • 下载

Android:在子线程中更新UI,解析异步消息处理机制(Handler)

这篇文章是通过阅读“第一行代码”这个书,学习总结的,整理出自己的想法和借鉴作者的思想来充分理解:Android中的异步消息处理机制。 众所周知,Android的UI也是线程不安全的。也就是说,如果想要...
  • btt2013
  • btt2013
  • 2015-12-01 12:35
  • 1099

Android异步机制更新UI线程(详解)

详细介绍Android中各种异步更新UI线程的机制
  • jiaohanhan
  • jiaohanhan
  • 2017-05-11 18:47
  • 289

Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面

http://blog.csdn.net/mylzc/article/details/6736988 本博文地址:http://blog.csdn.net/mylzc/article/detai...
  • Skypine_Lee
  • Skypine_Lee
  • 2012-05-18 14:04
  • 354

Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面

本博文地址:http://blog.csdn.net/mylzc/article/details/6736988 转载请注明出处 Android异步处理系列文章索引 Andro...
  • ricefcc
  • ricefcc
  • 2015-02-10 20:43
  • 333

Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面

转自:http://blog.csdn.net/mylzc/article/details/6736988 概述:每个Android应用程序都运行在一个dalvik虚拟机进程中,进程开始...
  • liuhanhan512
  • liuhanhan512
  • 2011-09-19 11:23
  • 453

Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面

Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面 [日期:2011-09-17] 来源:csdn  作者:mylzc ...
  • zhangxu365
  • zhangxu365
  • 2011-12-31 17:28
  • 577
    个人资料
    • 访问:502879次
    • 积分:4111
    • 等级:
    • 排名:第8701名
    • 原创:78篇
    • 转载:1篇
    • 译文:0篇
    • 评论:136条
    不忘初心,方能始终
    博客专栏
    文章分类
    最新评论