Загрузчики Android, путь?
Я привык к созданию списков в Android с помощью адаптеров. Если мне нужны длинные данные для получения, я использую asynctask или простой исполняемый файл, чтобы обновить структуру данных, на которую опирается адаптер, и вызвать notifyDataChanged на адаптере.
Хотя это не так просто, я, наконец, обнаружил, что это простая модель, и она обеспечивает хорошее разделение логического представления (в асинхронной задаче, обновления структуры данных) и представления (в основном адаптера, выступающего в качестве фабрики представлений).
Тем не менее, я недавно прочитал о загрузчиках, представленных в HoneyComb и включенных в библиотеку поддержки обратной совместимости, я попробовал их и нашел много сложностей. С ними трудно обращаться, и они добавляют какую-то магию всему этому процессу с помощью менеджеров загрузчиков, добавляют много кода и не уменьшают количество классов или взаимодействующих элементов, но я могу ошибаться и хотел бы услышать некоторые хорошие замечания погрузчики.
- Каковы их преимущества загрузчиков с точки зрения строк кода, ясности и усилий?
- Каковы их преимущества загрузчиков с точки зрения разделения ролей во время загрузки данных или, в более широком смысле, с точки зрения дизайна?
- Могут ли они заменить загрузку данных в моем списке, чтобы реализовать их через загрузчики?
Хорошо, это форум разработчиков, так что вот пример. Пожалуйста, сделайте это лучше с загрузчиками:
package com.sof.test.loader;
import java.util.ArrayList;
import java.util.List;
import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.TextView;
/** The activity. */
public class LoaderTestActivity extends ListActivity {
private DataSourceOrDomainModel dataSourceOrDomainModel = new DataSourceOrDomainModel();
private List<Person> listPerson;
private PersonListAdapter personListAdapter;
private TextView emptyView;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listPerson = new ArrayList<Person>();
personListAdapter = new PersonListAdapter( listPerson );
setListAdapter( personListAdapter );
setUpEmptyView();
new PersonLoaderThread().execute();
}
public void setUpEmptyView() {
emptyView = new TextView( this );
emptyView.setLayoutParams( new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT ) );
emptyView.setVisibility(View.GONE);
((ViewGroup)getListView().getParent()).addView(emptyView);
getListView().setEmptyView(emptyView);
}
/** Simulate a long task to get data. */
private class PersonLoaderThread extends AsyncTask<Void, Integer, List<Person>> {
@Override
protected List<Person> doInBackground(Void... params) {
return dataSourceOrDomainModel.getListPerson( new ProgressHandler());
}
@Override
protected void onProgressUpdate(Integer... values) {
emptyView.setText( "Loading data :" + String.valueOf( values[ 0 ] ) +" %" );
}
@Override
protected void onPostExecute(List<Person> result) {
listPerson.clear();
listPerson.addAll( result );
personListAdapter.notifyDataSetChanged();
}
private class ProgressHandler implements ProgressListener {
@Override
public void personLoaded(int count, int total) {
publishProgress( 100*count / total );
}
}
}
/** List item view factory : the adapter. */
private class PersonListAdapter extends ArrayAdapter<Person> {
public PersonListAdapter( List<Person> listPerson ) {
super(LoaderTestActivity.this, 0, listPerson );
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if( convertView == null ) {
convertView = new PersonView( getContext() );
}
PersonView personView = (PersonView) convertView;
personView.setPerson( (Person) getItem(position) );
return personView;
}
}
}
Небольшой интерфейс обратного вызова для прогресса
package com.sof.test.loader;
/** Callback handler during data load progress. */
public interface ProgressListener {
public void personLoaded(int count, int total );
}
Виджет элемента списка
package com.sof.test.loader;
import com.sof.test.loader.R;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.TextView;
/** List Item View, display a person */
public class PersonView extends LinearLayout {
private TextView personNameView;
private TextView personFirstNameView;
public PersonView(Context context) {
super(context);
LayoutInflater inflater= (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate( R.layout.person_view,this );
personNameView = (TextView) findViewById( R.id.person_name );
personFirstNameView = (TextView) findViewById( R.id.person_firstname );
}
public void setPerson( Person person ) {
personNameView.setText( person.getName() );
personFirstNameView.setText( person.getFirstName() );
}
}
Это xml: res/person_view.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/person_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/person_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true" />
<TextView
android:id="@+id/person_firstname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/person_name" />
</RelativeLayout>
Источник данных или модель, предоставляющая данные (медленно)
package com.sof.test.loader;
import java.util.ArrayList;
import java.util.List;
/** A source of data, can be a database, a WEB service or a model. */
public class DataSourceOrDomainModel {
private static final int PERSON_COUNT = 100;
public List<Person> getListPerson( ProgressListener listener ) {
List<Person> listPerson = new ArrayList<Person>();
for( int i=0; i < PERSON_COUNT ; i ++ ) {
listPerson.add( new Person( "person", "" + i ) );
//kids, never do that at home !
pause();
if( listener != null ) {
listener.personLoaded(i,PERSON_COUNT);
}//if
}
return listPerson;
}//met
private void pause() {
try {
Thread.sleep( 100 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
POJO, представляющий человека:
package com.sof.test.loader;
/** A simple POJO to be displayed in a list, can be manipualted as a domain object. */
public class Person {
private String name;
private String firstName;
public Person(String name, String firstName) {
this.name = name;
this.firstName = firstName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}//class
2 ответа
Одна из проблем вашего кода, которую загрузчики стремятся исправить, состоит в том, что произойдет, если ваша деятельность будет перезапущена (скажем, из-за поворота устройства или изменения конфигурации), когда ваша асинхронная задача еще выполняется? в вашем случае ваша возобновленная активность запустит второй экземпляр задачи и отбросит результаты первого. Когда первый из них завершится, вы можете получить сбои из-за того, что у вашей асинхронной задачи есть ссылка - это то, что теперь является законченным действием.
И да, использование загрузчиков часто приводит к более / более сложному коду, особенно если вы не можете использовать один из предоставленных загрузчиков.
Если кто-то ищет загрузочную версию моего предыдущего примера: вот она:
package com.sof.test.loader;
import java.util.ArrayList;
import android.app.LoaderManager;
import java.util.List;
import android.app.ListActivity;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.TextView;
/** The activity. */
public class LoaderTestActivity2 extends ListActivity implements
LoaderManager.LoaderCallbacks<List<Person>> {
private DataSourceOrDomainModel dataSourceOrDomainModel = new DataSourceOrDomainModel();
private List<Person> listPerson;
private PersonListAdapter personListAdapter;
private TextView emptyView;
private Loader<List<Person>> personLoader;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listPerson = new ArrayList<Person>();
personListAdapter = new PersonListAdapter(listPerson);
setListAdapter(personListAdapter);
personLoader = new PersonLoader(this, dataSourceOrDomainModel, new ProgressHandler() );
setUpEmptyView();
getLoaderManager().initLoader(0, null, this);
personLoader.forceLoad();
}
public void setUpEmptyView() {
emptyView = new TextView(this);
emptyView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
emptyView.setVisibility(View.GONE);
((ViewGroup) getListView().getParent()).addView(emptyView);
getListView().setEmptyView(emptyView);
}
public void publishProgress(int progress) {
emptyView.setText("Loading data :" + String.valueOf(progress) + " %");
}
@Override
public Loader<List<Person>> onCreateLoader(int arg0, Bundle arg1) {
return personLoader;
}
@Override
public void onLoadFinished(Loader<List<Person>> personLoader, List<Person> result) {
listPerson.clear();
listPerson.addAll(result);
personListAdapter.notifyDataSetChanged();
}
@Override
public void onLoaderReset(Loader<List<Person>> arg0) {
listPerson.clear();
personListAdapter.notifyDataSetChanged();
}
/** List item view factory : the adapter. */
private class PersonListAdapter extends ArrayAdapter<Person> {
public PersonListAdapter(List<Person> listPerson) {
super(LoaderTestActivity2.this, 0, listPerson);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = new PersonView(getContext());
}
PersonView personView = (PersonView) convertView;
personView.setPerson((Person) getItem(position));
return personView;
}
}
private class ProgressHandler implements ProgressListener {
@Override
public void personLoaded(final int count, final int total) {
runOnUiThread( new Runnable() {
@Override
public void run() {
publishProgress(100 * count / total);
}
});
}
}
}
class PersonLoader extends AsyncTaskLoader<List<Person>> {
private DataSourceOrDomainModel dataSourceOrDomainModel;
private ProgressListener progressHandler;
public PersonLoader(Context context, DataSourceOrDomainModel dataSourceOrDomainModel, ProgressListener progressHandler ) {
super(context);
this.dataSourceOrDomainModel = dataSourceOrDomainModel;
this.progressHandler = progressHandler;
}
@Override
public List<Person> loadInBackground() {
return dataSourceOrDomainModel.getListPerson( progressHandler );
}
}
В этот пример было бы сложнее добавить поддержку (support librairy), поскольку в поддерживаемой librairy нет эквивалента ListAcitivity. Мне нужно было бы создать ListFragment или FragmentActivity и дать ему макет, включая список.