Qué concepto de diseño utilizar para actualizar la interfaz de usuario async

Estoy trabajando en una aplicación que muestra un horario de trabajo en una línea de tiempo.

Este es un diseño aproximado de cómo se diseña la aplicación en este momento:

Introduzca aquí la descripción de la imagen

Los datos se almacenan en una base de datos SQLite. Cuando la Timeline (un objeto singleton) solicita los datos de la clase auxiliar de base de datos, obtiene una ArrayList del Event s (por ejemplo, un Event podría ser un servicio que comienza el 1 de mayo de 2016 a las 03:00 y termina el 3 de mayo 2016 a las 16:00). La Timeline luego transforma estos Event en TimelineItem , una clase que representa (parte de) un Event para un día en particular.

La carga de Event s y la transformación de Event s en TimelineItem s ambos se realizan en AsyncTasks. Hasta aquí todo bien.

Ahora viene la parte que estoy luchando con: la actualización de la interfaz de usuario después de una nueva búsqueda de DB.

Mi primer enfoque fue pasar el ArrayList actualizado de TimelineItems al adaptador RecyclerView y dejar que el adaptador sepa que los datos han cambiado con notifyDatasetChanged() . El problema con este enfoque es que 1) un montón de trabajo innecesario se está haciendo (porque estamos recalculando todos los eventos / TimelineItems, no sólo los cambios) y 2) la posición de desplazamiento en el RecyclerView se restablece después de cada DB buscar

En mi segundo enfoque, he implementado algunos métodos para comprobar qué eventos / TimelineItems han cambiado desde la última pantalla con la idea de sólo cambiar esos TimelineItems, con notifyItemChanged() . Menos trabajo se está haciendo y no hay necesidad de preocuparse por las posiciones de desplazamiento en absoluto. El poco complicado es que la comprobación de qué elementos han cambiado toma un cierto tiempo, así que necesita ser asincrónico también:

