簡體   English   中英

使用 AsyncTask 將數據從 Activity 傳遞到 Fragment - Android

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

我正在嘗試將 ArrayList 從 MainActivity 中的 AsyncTask 傳遞到一個片段,但我得到了一個NullPointerException用於調用CategoryAdapter.getItemCount()即使我在 BroadCastReceiver 調用之后傳遞數組。

我究竟做錯了什么?

主要活動

 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());
    }
}

主頁片段

@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");
    }
}

我也試過從 OnReceive 方法附加 recyclerView,但它沒有被附加。 先感謝您!

  • Category 是否序列化?
  • 可以把BroadcastReceiver當作內部的class,然后Adpater收到數據的時候更新數據,因為代碼運行速度很快,不需要注冊監聽,馬上處理。

我想您將數據從MainActivity傳遞到HomeFragment的方式不正確。

你期待什么

  1. 調用MainActivity#GetBooksAsync
  2. 等到onPostExecute被調用
  3. HomeFragment准備接收廣播消息,然后更新UI
  4. 將消息從MainActivity廣播到片段

這里發生了什么

  1. 調用MainActivity#GetBooksAsync
  2. 等到onPostExecute被調用
  3. MainActivity廣播消息。 沒有接收者可以接收此消息!
  4. HomeFragment准備接收廣播消息,然后更新UI

那你應該如何傳遞數據呢?

有幾種方法。

  1. 像您所做的那樣在 UI 組件之間廣播數據。 但是您需要注意組件的生命周期 也就是說,當您廣播數據時,接收器必須已經初始化並且 UI 組件處於活動狀態。

  2. 建一個 singleton class 來存放數據。 您的活動和片段將 singleton class 視為數據存儲的公共位置。

  3. 如果數據大小足夠小,請使用Intent額外屬性來傳遞數據。

  4. 使用實時數據。 我相信這是社區推薦的最現代的方式。 雖然我不確定它是如何工作的。


為了驗證這是一個生命周期問題,

您可以嘗試在發送廣播消息之前添加延遲。

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());
    }



你的 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);
            }
        }
        
        ......
}

您的片段應該有一個 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);
        }
    }
}

我認為您的代碼存在幾個問題:

  1. 您的任務在與 UIThread(調度任務並處理結果)不同的線程中運行。 這意味着它很可能在不同的處理器/內核上運行。 處理后的值(例如您的集合)緩存在處理器中,並且在執行后的某個時候將數據寫回 RAM。 但這可能會在調用onPostExecute方法之后發生,該方法也會將集合帶到另一個處理器緩存中。 但是,當在集合從任務返回到 RAM 之前完成此操作時,它仍然是空的。 這就是所謂的競爭條件

現在有幾種方法可以解決這個問題。 最簡單的一種是使用Collections.synchronizedList(categories)這可以防止處理器緩存列表值並始終將其返回到 RAM(或使用在所有處理器/內核之間共享的 L3 緩存)。

  1. 我不確定你傳遞給集合的到底是什么。 意圖(及其數據)需要可序列化,而您添加到集合中的內容可能不可序列化。

然后我會使用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());
    }
}

並注意AsyncTaskLocalBroadcastManager已棄用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM