简体   繁体   English

使用 AsyncTask 将数据从 Activity 传递到 Fragment - Android

[英]Passing data from Activity to Fragment using AsyncTask - Android

I'm trying to pass an ArrayList from an AsyncTask in the MainActivity to a fragment, but I'm getting a NullPointerException for invoking CategoryAdapter.getItemCount() even if I'm passing the array after the BroadCastReceiver Invoke.我正在尝试将 ArrayList 从 MainActivity 中的 AsyncTask 传递到一个片段,但我得到了一个NullPointerException用于调用CategoryAdapter.getItemCount()即使我在 BroadCastReceiver 调用之后传递数组。

What Am I doing wrong?我究竟做错了什么?

MainActivity主要活动

 class GetBooksAsync extends AsyncTask<Void, Void, Void> {
        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getApplicationContext());
    @Override
    protected Void doInBackground(Void... voids) {
        for (ECategories category : ECategories.values()) {
            try {
                categories.add(new Category(category.toString(), apiClient.getBooks(category)));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        Intent intent = new Intent("com.android.mainapp");
        intent.putExtra("categories", categories);
        manager.sendBroadcast(intent);
        replaceFragment(new HomeFragment());
    }
}

HomeFragment主页片段

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    initBroadCastReceiver();
    categoryAdapter = new CategoryAdapter(categories,getContext());
    View view = inflater.inflate(R.layout.fragment_home, container, false);
    recyclerView = view.findViewById(R.id.parent_rv);
    recyclerView.setAdapter(categoryAdapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
    categoryAdapter.notifyDataSetChanged();
    return view;
}

private void initBroadCastReceiver() {
    manager = LocalBroadcastManager.getInstance(getContext());
    MyBroadCastReceiver receiver = new MyBroadCastReceiver();
    IntentFilter filter = new IntentFilter();
    filter.addAction("com.android.mainapp");
    manager.registerReceiver(receiver,filter);
}

class MyBroadCastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        //get the categories from the intent
        categories = new ArrayList<Category>();
        categories = (ArrayList<Category>) intent.getSerializableExtra("categories");
    }
}

i've also tried attaching the recyclerView from the OnReceive Method, but it's not getting attached.我也试过从 OnReceive 方法附加 recyclerView,但它没有被附加。 Thank you in advance!先感谢您!

  • Is Category serialized? Category 是否序列化?
  • You can use BroadcastReceiver as an internal class, and then update the data of Adpater when it receives the data, because the code runs very fast, and it is not necessary to register for monitoring, and it will be processed immediately.可以把BroadcastReceiver当作内部的class,然后Adpater收到数据的时候更新数据,因为代码运行速度很快,不需要注册监听,马上处理。

I guess the way you pass the data from MainActivity to HomeFragment is incorrect.我想您将数据从MainActivity传递到HomeFragment的方式不正确。

WHAT YOU EXPECT你期待什么

  1. Call MainActivity#GetBooksAsync调用MainActivity#GetBooksAsync
  2. Wait till onPostExecute has been called等到onPostExecute被调用
  3. HomeFragment is ready to receive the broadcast message, then update UI HomeFragment准备接收广播消息,然后更新UI
  4. Broadcast the message from MainActivity to the fragment将消息从MainActivity广播到片段

WHAT IS HAPPENING HERE这里发生了什么

  1. Call MainActivity#GetBooksAsync调用MainActivity#GetBooksAsync
  2. Wait till onPostExecute has been called等到onPostExecute被调用
  3. Broadcast the message from MainActivity .MainActivity广播消息。 There is no receiver to receive this message!没有接收者可以接收此消息!
  4. HomeFragment is ready to receive the broadcast message, then update UI HomeFragment准备接收广播消息,然后更新UI

HOW SHALL YOU PASS THE DATA THEN?那你应该如何传递数据呢?

There are several way.有几种方法。

  1. Broadcast data between the UI component like the things you did.像您所做的那样在 UI 组件之间广播数据。 But you will need to beaware the life cycle of the components .但是您需要注意组件的生命周期 That is, when you broadcast the data, the receiver must already init and the UI component is in active.也就是说,当您广播数据时,接收器必须已经初始化并且 UI 组件处于活动状态。

  2. Build a singleton class to store the data.建一个 singleton class 来存放数据。 Your activity and fragment treats the singleton class as a common place for the data storage.您的活动和片段将 singleton class 视为数据存储的公共位置。

  3. Use Intent and the extra property to pass the data IF the data size is small enough.如果数据大小足够小,请使用Intent额外属性来传递数据。

  4. Use LiveData.使用实时数据。 I believe it is the most modern way recommended by the community.我相信这是社区推荐的最现代的方式。 Though I am not sure how its work.虽然我不确定它是如何工作的。


