Xamarin Android:向ViewFlipper添加新的ImageView时抛出异常

时间:2014-08-03 21:39:37

标签: android xamarin viewflipper

这是我的第一个Xamarin项目。我正在尝试为Android创建一个简单的幻灯片应用。

这是托管ViewFlipper的Main.axml代码。

    <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    <ViewFlipper
        android:id="@+id/view_flipper"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
    </ViewFlipper>
</RelativeLayout>

这是活动代码。

namespace RS
{
    [assembly: UsesPermission(Android.Manifest.Permission.Internet)]
    [Activity (Label = "RS", MainLauncher = true)]
    public class MainActivity : Activity
    {
        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            // Set our view from the "main" layout resource
            SetContentView (Resource.Layout.Main);

            StartSlideshow (this);
        }

        private Bitmap GetImageBitmapFromUrl(string url)
        {
            Bitmap imageBitmap = null;

            url = url.Replace("\"", "");

            using (var webClient = new WebClient())
            {
                var imageBytes = webClient.DownloadData(url);
                if (imageBytes != null && imageBytes.Length > 0)
                {
                    imageBitmap = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
                }
            }

            return imageBitmap;
        }

        private void StartSlideshow(Context ctx) 
        {   
            string[] slides = null;
            string url = "http://jsonplaceholder.typicode.com/photos";
            var httpReq = (HttpWebRequest)HttpWebRequest.Create (new Uri (url));

            httpReq.BeginGetResponse ((ar) => {

                //Fetch the slides
                var request = (HttpWebRequest)ar.AsyncState;
                using (var response = (HttpWebResponse)request.EndGetResponse (ar))     {                           
                    var s = response.GetResponseStream ();
                    var j = (JsonArray)JsonArray.Load (s);

                    //Prepare the slides
                    slides = (from result in j
                        select result ["url"].ToString ()).ToArray ();

                    //Display the slides
                    ViewFlipper mViewFlipper = FindViewById<ViewFlipper>(Resource.Id.view_flipper);

                    for (int i = 0; i < 10; i++) {
                        ImageView image = new ImageView(ctx);
                        image.SetImageBitmap(GetImageBitmapFromUrl(slides[i]));
                        try {
                            mViewFlipper.AddView(image);                            
                        } catch (Exception ex) {
                            Log.Error(ex.ToString(), "Couldn't add image to flipper");
                        }
                    }

                    mViewFlipper.AutoStart = true;
                    mViewFlipper.SetFlipInterval (2000);
                    mViewFlipper.StartFlipping ();
                }
            } , httpReq);

        }
    }
}

当我尝试将新的ImageView添加到ViewFlipper时(在我从Web服务调用中获取要显示的图像列表之后),我收到以下错误。

ex  {Android.Util.AndroidRuntimeException: Exception of type 'Android.Util.AndroidRuntimeException' was thrown.
  at Android.Runtime.JNIEnv.CallVoidMethod (IntPtr jobject, IntPtr jmethod, Android.Runtime.JValue[] parms) [0x00063] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.12-series/163212a9/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs:507 
  at Android.Views.ViewGroup.AddView (Android.Views.View child) [0x00043] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.12-series/163212a9/source/monodroid/src/Mono.Android/platforms/android-19/src/generated/Android.Views.ViewGroup.cs:1761 
  at RS.MainActivity+<>c__DisplayClass2.<StartSlideshow>b__0 (IAsyncResult ar) [0x000a0] in c:\Users\girish\Dropbox\Work\Rejini\android\RejiniSlider\RS\RS\MainActivity.cs:84 
  --- End of managed exception stack trace ---
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
    at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4039)
    at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:709)
    at android.view.View.requestLayout(View.java:12675)
    at android.view.View.requestLayout(View.java:12675)
    at android.view.View.requestLayout(View.java:12675)
    at android.view.View.requestLayout(View.java:12675)
    at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:268)
    at android.view.View.requestLayout(View.java:12675)
    at android.view.ViewGroup.addView(ViewGroup.java:3206)
    at android.widget.ViewAnimator.addView(ViewAnimator.java:182)
    at android.view.ViewGroup.addView(ViewGroup.java:3165)
    at android.view.ViewGroup.addView(ViewGroup.java:3145)
    at dalvik.system.NativeStart.run(Native Method)
}   Android.Util.AndroidRuntimeException

两个问题:

  1. 这是在Android中构建动态幻灯片的错误方法吗?

  2. 如何解决上面显示的错误?

  3. 感谢您的帮助。

    更新 我根据tomjen和Simon的答案更新了代码以使用async。这是更新后的方法:

    private Task<Bitmap> GetImageBitmapFromUrl(string url)
        {
            Task<Bitmap> imageBitmap = null;
    
            url = url.Replace("\"", "");
    
            RunOnUiThread ( () => {
                using (var webClient = new WebClient())
                {
                    var imageBytes = webClient.DownloadDataTaskAsync(url);
                    if (imageBytes != null) // && imageBytes.Result.Length > 0
                    {
                        try {
                            imageBitmap = BitmapFactory.DecodeByteArrayAsync(imageBytes.Result, 0, imageBytes.Result.Length);
                        } catch (Exception ex) {
                            Log.Error(ex.ToString(), "Couldn't download image");
                        }
                    }
                }
            });
    
            return imageBitmap;
        }
    

    不幸的是,我仍然得到同样的错误。 :(

2 个答案:

答案 0 :(得分:1)

不要使用BeginGet / EndGet模式。它只是出于历史原因而存在。使用(更容易)async / await模式 - 这也确保其余部分发生在UI线程上。

您需要的方法是WebClient类(http://msdn.microsoft.com/en-us/library/system.net.webclient.downloaddatataskasync(v=vs.110).aspx)中的DownloadDataAsyncTask。

更改

private Bitmap GetImageBitmapFromUrl(string url)
    {
        Bitmap imageBitmap = null;

        url = url.Replace("\"", "");

        using (var webClient = new WebClient())
        {
            var imageBytes = webClient.DownloadData(url);
            if (imageBytes != null && imageBytes.Length > 0)
            {
                imageBitmap = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
            }
        }

        return imageBitmap;
    }

类似

private async Bitmap GetImageBitmapFromUrl(string url)
    {
        Bitmap imageBitmap = null;

        url = url.Replace("\"", "");

        using (var webClient = new WebClient())
        {
            var imageBytes = await webClient.DownloadDataAsyncTask(url);
            if (imageBytes != null && imageBytes.Length > 0)
            {
                imageBitmap = await BitmapFactory.DecodeByteArrayAsync(imageBytes, 0, imageBytes.Length);
            }
        }

        return imageBitmap;
    }

这样做的结果是除了IO之外,方法中的所有内容都在主线程上运行,并且该代码只是等待IO完成。

你也可以(并且应该)为其他方法做类似的事情。您可以使用WebClient对象上的DownloadStringAsyncTask方法执行此操作(http://msdn.microsoft.com/en-us/library/system.net.webclient.downloadstringtaskasync(v=vs.110).aspx)。

答案 1 :(得分:0)

您无法从后台线程更新UI组件。只有主线程(也就是UI线程)可以做到这一点:

  

只有创建视图层次结构的原始线程才能触及其视图。

您需要在后台线程中执行网络请求,然后通知主线程更新UI。您应该查看AsyncTaskHandler