Проблемы с производительностью при загрузке изображений для RecyclerView - PullRequest
0 голосов
/ 23 декабря 2018

У меня есть ListView с пользовательским адаптером.В каждой строке есть ImageView, который я отображаю с использованием растрового изображения, но мой текущий код блокирует поток пользовательского интерфейса, поскольку я использую get() после выполнения моей AsyncTask, которая загружает растровые изображения.Я хотел бы изменить свой код и получить доступ к imageViews в onPostExecute() или что-то подобное.Так что строки уже отображаются без ожидания загрузки всех спрайтов.

Класс адаптера (загрузка начинается здесь)

public class PokemonAdapter extends ArrayAdapter<PokemonPOJO> implements View.OnClickListener{

private ArrayList<PokemonPOJO> dataSet;
Context mContext;
private int lastPosition = -1;

// View lookup cache
private static class ViewHolder {
    TextView txtName;
    TextView txtCP;
    TextView txtGenderShiny;
    ImageView sprite;
    Button btnDelete;
}

public PokemonAdapter(ArrayList<PokemonPOJO> data, Context context) {
    super(context, R.layout.row_pokemon, data);
    this.dataSet = data;
    this.mContext=context;
}

@Override
public void onClick(View v) {

    int position=(Integer) v.getTag();
    Object object= getItem(position);
    PokemonPOJO dataModel=(PokemonPOJO)object;

    switch (v.getId())
    {
        case R.id.btn_delete:
            FirebaseDatabase.getInstance().getReference("pokemons").child(dataModel.getUid()).removeValue();
            Toast.makeText(getContext(), "Pokemon removed!", Toast.LENGTH_SHORT).show();
            this.remove(dataModel);
            break;
    }
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // Get the data item for this position
    PokemonPOJO dataModel = getItem(position);
    // Check if an existing view is being reused, otherwise inflate the view
    ViewHolder viewHolder; // view lookup cache stored in tag

    final View result;

    if (convertView == null) {

        viewHolder = new ViewHolder();
        LayoutInflater inflater = LayoutInflater.from(getContext());
        convertView = inflater.inflate(R.layout.row_pokemon, parent, false);
        viewHolder.txtName = (TextView) convertView.findViewById(R.id.text_name);
        viewHolder.txtCP = (TextView) convertView.findViewById(R.id.text_cp);
        viewHolder.txtGenderShiny = (TextView) convertView.findViewById(R.id.text_gendershiny);
        viewHolder.sprite = (ImageView) convertView.findViewById(R.id.img_sprite);
        viewHolder.btnDelete = (Button)convertView.findViewById(R.id.btn_delete);
        result=convertView;

        convertView.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) convertView.getTag();
        result=convertView;
    }

    lastPosition = position;

    viewHolder.txtName.setText(dataModel.getName());
    viewHolder.txtCP.setText("CP: " + Integer.toString(dataModel.getCP()));
    viewHolder.txtGenderShiny.setText(dataModel.getGender() + (dataModel.isShiny() ? " (Shiny)" : ""));
    viewHolder.btnDelete.setOnClickListener(this);

    try {
        Bitmap bm = new DownloadImageTask().execute(dataModel.getSpriteUrl()).get();
        viewHolder.sprite.setImageBitmap(bm);
    } catch (Exception e) {
        e.printStackTrace();
    }
    viewHolder.btnDelete.setTag(position);

