从服务器下载图片并保存到SD卡,不使用BitmapFactory。

5
我正在创建一个应用程序,用于从服务器下载图像并在ListView中显示。我遇到的问题是内存泄漏,导致我的应用程序崩溃。我在 Android 博客中搜索了一些文章,例如 此链接,它提供了一个很好的想法,但仍然不能够在多个线程中使用。一些 Android 设备可以处理它,但有些设备只能在单个线程中处理,有时根本无法工作。
我的应用程序有许多活动,每个活动都有一个需要尽可能快速地显示图像的 ListView。通过 Google IO 2012,他们使用缓冲区将原始图像保存到 SD 卡中,并解决了内存泄漏的问题,但由于需要下载的图像太大,加载速度太慢。
我的问题是:有没有办法将图像缩放并将其写入SD卡?我找到了一些可能的解决方案,如在 inputstream 对象中使用 Skip byte,我还能够找到需要下载的图像的宽度、高度和每像素位数。
以下代码在 Google IO 2012 中使用,可以很好地支持多线程,在我的情况下,我有四个线程在后台运行。
private void downloadAndWriteFile(final String url, final File file) throws OutOfMemoryError {
    BufferedOutputStream out = null;

    try {
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setDoInput(true);
        conn.connect();

        final InputStream in = new BufferedInputStream(conn.getInputStream(), IO_BUFFER_SIZE_BYTES);    // buffer size 1KB
        out = new BufferedOutputStream(new FileOutputStream(file), IO_BUFFER_SIZE_BYTES);

        int b;
        while ((b = in.read()) != -1) {
            out.write(b);
        }
        out.close();
        conn.disconnect();
    }
    catch (Exception e) {
        Log.e(TAG, "!!downloadAndWriteFile " + e.getMessage());
        file.delete();
    }
}
3个回答

1

1) 在设置图像之前,请使用以下代码释放与此位图关联的本机对象,并清除对像素数据的引用。如果没有其他引用,它只是允许垃圾回收。

BitmapDrawable drawable = (BitmapDrawable) myImage.getDrawable();
Bitmap bitmap = drawable.getBitmap();
if (bitmap != null)
{
    bitmap.recycle();
}

2) 使用此方法来减少内存中位图的大小:

/**
 * decodes image and scales it to reduce memory consumption
 * 
 * @param file
 * @param requiredSize
 * @return
 */
public static Bitmap decodeFile(File file, int requiredSize) {
    try {

        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(file), null, o);

        // The new size we want to scale to

        // Find the correct scale value. It should be the power of 2.
        int width_tmp = o.outWidth, height_tmp = o.outHeight;
        int scale = 1;
        while (true) {
            if (width_tmp / 2 < requiredSize
                    || height_tmp / 2 < requiredSize)
                break;
            width_tmp /= 2;
            height_tmp /= 2;
            scale *= 2;
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;

        Bitmap bmp = BitmapFactory.decodeStream(new FileInputStream(file),
                null, o2);

        return bmp;

    } catch (FileNotFoundException e) {
    } finally {
        System.gc();
    }
    return null;
}

谢谢您的回答,我已经尝试过这种情况,但是对于一些不允许JNI分配更多内存的设备,它仍然会出现内存泄漏问题。Google IO 2012的代码可以使其在支持所有设备的多线程中工作,但是如果图像太大,则速度非常慢。有时用户会快速切换活动,因此第一次加载时效果并不好。 - vsatkh

0
你可以使用这个。
private void downloadImagesToSdCard(String downloadUrl,String imageName) {
try {
    URL url = new URL(downloadUrl); //you can write here any link

    File myDir =  new File("/sdcard"+"/"+Constants.imageFolder);
    //Something like ("/sdcard/file.mp3")


    if (!myDir.exists()) {
        myDir.mkdir();
        Log.v("", "inside mkdir");

    }

    Random generator = new Random();
    int n = 10000;
    n = generator.nextInt(n);
    String fname = imageName;
    File file = new File (myDir, fname);
    if (file.exists ()) file.delete (); 

         /* Open a connection to that URL. */
        URLConnection ucon = url.openConnection();
        InputStream inputStream = null;
       HttpURLConnection httpConn = (HttpURLConnection)ucon;
      httpConn.setRequestMethod("GET");
      httpConn.connect();

      if (httpConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
       inputStream = httpConn.getInputStream();
      }

        /*
         * Define InputStreams to read from the URLConnection.
         */
       // InputStream is = ucon.getInputStream();
        /*
         * Read bytes to the Buffer until there is nothing more to read(-1).
         */

        FileOutputStream fos = new FileOutputStream(file);
        int size = 1024*1024;
        byte[] buf = new byte[size];
        int byteRead;
        while (((byteRead = inputStream.read(buf)) != -1)) {
            fos.write(buf, 0, byteRead);
            bytesDownloaded += byteRead;
        }
        /* Convert the Bytes read to a String. */

        fos.close();

} catch(IOException io) {
    networkException = true;
    continueRestore = false;
} catch(Exception e) {   
    continueRestore = false;
    e.printStackTrace();
}

}


0

这段代码可以在不使用位图工厂的情况下下载图片,但无法在模拟器中运行,请使用任何安卓手机。

package com.example.filedownload;


import org.apache.http.util.ByteArrayBuffer;

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.content.*;
import android.app.*;
import android.net.*;
import android.app.DownloadManager.Request;
import android.os.Environment;
public class MainActivity extends Activity {
    public  long reference;
    BroadcastReceiver receiver;
    @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        Button button=(Button)findViewById(R.id.button1);
                button.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {


                                String file = "http://tmacfitness.com/wp-content/uploads/2013/04/Beauty-of-nature-random-4884759-1280-800.jpg";
                                String serviceString = Context.DOWNLOAD_SERVICE; 
                                DownloadManager downloadManager;
                                downloadManager = (DownloadManager)getSystemService(serviceString);
                                Uri uri = Uri.parse(file);
                                DownloadManager.Request request ;
                                request =  new Request(uri);
                                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,  "accel.jpg");   
                                reference = downloadManager.enqueue(request);



                }

        });

        IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
        receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
        long ref = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        if (reference == ref) {
        setContentView(R.layout.finalscreen);
        unregister();
        }
        }
        };
        registerReceiver(receiver, filter); 
    }
       public void unregister(){
           unregisterReceiver(receiver);

    }
}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <TableLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true" >

        <TableRow
            android:id="@+id/tableRow1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >
        </TableRow>

        <TableRow
            android:id="@+id/tableRow2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <CheckedTextView
                android:id="@+id/checkedTextView1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Accel" />

        </TableRow>

        <TableRow
            android:id="@+id/tableRow3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <Button
                android:id="@+id/button1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Button" />

        </TableRow>

        <TableRow
            android:id="@+id/tableRow4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >
        </TableRow>
    </TableLayout>

</RelativeLayout>

finalscreen.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="270dp"
        android:layout_height="wrap_content"
        android:layout_weight="2.12"
        android:text="DOWNLOAD COMPLETED" />

</LinearLayout>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接