这是我的第一个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
两个问题:
这是在Android中构建动态幻灯片的错误方法吗?
如何解决上面显示的错误?
感谢您的帮助。
更新 我根据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;
}
不幸的是,我仍然得到同样的错误。 :(
答案 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。您应该查看AsyncTask
或Handler
。