    // Return the completed view to render on screen
    return convertView;
}

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {

    @Override
    protected Bitmap doInBackground(String... urls) {
        String urldisplay = urls[0];
        Bitmap bm = null;
        try {
            InputStream in = new java.net.URL(urldisplay).openStream();
            bm = BitmapFactory.decodeStream(in);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bm;
    }

}

Фрагмент с ListView

public class MyPokemonFragment extends Fragment {

    private FirebaseAuth auth;
    private DatabaseReference pokemonDb;
    private TextView text_noPokemon;
    private ListView listViewPokemon;
    private static PokemonAdapter adapter;
    private populateListViewTask populateListView;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_mypokemon,null);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {

        super.onViewCreated(view, savedInstanceState);
        auth = FirebaseAuth.getInstance();

        listViewPokemon = view.findViewById(R.id.list_pokemon);
        text_noPokemon= view.findViewById(R.id.text_noPokemon);

        Query getUserPokemon = FirebaseDatabase.getInstance().getReference("pokemons").orderByChild("userUid").equalTo(auth.getCurrentUser().getUid());
        getUserPokemon.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot snapshot) {

                if(!snapshot.hasChildren()) {
                    text_noPokemon.setText("You have not added any Pokémon yet.");
                }
                else {
                    TreeMap<String, Pokemon> pokemons = new TreeMap<>();
                    for (DataSnapshot pokemon : snapshot.getChildren()) {
                        pokemons.put(pokemon.getKey(), pokemon.getValue(Pokemon.class));
                    }
                    populateListView = new populateListViewTask();
                    populateListView.execute(pokemons);
                }
            }

            @Override
            public void onCancelled(DatabaseError databaseError) { }
        });

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if(populateListView != null && populateListView.getStatus() == AsyncTask.Status.RUNNING)
            populateListView.cancel(true);
    }

    private class populateListViewTask extends AsyncTask<TreeMap<String, Pokemon>, Void, ArrayList<PokemonPOJO>> {

        @Override
        protected ArrayList<PokemonPOJO> doInBackground(TreeMap<String, Pokemon>... maps) {

            ArrayList<PokemonPOJO> pojos = new ArrayList<>();
            HttpURLConnection connection = null;
            BufferedReader reader = null;

            Iterator it = maps[0].entrySet().iterator();
            while(it.hasNext()) {
                Map.Entry pair = (Map.Entry)it.next();
                Pokemon p = (Pokemon)pair.getValue();
                try {
                    URL url = new URL("https://pokeapi.co/api/v2/pokemon/" + p.getPokedexNr() + "/");
                    connection = (HttpURLConnection) url.openConnection();
                    connection.connect();
                    InputStream stream = connection.getInputStream();
                    reader = new BufferedReader(new InputStreamReader(stream));
                    StringBuffer buffer = new StringBuffer();
                    String line = "";
                    while ((line = reader.readLine()) != null) {
                        buffer.append(line + "\n");
                    }

                    JSONObject j = new JSONObject(buffer.toString());
                    String name = j.getString("name");
                    String spriteUrl = (p.isShiny() ? j.getJSONObject("sprites").getString("front_shiny") : j.getJSONObject("sprites").getString("front_default"));

                    PokemonPOJO pojo = new PokemonPOJO((String)pair.getKey(), p.getPokedexNr(), name, spriteUrl, p.isShiny(), p.getGender(), p.getCP());
                    pojos.add(pojo);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    connection.disconnect();
                    try {
                        if (reader != null)
                            reader.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return pojos;
        }

        @Override
        protected void onPostExecute (ArrayList < PokemonPOJO > pojos) {
            adapter = new PokemonAdapter(pojos, getContext());
            listViewPokemon.setAdapter(adapter);
        }
    }
}

Pokemonстрока XML

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/img_sprite"
            android:layout_width="96dp"
            android:layout_height="96dp"
            android:scaleType="fitCenter" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="left|center_vertical"
            android:orientation="vertical">

            <TextView
                android:id="@+id/text_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="@android:color/black"
                android:textSize="20sp"
                android:textStyle="bold" />

            <TextView
                android:id="@+id/text_cp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="18sp" />

            <TextView
                android:id="@+id/text_gendershiny"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="18sp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="end"
            android:orientation="vertical">

            <Button
                android:id="@+id/btn_delete"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:backgroundTint="@color/colorPrimary"
                android:text="DELETE"
                android:textColor="#ffffff"
                android:textSize="16sp"
                android:textStyle="bold" />
        </LinearLayout>

    </LinearLayout>

</android.support.v7.widget.CardView>

1 Ответ

0 голосов
/ 23 декабря 2018

У вас проблемы с производительностью, потому что вы вызываете метод get() на вашем AsyncTask.Метод get() в основном заставляет основной поток ждать, пока код в AsyncTask завершит выполнение, прежде чем основной поток продолжит выполнение других инструкций.Почему Google добавил этот метод, по меньшей мере, любопытно.Так что сделайте это, чтобы исправить ваш код.

Создайте новый файл класса Java.Назовите файл «DownloadImageTask» и добавьте этот код:

public interface DownloadImageListener {
    void onCompletedImageDownload(Bitmap bm);
}


public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {

    private static final String TAG = DownloadImageTask.class.getSimpleName();

    private DownloadImageListener mListener;
    private String imageUrl = "";

    public DownloadImageTask(String imageUrl, DownloadImageListener listener){
        this.imageUrl = imageUrl;
        this.mListener = listener;
    }


    @Override
    protected Bitmap doInBackground(String... urls) {
        Bitmap bm = null;
        try {
            InputStream in = new java.net.URL(imageUrl).openStream();
            bm = BitmapFactory.decodeStream(in);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bm;
    }

    protected void onPostExecute(Bitmap bm) {
        mListener.onCompletedImageDownload(bm);
    }
}


Если у вас возникли проблемы с добавлением общедоступного interface в файл Java «DownloadImageTask», просто создайте отдельное имя файла Java «DownloadImageListener»"и вставьте туда код interface.


Установите код для запроса AsyncTask.
Измените код Adapter внутри вашего getView() с этого:

try {
    Bitmap bm = new DownloadImageTask().execute(dataModel.getSpriteUrl()).get();
    viewHolder.sprite.setImageBitmap(bm);
} catch (Exception e) {
    e.printStackTrace();
}

на это:

try {
    DownloadImageListener listener = new DownloadImageListener() {
        @Override
        public void onCompletedImageDownload(Bitmap bm) {
            if(bm != null){
                viewHolder.sprite.setImageBitmap(bm);
            }
        }
    };

    String imageUrl = dataModel.getSpriteUrl();

    DownloadImageTask downloadImageTask = new DownloadImageTask(imageUrl, listener);
    downloadImageTask.execute();

} catch (Exception e) {
    Log.e(TAG, e.getMessage());
}


Это позволяетAsyncTask для выполнения, и когда возвращается Bitmap, слушатель запускается в методе onPostExecute(), отправляя Bitmap вашему ListView в методе обратного вызова onCompletedImageDownload().

Дополнительная информация:
Для дальнейшего повышения производительности вы можете создать модель кэширования для сохранения и извлечения изображений с устройства, если вы уже загружали их в прошлом.Но для этого требуются некоторые действительно продвинутые методы - и становится действительно сложно, когда изображения, которые вы хотите загрузить, могут время от времени меняться.

...