Traté de hacer las manipulaciones de código en doInBackground() y la actualización de la interfaz de usuario mediante la publicación de eventos de bus otto en onProgressUpdate() .

 private class InsertEventsTask extends AsyncTask<Void, Integer, Void> { @Override protected Void doInBackground(Void... params) { ArrayList<Event> events = mCachedEvents; // if mChangedEvents is not null and not empty if (events != null && !events.isEmpty()) { // get the list of pairs for the events ArrayList<TimelineItemForDateTimePair> listOfPairs = convertEventsToPairs(events); // insert the TimelineItems from the pairs into the Timeline for (int i = 0; i < listOfPairs.size(); i++) { // get the last position for the DateTime associated with the pair int position = findLastPositionForDate(listOfPairs.get(i).dateTime); // if position is -1, the events started on a day before the timeline starts // so keep skipping pairs until position > -1 if (position > -1) { // if the item is a PlaceholderItem if (mTimelineItems.get(position).isPlaceholderItem) { // remove the PlaceholderItem mTimelineItems.remove(position); // and add the TimelineItem from the pair at the position the PlaceholderItem was at mTimelineItems.add(position, listOfPairs.get(i).timelineItem); // update the UI on the UI thread publishProgress(position, TYPE_CHANGED); } else { // if the item is not a PlaceholderItem, there's already an normal TimelineItem in place // place the new item at the next position on the Timeline mTimelineItems.add(position + 1, listOfPairs.get(i).timelineItem); publishProgress(position, TYPE_ADDED); } } } } return null; } /** * onProgressUpdate handles the UI changes on the UI thread for us. Type int available: * - TYPE_CHANGED * - TYPE_ADDED * - TYPE_DELETED * * @param values value[0] is the position as <code>int</code>, * value[1] is the type of manipulation as <code>int</code> */ @Override protected void onProgressUpdate(Integer... values) { int position = values[0]; int type = values[1]; // update the UI for each changed/added/deleted TimelineItem if (type == TYPE_CHANGED) { BusProvider.getInstance().post(new TimelineItemChangedNotification(position)); } else if (type == TYPE_ADDED) { BusProvider.getInstance().post((new TimelineItemAddedNotification(position))); } else if (type == TYPE_DELETED) { // TODO: make delete work bro! } } } 

El problema es que, de alguna manera, el desplazamiento mientras se está publicando este progreso desordena la interfaz de usuario completamente.

Mi principal problema es: cuando actualizo un elemento específico en el conjunto de datos (TimelineItems) del adaptador, notifyItemChanged () cambia el elemento pero no lo coloca en la posición correcta.

Aquí está mi adaptador:

 /** * A custom RecyclerView Adapter to display a Timeline in a TimelineFragment. */ public class TimelineAdapter extends RecyclerView.Adapter<TimelineAdapter.TimelineItemViewHolder> { /************* * VARIABLES * *************/ private ArrayList<TimelineItem> mTimelineItems; /**************** * CONSTRUCTORS * ****************/ /** * Constructor with <code>ArrayList<TimelineItem></code> as data set argument. * * @param timelineItems ArrayList with TimelineItems to display */ public TimelineAdapter(ArrayList<TimelineItem> timelineItems) { this.mTimelineItems = timelineItems; } // Create new views (invoked by the layout manager) @Override public TimelineItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // create a new view View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_timeline, parent, false); // set the view's size, margins, paddings and layout parameters // ... return new TimelineItemViewHolder(v); } // Replace the contents of a view (invoked by the layout manager) @Override public void onBindViewHolder(TimelineItemViewHolder holder, int position) { // - get element from your data set at this position // - replace the contents of the view with that element // if the item is a ShowPreviousMonthsItem, set the showPreviousMonthsText accordingly if (mTimelineItems.get(position).isShowPreviousMonthsItem) { holder.showPreviousMonthsText.setText(mTimelineItems.get(position).showPreviousMonthsText); } else { // otherwise set the showPreviousMonthsText blank holder.showPreviousMonthsText.setText(""); } // day of month & day of week of the TimelineItem if (mTimelineItems.get(position).isFirstItemOfDay) { holder.dayOfWeek.setText(mTimelineItems.get(position).dayOfWeek); holder.dayOfMonth.setText(mTimelineItems.get(position).dayOfMonth); } else { holder.dayOfWeek.setText(""); holder.dayOfMonth.setText(""); } // Event name for the TimelineItem holder.name.setText(mTimelineItems.get(position).name); // place and goingTo of the TimelineItem // if combinedPlace == "" if(mTimelineItems.get(position).combinedPlace.equals("")) { if (mTimelineItems.get(position).isFirstDayOfEvent) { holder.place.setText(mTimelineItems.get(position).place); } else { holder.place.setText(""); } if (mTimelineItems.get(position).isLastDayOfEvent) { holder.goingTo.setText(mTimelineItems.get(position).goingTo); } else { holder.goingTo.setText(""); } holder.combinedPlace.setText(""); } else { holder.place.setText(""); holder.goingTo.setText(""); holder.combinedPlace.setText(mTimelineItems.get(position).combinedPlace); } if(mTimelineItems.get(position).startDateTime != null) { holder.startTime.setText(mTimelineItems.get(position).startDateTime.toString("HH:mm")); } else { holder.startTime.setText(""); } if(mTimelineItems.get(position).endDateTime != null) { holder.endTime.setText(mTimelineItems.get(position).endDateTime.toString("HH:mm")); } else { holder.endTime.setText(""); } if (!mTimelineItems.get(position).isShowPreviousMonthsItem) { if (mTimelineItems.get(position).date.getDayOfWeek() == DateTimeConstants.SUNDAY) { holder.dayOfWeek.setTextColor(Color.RED); holder.dayOfMonth.setTextColor(Color.RED); } else { holder.dayOfWeek.setTypeface(null, Typeface.NORMAL); holder.dayOfMonth.setTypeface(null, Typeface.NORMAL); holder.dayOfWeek.setTextColor(Color.GRAY); holder.dayOfMonth.setTextColor(Color.GRAY); } } else { ((RelativeLayout) holder.dayOfWeek.getParent()).setBackgroundColor(Color.WHITE); } holder.bindTimelineItem(mTimelineItems.get(position)); } // Return the size of the data set (invoked by the layout manager) @Override public int getItemCount() { return mTimelineItems.size(); } // replace the data set public void setTimelineItems(ArrayList<TimelineItem> timelineItems) { this.mTimelineItems = timelineItems; } // replace an item in the data set public void swapTimelineItemAtPosition(TimelineItem item, int position) { mTimelineItems.remove(position); mTimelineItems.add(position, item); notifyItemChanged(position); } // the ViewHolder class containing the relevant views, // also binds the Timeline item itself to handle onClick events public class TimelineItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { protected TextView dayOfWeek; protected TextView dayOfMonth; protected TextView showPreviousMonthsText; protected TextView name; protected TextView place; protected TextView combinedPlace; protected TextView goingTo; protected TextView startTime; protected TextView endTime; protected TimelineItem timelineItem; public TimelineItemViewHolder(View view) { super(view); view.setOnClickListener(this); this.dayOfWeek = (TextView) view.findViewById(R.id.day_of_week); this.dayOfMonth = (TextView) view.findViewById(R.id.day_of_month); this.showPreviousMonthsText = (TextView) view.findViewById(R.id.load_previous_data); this.name = (TextView) view.findViewById(R.id.name); this.place = (TextView) view.findViewById(R.id.place); this.combinedPlace = (TextView) view.findViewById(R.id.combined_place); this.goingTo = (TextView) view.findViewById(R.id.going_to); this.startTime = (TextView) view.findViewById(R.id.start_time); this.endTime = (TextView) view.findViewById(R.id.end_time); } public void bindTimelineItem(TimelineItem item) { timelineItem = item; } // handles the onClick of a TimelineItem @Override public void onClick(View v) { // if the TimelineItem is a ShowPreviousMonthsItem if (timelineItem.isShowPreviousMonthsItem) { BusProvider.getInstance().post(new ShowPreviousMonthsRequest()); } // if the TimelineItem is a PlaceholderItem else if (timelineItem.isPlaceholderItem) { Toast.makeText(v.getContext(), "(no details)", Toast.LENGTH_SHORT).show(); } // else the TimelineItem is an actual event else { Toast.makeText(v.getContext(), "eventId = " + timelineItem.eventId, Toast.LENGTH_SHORT).show(); } } } 

Y este es el método que se desencadena en el TimelineFragment cuando se publica un cambio en el bus de eventos:

 @Subscribe public void onTimelineItemChanged(TimelineItemChangedNotification notification) { int position = notification.position; Log.d(TAG, "TimelineItemChanged detected for position " + position); mAdapter.swapTimelineItemAtPosition(mTimeline.mTimelineItems.get(position), position); mAdapter.notifyItemChanged(position); Log.d(TAG, "Item for position " + position + " swapped"); } 

Una cosa a tener en cuenta es que el conjunto de datos del adaptador parece mostrar correctamente después de desplazarse lejos de los datos cambiados lo suficientemente lejos y volver a la posición después de eso. Inicialmente la interfaz de usuario es totalmente desordenado sin embargo.

EDITAR:

Me pareció que la adición

 mAdapter.notifyItemRangeChanged(position, mAdapter.getItemCount()); 

Resuelve el problema pero – lamentablemente – establece la posición de desplazamiento a la que se está cambiando 🙁

Aquí está mi TimelineFragment:

 /** * Fragment displaying a Timeline using a RecyclerView */ public class TimelineFragment extends BackHandledFragment { // DEBUG flag and TAG private static final boolean DEBUG = false; private static final String TAG = TimelineFragment.class.getSimpleName(); // variables protected RecyclerView mRecyclerView; protected TimelineAdapter mAdapter; protected LinearLayoutManager mLinearLayoutManager; protected Timeline mTimeline; protected MenuItem mMenuItemScroll2Today; protected MenuItem mMenuItemReload; protected String mToolbarTitle; // TODO: get the value of this boolean from the shared preferences private boolean mUseTimelineItemDividers = true; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // get a handle to the app's Timeline singleton mTimeline = Timeline.getInstance(); setHasOptionsMenu(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_timeline, container, false); rootView.setTag(TAG); mRecyclerView = (RecyclerView) rootView.findViewById(R.id.timeline_list); mRecyclerView.hasFixedSize(); // LinearLayoutManager constructor mLinearLayoutManager = new LinearLayoutManager(getActivity()); // set the layout manager setRecyclerViewLayoutManager(); // adapter constructor mAdapter = new TimelineAdapter(mTimeline.mTimelineItems); // set the adapter for the RecyclerView. mRecyclerView.setAdapter(mAdapter); // add lines between the different items if using them if (mUseTimelineItemDividers) { RecyclerView.ItemDecoration itemDecoration = new TimelineItemDivider(this.getContext()); mRecyclerView.addItemDecoration(itemDecoration); } // add the onScrollListener mRecyclerView.addOnScrollListener(new TimelineOnScrollListener(mLinearLayoutManager) { // when the first visible item on the Timeline changes, // adjust the Toolbar title accordingly @Override public void onFirstVisibleItemChanged(int position) { mTimeline.mCurrentScrollPosition = position; try { String title = mTimeline.mTimelineItems .get(position).date .toString(TimelineConfig.TOOLBAR_DATE_FORMAT); // if mToolbarTitle is null, set it to the new title and post on bus if (mToolbarTitle == null) { if (DEBUG) Log.d(TAG, "mToolbarTitle is null - posting new title request on bus: " + title); mToolbarTitle = title; BusProvider.getInstance().post(new ChangeToolbarTitleRequest(mToolbarTitle)); } else { // if mToolbarTitle is not null // only post on the bus if the new title is different from the previous one if (!title.equals(mToolbarTitle)) { if (DEBUG) Log.d(TAG, "mToolbarTitle is NOT null, but new title detected - posting new title request on bus: " + title); mToolbarTitle = title; BusProvider.getInstance().post(new ChangeToolbarTitleRequest(mToolbarTitle)); } } } catch (NullPointerException e) { // if the onFirstVisibleItemChanged is called on a "ShowPreviousMonthsItem", // leave the title as it is } } }); return rootView; } /** * Set RecyclerView's LayoutManager to the one given. */ public void setRecyclerViewLayoutManager() { int scrollPosition; // If a layout manager has already been set, get current scroll position. if (mRecyclerView.getLayoutManager() != null) { scrollPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager()) .findFirstCompletelyVisibleItemPosition(); } else { scrollPosition = mTimeline.mFirstPositionForToday; } mRecyclerView.setLayoutManager(mLinearLayoutManager); mLinearLayoutManager.scrollToPositionWithOffset(scrollPosition, 0); } // set additional menu items for the Timeline fragment @Override public void onPrepareOptionsMenu(Menu menu) { // scroll to today mMenuItemScroll2Today = menu.findItem(R.id.action_scroll2today); mMenuItemScroll2Today.setVisible(true); mMenuItemScroll2Today.setIcon(Timeline.getIconForDateTime(new DateTime())); mMenuItemScroll2Today.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { // stop scrolling mRecyclerView.stopScroll(); // get today's position int todaysPosition = mTimeline.mFirstPositionForToday; // scroll to today's position mLinearLayoutManager.scrollToPositionWithOffset(todaysPosition, 0); return false; } }); // reload data from Hacklberry mMenuItemReload = menu.findItem(R.id.action_reload_from_hacklberry); mMenuItemReload.setVisible(true); mMenuItemReload.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { // stop scrolling mRecyclerView.stopScroll(); // mTimeline.reloadDBForCurrentMonth(); mTimeline.loadEventsFromUninfinityDBAsync(mTimeline.mTimelineStart, mTimeline.mTimelineEnd); return false; } }); super.onPrepareOptionsMenu(menu); } @Override public void onResume() { super.onResume(); // if the Timeline has been invalidated, let AllInOneActivity know it needs to replace // this Fragment with a new one if (mTimeline.isInvalidated()) { Log.d(TAG, "posting TimelineInvalidatedNotification on the bus ..."); BusProvider.getInstance().post( new TimelineInvalidatedNotification()); } // fetch today's menu icon if (mMenuItemScroll2Today != null) { if (DEBUG) Log.d(TAG, "fetching scroll2today menu icon"); mMenuItemScroll2Today.setIcon(Timeline.getIconForDateTime(new DateTime())); } } // from BackHandledFragment @Override public String getTagText() { return TAG; } // from BackHandledFragment @Override public boolean onBackPressed() { return false; } @Subscribe public void onHacklberryReloaded(HacklberryLoadedNotification notification) { resetReloading(); } // handles ShowPreviousMonthsRequests posted on the bus by the TimelineAdapter's ShowPreviousMonthsItem onClick() @Subscribe public void onShowPreviousMonthsRequest(ShowPreviousMonthsRequest request) { // create an empty OnItemTouchListener to prevent the user from manipulating // the RecyclerView while it loads more data (would mess up the scroll position) EmptyOnItemTouchListener listener = new EmptyOnItemTouchListener(); // add it to the RecyclerView mRecyclerView.addOnItemTouchListener(listener); // load the previous months (= add the required TimelineItems) int newScrollToPosition = mTimeline.showPreviousMonths(); // pass the new data set to the TimelineAdapter mAdapter.setTimelineItems(mTimeline.mTimelineItems); // notify the adapter the data set has changed mAdapter.notifyDataSetChanged(); // scroll to the last scroll (updated) position mLinearLayoutManager.scrollToPositionWithOffset(newScrollToPosition, 0); } @Subscribe public void onTimelineItemChanged(TimelineItemChangeNotification notification) { int position = notification.position; Log.d(TAG, "TimelineItemChanged detected for position " + position); mAdapter.swapTimelineItemAtPosition(mTimeline.mTimelineItems.get(position), position); //mAdapter.notifyItemRangeChanged(position, position); Log.d(TAG, "Item for position " + position + " swapped"); } 

He tomado una captura de pantalla de la aplicación después de que se cargue por primera vez. Voy a explicar muy rápido lo que sucede en la inicialización:

  1. La línea de tiempo se construye poblando todos los días con PlaceholderItems (un TimelineItem con sólo una fecha).
  2. Los eventos se cargan desde el DB y se transforman en TimelineItems
  3. Cada vez que un nuevo TimelineItem ha cambiado y está listo, la línea de tiempo empuja el TimelineFragment a través del bus otto para actualizar el conjunto de datos del adaptador para esa posición en particular con el nuevo TimelineItem.

Aquí hay una captura de pantalla de lo que sucede después de la carga inicial:

La línea de tiempo se carga pero ciertos elementos se insertan en la posición incorrecta.

Introduzca aquí la descripción de la imagen

Al desplazarse y volver al rango de días que se mostraba incorrectamente antes, todo es bueno:

Introduzca aquí la descripción de la imagen

Introduzca aquí la descripción de la imagen

Acerca de su segundo enfoque. Probablemente su código no es workind porque tiene Data Race en mTimelineItems y mCachedEvents . No puedo ver todo tu código, pero parece que estás usando mTimelineItems dentro de doInBackground() simultáneamente con el subproceso de interfaz de usuario sin ninguna sincronización.

Te propongo hacer una mezcla de tu primer y segundo acercamientos:

  1. Haga una copia de los datos originales ( mTimelineItems ) y envíela a AsyncTask .
  2. Cambie la copia asincrónicamente en doInBackground() y registre todos los cambios.
  3. Devuelve los datos y registros modificados al subproceso de la interfaz de usuario.
  4. Aplique los nuevos datos al RecyclerView mediante registros.

Permítanme ilustrar este enfoque en código.

Gestión de datos:

 public class AsyncDataUpdater { /** * Example data entity. We will use it * in our RecyclerView. */ public static class TimelineItem { public final String name; public final float value; public TimelineItem(String name, float value) { this.name = name; this.value = value; } } /** * That's how we will apply our data changes * on the RecyclerView. */ public static class Diff { // 0 - ADD; 1 - CHANGE; 2 - REMOVE; final int command; final int position; Diff(int command, int position) { this.command = command; this.position = position; } } /** * And that's how we will notify the RecyclerView * about changes. */ public interface DataChangeListener { void onDataChanged(ArrayList<Diff> diffs); } private static class TaskResult { final ArrayList<Diff> diffs; final ArrayList<TimelineItem> items; TaskResult(ArrayList<TimelineItem> items, ArrayList<Diff> diffs) { this.diffs = diffs; this.items = items; } } private class InsertEventsTask extends AsyncTask<Void, Void, TaskResult> { //NOTE: this is copy of the original data. private ArrayList<TimelineItem> _old_items; InsertEventsTask(ArrayList<TimelineItem> items) { _old_items = items; } @Override protected TaskResult doInBackground(Void... params) { ArrayList<Diff> diffs = new ArrayList<>(); try { //TODO: long operation(Database, network, ...). Thread.sleep(1000); } catch(InterruptedException e) { e.printStackTrace(); } //Some crazy manipulation with data... //NOTE: we change the copy of the original data! Random rand = new Random(); for(int i = 0; i < 10; i ++) { float rnd = rand.nextFloat() * 100.0f; for(int j = 0; j < _old_items.size(); j++) { if(_old_items.get(j).value > rnd) { TimelineItem item = new TimelineItem("Item " + rnd, rnd); //Change data. _old_items.add(j, item); //Log the changes. diffs.add(new Diff(0, j)); break; } } } for(int i = 0; i < 5; i ++) { int rnd_index = rand.nextInt(_old_items.size()); //Change data. _old_items.remove(rnd_index); //Log the changes. diffs.add(new Diff(2, rnd_index)); } //... return new TaskResult(_old_items, diffs); } @Override protected void onPostExecute(TaskResult result) { super.onPostExecute(result); //Apply the new data in the UI thread. _items = result.items; if(_listener != null) _listener.onDataChanged(result.diffs); } } private DataChangeListener _listener; private InsertEventsTask _task = null; /** Managed data. */ private ArrayList<TimelineItem> _items = new ArrayList<>(); public AsyncDataUpdater() { // Some test data. for(float i = 10.0f; i <= 100.0f; i += 10.0f) _items.add(new TimelineItem("Item " + i, i)); } public void setDataChangeListener(DataChangeListener listener) { _listener = listener; } public void updateDataAsync() { if(_task != null) _task.cancel(true); // NOTE: we should to make the new copy of the _items array. _task = new InsertEventsTask(new ArrayList<>(_items)); _task.execute(); } public int getItemsCount() { return _items.size(); } public TimelineItem getItem(int index) { return _items.get(index); } } 

Uso en la interfaz de usuario:

 public class MainActivity extends AppCompatActivity { private static class ViewHolder extends RecyclerView.ViewHolder { private final TextView name; private final ProgressBar value; ViewHolder(View itemView) { super(itemView); name = (TextView)itemView.findViewById(R.id.tv_name); value = (ProgressBar)itemView.findViewById(R.id.pb_value); } void bind(AsyncDataUpdater.TimelineItem item) { name.setText(item.name); value.setProgress((int)item.value); } } private static class Adapter extends RecyclerView.Adapter<ViewHolder> implements AsyncDataUpdater.DataChangeListener { private final AsyncDataUpdater _data; Adapter(AsyncDataUpdater data) { _data = data; _data.setDataChangeListener(this); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.list_item, parent, false); return new ViewHolder(v); } @Override public void onBindViewHolder(ViewHolder holder, int position) { holder.bind(_data.getItem(position)); } @Override public int getItemCount() { return _data.getItemsCount(); } @Override public void onDataChanged(ArrayList<AsyncDataUpdater.Diff> diffs) { //Apply changes. for(AsyncDataUpdater.Diff d : diffs) { if(d.command == 0) notifyItemInserted(d.position); else if(d.command == 1) notifyItemChanged(d.position); else if(d.command == 2) notifyItemRemoved(d.position); } } } private AsyncDataUpdater _data = new AsyncDataUpdater(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RecyclerView rv_content = (RecyclerView)findViewById(R.id.rv_content); rv_content.setLayoutManager(new LinearLayoutManager(this)); rv_content.setAdapter(new Adapter(_data)); Button btn_add = (Button)findViewById(R.id.btn_add); btn_add.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { _data.updateDataAsync(); } }); } } 

Puse la aplicación del ejemplo en GH, así que usted puede probarlo si usted quiere.

Actualización 1

Acerca de Data Race.

  1. this.mTimelineItems = timelineItems; Dentro del constructor TimelineAdapter() hace una copia de la referencia a ArrayList , pero no la copia de ArrayList . Por lo tanto, tiene dos referencias: TimelineAdapter.mTimelineItems y Timeline.mTimelineItems , que se refieren al mismo objeto ArrayList . Por favor, mira esto .

  2. La carrera de datos se produce cuando doInBackground() llamado desde Worker Thread y onProgressUpdate() llamado desde UI Thread simultáneamente. La razón principal es que publishProgress() no llama onProgressUpdate() síncrona . En su lugar, publishProgress() planea la llamada de onProgressUpdate() en el subproceso de UI en el futuro. Esta es una buena descripción del problema.

Fuera de contexto.

Esta:

 mTimelineItems.set(position, item); 

Debe ser más rápido que esto:

 mTimelineItems.remove(position); mTimelineItems.add(position, item); 
  • Cómo detener asynctask hilo en Android?
  • ¿Debo dar params al constructor oa AsyncTask.execute (params)?
  • Conceptos básicos de Android: ejecutar código en el subproceso de la interfaz de usuario
  • ¿Es posible separar la interfaz de usuario de WebView y los subprocesos HTTP?
  • AsyncTask "Sólo el subproceso original que creó una jerarquía de vista puede tocar sus vistas."
  • Error en el método ProgressDialog.dimiss cuando se llama por AsyncTask
  • Error en la conexión FTP de Android
  • ListView es muy lento - android
  • Cómo obtener el resultado de OnPostExecute () en la actividad principal porque AsyncTask es una clase aparte?
  • ¿AsyncTask sigue esperando?
  • ¿Necesita ayuda para descargar imágenes de fondo en Android?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.