To verify the fact that it is an life cycle issue,为了验证这是一个生命周期问题,

you can try to add a delay before you sending the broadcast message.您可以尝试在发送广播消息之前添加延迟。

class GetBooksAsync extends AsyncTask<Void, Void, Void> {
...
    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        Intent intent = new Intent("com.android.mainapp");
        intent.putExtra("categories", categories);
        
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                manager.sendBroadcast(intent);
            }
        };

        Timer timer = new Timer();
        timer.schedule(task, 5 * 1000); // Delay the broadcast after 5 seconds

        
        replaceFragment(new HomeFragment());
    }



Your Adapter should be written like this.你的 Adapter 应该这样写。

    class CategoryAdapter extends RecyclerView.Adapter<CategoryAdapter.VHolder>{
        
        private ArrayList<Category> list = new ArrayList<Category>();

        public void setList(ArrayList<Category> list) {
            this.list = list;
            notifyDataSetChanged();
        }

        public CategoryAdapter(Context context) {
            // Do not pass a list in the constructor, because the list may be empty
        }
         
        class VHolder extends RecyclerView.ViewHolder {

            public VHolder(@NonNull View itemView) {
                super(itemView);
            }
        }
        
        ......
}

Your fragment should have a global Adapter for BroadcastReceiver to update data您的片段应该有一个 BroadcastReceiver 的全局适配器来更新数据

public class Test extends Fragment {

    // Create a global Adapter for BroadcastReceiver to call and update data
    private CategoryAdapter adapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        adapter = new CategoryAdapter(getContext());
        initBroadCastReceiver();
        View view = inflater.inflate(R.layout.fragment_home, container, false);
        recyclerView = view.findViewById(R.id.parent_rv);
        recyclerView.setAdapter(categoryAdapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        return view;
    }

    private void initBroadCastReceiver() {
        manager = LocalBroadcastManager.getInstance(getContext());
        MyBroadCastReceiver receiver = new MyBroadCastReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.android.mainapp");
        manager.registerReceiver(receiver,filter);
    }

    class MyBroadCastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            //get the categories from the intent
            ArrayList<Category> categories = (ArrayList<Category>) intent.getSerializableExtra("categories");
            adapter.setList(categories);
        }
    }
}

I think there are several problems with your code:我认为您的代码存在几个问题:

  1. Your task is running in a different thread than the UIThread (which schedules the task and processes the result).您的任务在与 UIThread(调度任务并处理结果)不同的线程中运行。 That means it most probably runs on a different processor/core.这意味着它很可能在不同的处理器/内核上运行。 Processed values (such as your collection) are cached in a processor and somewhen after execution the data is written back to RAM.处理后的值(例如您的集合)缓存在处理器中,并且在执行后的某个时候将数据写回 RAM。 But that might happen after the onPostExecute method is called, which takes the collection to another processor cache as well.但这可能会在调用onPostExecute方法之后发生,该方法也会将集合带到另一个处理器缓存中。 But when this is done before the collection is returned to the RAM from the task, it's still empty.但是,当在集合从任务返回到 RAM 之前完成此操作时,它仍然是空的。 That's called a race condition .这就是所谓的竞争条件

Now there are several ways to solve that.现在有几种方法可以解决这个问题。 The simplest one is to use Collections.synchronizedList(categories) This prevents the processor from caching list values and always return it to the RAM (or using L3 cache which is shared between all processors/cores).最简单的一种是使用Collections.synchronizedList(categories)这可以防止处理器缓存列表值并始终将其返回到 RAM(或使用在所有处理器/内核之间共享的 L3 缓存)。

  1. I'm not sure what exactly you pass to the collection.我不确定你传递给集合的到底是什么。 Intents (and it's data) need to be serializable and what you add to your collection is probably not serializable.意图(及其数据)需要可序列化,而您添加到集合中的内容可能不可序列化。

Then I would use the AsyncTask parameters:然后我会使用AsyncTask参数:

class GetBooksAsync extends AsyncTask<ECategories, Void, Collection<Category>> {
        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getApplicationContext());
    @Override
    protected Void doInBackground(ECategories... eCategories) {
        Collection<Category> categories = [whatever you want to use];
        for (ECategories category : eCategories) {
            try {
                categories.add(new Category(category.toString(), apiClient.getBooks(category)));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return categories;
    }
    @Override
    protected void onPostExecute(Collection<Category> categories) {
        super.onPostExecute(categories);
        Intent intent = new Intent("com.android.mainapp");
        intent.putExtra("categories", categories);
        manager.sendBroadcast(intent);
        replaceFragment(new HomeFragment());
    }
}

And note that AsyncTask and LocalBroadcastManager are deprecated.并注意AsyncTaskLocalBroadcastManager已弃用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM