Android 图像的网格视图消耗所有内存并崩溃

Android grid view of images consumes all memory and crashes

我正在尝试调试我的应用程序中我消耗所有内存并导致应用程序崩溃的位置。

它全部加载查找,但当我向下滚动网格视图时,我将更多图像加载到 GridView 一切正常。如果我向上滚动,我可以看到它们正在从缓存中加载。但是当我继续向下滚动时,设备分配了大约 250mb 并崩溃了。我哪里出错了。

我可能还应该注意到,这个 Gird 在崩溃时有大约 150 张图片(每张约 20kb)。我希望得到更多。

请对我温柔一点,我是 Java 和 Android 的新手。

物品适配器

public ItemAdapter(Context context, int resource, List<Item> objects) {
    super(context, resource, objects);
    this.context = context;
    this.itemList = objects;

    final int maxMemory = (int)(Runtime.getRuntime().maxMemory() / 1024);
    final int cacheSize = maxMemory / 8;
    imageCache = new LruCache<>(cacheSize);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
    View view = inflater.inflate(R.layout.feed_staggered_grid_view_item, parent, false);

    //Display item name in textview widget
    Item item = itemList.get(position);
    TextView item_title = (TextView) view.findViewById(R.id.textView1);
    item_title.setText(item.getTitle());

    TextView item_brand = (TextView) view.findViewById(R.id.textView2);
    item_brand.setText(item.getBrand());

    //display image
    Bitmap bitmap = imageCache.get(item.getItemId());
    if (bitmap != null) {
        ImageView image = (ImageView) view.findViewById(R.id.imageView);
        image.setImageBitmap(bitmap);
    } else {
        ItemAndView container = new ItemAndView();
        container.item = item;
        container.view = view;

        ImageLoader loader = new ImageLoader();
        loader.execute(container);
    }

    return view;
}

class ItemAndView {
    public Item item;
    public View view;
    public Bitmap bitmap;
}

private class ImageLoader extends AsyncTask<ItemAndView, Void, ItemAndView> {

    @Override
    protected ItemAndView doInBackground(ItemAndView... params) {
        ItemAndView container = params[0];
        Item item = container.item;

        try {
            StringBuilder sb = new StringBuilder();
            sb.append(FeedFragment.IMAGES_BASE_URL);
            sb.append(item.getCategory());
            sb.append("/");
            sb.append(item.getProductId());
            sb.append("/img1.jpg");
            String imageUrl = sb.toString();

            InputStream in = (InputStream) new URL(imageUrl).getContent();
            Bitmap bitmap = BitmapFactory.decodeStream(in);
            item.setBitmap(bitmap);
            in.close();
            container.bitmap = bitmap;
            return container;
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(ItemAndView itemAndView) {
        ImageView image = (ImageView) itemAndView.view.findViewById(R.id.imageView);
        image.setImageBitmap(itemAndView.bitmap);
        if (imageCache.get(itemAndView.item.getItemId()) == null) {
            imageCache.put(itemAndView.item.getItemId(), itemAndView.bitmap);
        }
    }
}

FeedFragment

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (feedItemList == null) {
        requestData(FEED_BASE_URL + pageNum);
    }
    return inflater.inflate(R.layout.feed_staggered_grid_view, container, false);
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (feedItemList != null) {
        updateDisplay();
    }
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
}

private void requestData(String uri) {
    MyTask task = new MyTask();
    task.execute(uri);
    pageNum++;
}

protected void updateDisplay() {
    feedAdapter = new ItemAdapter(getActivity(), R.layout.feed_staggered_grid_view_item, feedItemList);
    feedGridView = (StaggeredGridView) getView().findViewById(R.id.grid_view);
    feedGridView.setAdapter(feedAdapter);
    feedGridView.setOnScrollListener(this);
    feedGridView.setOnItemClickListener(this);
}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    if (!isLoading && totalItemCount != 0) {
        int lastItemInScreen = firstVisibleItem + visibleItemCount;
        if (lastItemInScreen >= totalItemCount) {
            isLoading = true;
            requestData(FEED_BASE_URL + pageNum);
        }
    }
}

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    Item item = feedItemList.get(position);
    Intent intent = new Intent(getSherlockActivity(), ItemViewer.class);

    Bundle extras = new Bundle();
    extras.putString("item_title", item.getTitle());
    extras.putString("item_description", item.getDescription());
    extras.putString("item_brand", item.getBrand());
    extras.putDouble("item_price", item.getPrice());
    extras.putString("item_photo", item.getPhoto());
    intent.putExtras(extras);

    startActivity(intent);
}

private class MyTask extends AsyncTask<String, String, List<Item>> {

    @Override
    protected void onPreExecute() {
    }

    @Override
    protected List<Item> doInBackground(String... params) {
        String content = HttpManager.getData(params[0]);
        List<Item> data = ItemFeedJSONParser.parseFeed(content);
        if (feedItemList == null) {
            feedItemList = data;
        } else {
            feedItemList.addAll(data);
        }

        return null;
    }

    @Override
    protected void onPostExecute(List<Item> result) {
        if (pageNum <= 1) {
            updateDisplay();
        } else {
            feedAdapter.notifyDataSetChanged();
        }

        isLoading = false;

    }
}

这里是错误信息

 FATAL EXCEPTION: AsyncTask #5
Process: com.test.game.test, PID: 10931
java.lang.RuntimeException: An error occured while executing doInBackground()
        at android.os.AsyncTask.done(AsyncTask.java:300)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
        at java.util.concurrent.FutureTask.run(FutureTask.java:242)
        at android.os.AsyncTask$SerialExecutor.run(AsyncTask.java:231)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
        at java.lang.Thread.run(Thread.java:818)
 Caused by: java.lang.OutOfMemoryError: Failed to allocate a 429212 byte allocation with 80604 free bytes and 78KB until OOM
        at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
        at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
        at android.graphics.BitmapFactory.decodeStreamInternal(BitmapFactory.java:635)
        at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:611)
        at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:649)
        at com.test.game.adapters.ItemAdapter$ImageLoader.doInBackground(ItemAdapter.java:106)
        at com.test.game.adapters.ItemAdapter$ImageLoader.doInBackground(ItemAdapter.java:89)
        at android.os.AsyncTask.call(AsyncTask.java:288)
        at java.util.concurrent.FutureTask.run(FutureTask.java:237)
        at android.os.AsyncTask$SerialExecutor.run(AsyncTask.java:231)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
        at java.lang.Thread.run(Thread.java:818)

与其为每个调用创建 view,不如尝试使用 convertView。在 getView:

的前两行代码中尝试以下代码
View view;
if (convertView==null){
   LayoutInflater inflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
   view = inflater.inflate(R.layout.feed_staggered_grid_view_item, parent, false);
}else{
   view = convertView;
}

这里的问题是您加载的位图太大了。不仅图像的下载而且放置也应该在后台完成。使用 AsyncTask 来执行此操作,或者您始终可以使用 Picasso or Ion。我遇到了同样的问题,用Ion解决了,效果很好。

此外,不要为每个项目加载一个不同的视图,使用 convertView。

我认为您需要转到 mainfiest 文件并在您的应用程序标签中添加以下行:

android:largeHeap="true"