Только исходный поток, создавший иерархию представлений, может касаться своего исключения представлений при использовании потока в методе OnCreate Xamarin. Android - PullRequest
0 голосов
/ 27 марта 2020

У меня есть следующий экран:

enter image description here

Когда я нажимаю Ponentes ImageButton, через несколько секунд открывается SpeakersActivity. После нажатия на Ponentes ImageButton я не хочу оставаться на его экране в течение нескольких секунд, но я хочу, чтобы SpeakersActivity немедленно открывался с loadingSpinner ProgressBar, пока загружаются данные в SpeakersActivity. Я поставил код, который вызывает задержку в потоке в методе OnCreate SpeakersActiviy:

protected override async void OnCreate(Bundle savedInstanceState)
{
    .....
    .....

    loadingSpinner.Visibility = ViewStates.Visible;
    await Task.Run(() =>
    {
        //get all the speakers from the db
        allSpeakers = DatabaseHelper.GetAllFromTable<Speaker>("speakers.db");

        //get only the international spakers
        internationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("international")).ToList();

        //get only the national speakers
        nationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("national")).ToList();

        //fill in the RecyclerView with data
        speakersRecyclerView = FindViewById<RecyclerView>(Resource.Id.speakersRecyclerView);
        speakersLayoutManager = new LinearLayoutManager(this);
        speakersRecyclerView.SetLayoutManager(speakersLayoutManager);
        speakersAdapter = new SpeakersAdapter(speakers);
        speakersAdapter.ItemClick += OnItemClick;
        speakersRecyclerView.SetAdapter(speakersAdapter);

        loadingSpinner.Visibility = ViewStates.Gone;
    });
    .....
    .....
}

Я получаю Only the original thread that created a view hierarchy can touch its views exception. В чем причина этого исключения и как я могу добиться того, чего хочу? Если бы у меня было единственное командное решение:

await Task.Run(() => {.....}); 

, было бы лучше, поэтому, если другой человек прочитает мой код в будущем, он не слишком задумывается о том, что происходит.

Вот SpeakersActivity.axml:

<LinearLayout>
    <Toolbar>
        <TextView/>
    </Toolbar>
    <LinearLayout>
        <TextView/>
        <TextView/>
    </LinearLayout>
    <android.support.v7.widget.RecyclerView
        android:id = "@+id/speakersRecyclerView"/>
    <ProgressBar
        android:id = "@+id/loadingSpinner"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:indeterminateDrawable="@drawable/animdraw"
        android:visibility="gone"
        android:layout_gravity="center"/>
</LinearLayout>

Я пропустил атрибуты View, потому что они не имеют значения.

Ответы [ 2 ]

0 голосов
/ 30 марта 2020

Работает, единственная проблема в том, что loadingSpinner ProgressBar вращается в течение половины времени загрузки. Например, если для загрузки SpeakersActivity после нажатия Ponentes ImageButton требуется 4 секунды, loadingSpinner вращается в течение 1 или 2 секунд, а затем остается неподвижным, пока не отобразятся данные SpeakersActivity, например:

enter image description here

Вот весь SpeakersActivity код:

[Activity(Label = "SpeakersActivity", ScreenOrientation = ScreenOrientation.Portrait)]
public class SpeakersActivity : BaseActivity
{
    TextView internationalSpeakersTextView;

    TextView nationalSpeakersTextView;

    RecyclerView speakersRecyclerView;

    // Layout manager that lays out each card in the RecyclerView:
    RecyclerView.LayoutManager speakersLayoutManager;

    // Adapter that accesses the data set (speakers):
    SpeakersAdapter speakersAdapter;

    /// <summary>
    /// List that contains all the speakers
    /// </summary>
    List<Speaker> allSpeakers;

    /// <summary>
    /// List that contains the international speakers
    /// </summary>
    List<Speaker> internationalSpeakers;

    /// <summary>
    /// List that contains the national speakers
    /// </summary>
    List<Speaker> nationalSpeakers;

    ProgressBar loadingSpinner;

    protected override async void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        SetContentView(Resource.Layout.SpeakersActivity);

        Android.Widget.Toolbar toolbar = FindViewById<Android.Widget.Toolbar>(Resource.Id.toolbar);
        toolbar.NavigationOnClick += delegate
        {
            this.OnBackPressed();
        };

        loadingSpinner = FindViewById<ProgressBar>(Resource.Id.loadingSpinner);
        loadingSpinner.Visibility = ViewStates.Visible;
        await Task.Run(() =>
        {
            //get all the speakers from the db
            allSpeakers = DatabaseHelper.GetAllFromTable<Speaker>("speakers.db");

            //get only the international spakers
            internationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("international")).ToList();

            //get only the national speakers
            nationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("national")).ToList();

            RunOnUiThread(() =>
            {
                speakersRecyclerView = FindViewById<RecyclerView>(Resource.Id.speakersRecyclerView);
                speakersLayoutManager = new LinearLayoutManager(this);
                speakersRecyclerView.SetLayoutManager(speakersLayoutManager);
                LoadSpeakers(internationalSpeakers);

                loadingSpinner.Visibility = ViewStates.Gone;
            });
        });

        internationalSpeakersTextView = FindViewById<TextView>(Resource.Id.internationalSpeakersTextView);
        internationalSpeakersTextView.Click += delegate
        {
            //change TextViews's style when selected/not-selected
            internationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_selected);
            nationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_unselected);

            LoadSpeakers(internationalSpeakers);
        };

        nationalSpeakersTextView = FindViewById<TextView>(Resource.Id.nationalSpeakersTextView);
        nationalSpeakersTextView.Click += delegate
        {
            //change TextViews's style when selected/not-selected
            nationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_selected);
            internationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_unselected);

            LoadSpeakers(nationalSpeakers);
        };
    }

    /// <summary>
    /// Load speakers inside activity
    /// </summary>
    /// <param name="speakers">speakers</param>
    private void LoadSpeakers(List<Speaker> speakers)
    {
        speakersAdapter = new SpeakersAdapter(speakers);
        speakersAdapter.ItemClick += OnItemClick;
        speakersRecyclerView.SetAdapter(speakersAdapter);
    }

    private void OnItemClick(object sender, string speakerResumeUrl)
    {
        var speakerDetailsActivity = new Intent(this, typeof(SpeakerDetailsActivity));
        speakerDetailsActivity.PutExtra("speakerResumeUrl", speakerResumeUrl);
        StartActivity(speakerDetailsActivity);
    }

    public override void OnBackPressed()
    {
        StartActivity(typeof(CongressActivity));
        Finish();
    }
}

Вот ProgressBar вид из SpeakersActivity.axml:

<ProgressBar
     android:id = "@+id/loadingSpinner"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:indeterminateDrawable="@drawable/animdraw"
     android:visibility="gone"
     android:layout_gravity="center"/>

А вот и animdraw.xml:

<?xml version="1.0" encoding="utf-8" ?> 
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/spinner"
    android:fromDegrees="0.0"
    android:pivotX="50.0%"
    android:pivotY="50.0%"
    android:toDegrees="360.0" />
0 голосов
/ 27 марта 2020

Это проблема с потоком. Вам необходимо знать, что все представления, созданные в вашем пользовательском интерфейсе, создаются в MainThread, который также называется UIThread.

Теперь это общая концепция. что если у вас есть представление, только поток, который его создает, может его изменить, т. е. UIThread теперь возникает вопрос, как вы это сделаете, когда выполняющийся код находится в фоновом или вторичном потоке, независимо от того, кто его создал? Таким образом, используемая вами платформа обычно дает вам такой путь.

Например, Android,

Android имеет метод в своем классе Activity, который теперь называется RunOnUiThread это метод, который вы используете, когда есть что-то, что нужно сделать в пользовательском интерфейсе, но вы находитесь в фоновом потоке. iOS также имеет метод, который называется InvokeOnMainThread, XF имеет BeginInvokeOnMainThread!

Теперь, для вашей ситуации, вы добавляете фоновую задачу, которая лениво загружает ваш View, что здорово, теперь вам нужно понимать, что это не в главном потоке, так как вы создали задачу, которая должна выполняться в другом потоке через Task.Run, поэтому теперь вы используете метод Trick, о котором мы говорили, где бы вы ни играли с Views:

Обратите внимание, что я могу что-то упустить, потому что я не знаю весь код, но вы можете сделать все остальное самостоятельно, если вы освоите основы, поэтому здесь ничего не говорится:

protected override async void OnCreate(Bundle savedInstanceState)
{
    .....
    .....
loadingSpinner.Visibility = ViewStates.Visible;
await Task.Run(() =>
{
    //get all the speakers from the db
    allSpeakers = DatabaseHelper.GetAllFromTable<Speaker>("speakers.db");

    //get only the international spakers
    internationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("international")).ToList();

    //get only the national speakers
    nationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("national")).ToList();
  RunOnUiThread(()=>
  {
    //fill in the RecyclerView with data
    speakersRecyclerView = FindViewById<RecyclerView>(Resource.Id.speakersRecyclerView);
    speakersLayoutManager = new LinearLayoutManager(this);
    speakersRecyclerView.SetLayoutManager(speakersLayoutManager);
    speakersAdapter = new SpeakersAdapter(speakers);
    speakersAdapter.ItemClick += OnItemClick;
    speakersRecyclerView.SetAdapter(speakersAdapter);

    loadingSpinner.Visibility = ViewStates.Gone;
});
});
    .....
    .....
}

Гудлак, не стесняйтесь вернуться, если вы есть вопросы!

...