我需要将几个图像下载到目录,以便可以离线访问内容

时间:2015-11-09 13:23:56

标签: java android json caching android-glide

我得到一些包含一些食物菜单项的JSON数据

请注意:这只是一个示例,有时会有超过2张图片,而且阵列中有更多菜单项!

{
  "menu": [
    {
      "url": "/api/v1/menu/1",
      "name": "Best Food",
      "description": "really nice food",
      "opening_time": "every day from 9am to 6pm",
      "contact_email": "info@food.com",
      "tel_number": "+54 911 3429 5762",
      "website": "http://bestfood.com",
      "images": [
        {
          "url": "https://blahblah/image1.jpg"
        },
        {
          "url": "https://blahblah/image2.jpg"
        }
      ]
    },

  ]
}

每个项目都有一些信息和图像网址数组。

我正在使用Glide图像库来处理这些图像,并使用Retrofit 2.0从端点下载JSON数据。一切都很顺利。

但是,我需要存储下载的数据以供离线访问。

目前,我在现有模型上使用ORM Lite将所有JSON数据存储在数据库中。这部分还可以。

但是,在我的数据库中,我只存储图像URL,因为我被告知在数据库中存储图像(作为blob)不是好方法。

因此,我的应用中有一个部分可以查看已保存的菜单,如果用户选择,可以选择下载以供离线访问。

此时值得一提的是,我已经在数据库中拥有原始菜单信息,因为用户必须首先查看菜单以便在数据库中获取它。

但问题是图像。

这是我不知道如何继续的地方,但我列出了我正在考虑的解决方案和问题,并希望人们可以告诉我什么是最佳的行动方案。

  1. 使用服务下载图像。我觉得这是强制性的,因为我不知道会有多少图像,即使用户退出应用程序我也想继续下载

  2. Glide只提供图片下载选项,您可以在我阅读herehere时配置其缓存位置(内部私有或外部公开)。问题是我对设置缓存大小感到不舒服,因为我不知道需要什么。我想设置无限。

  3. 我需要能够删除保存的菜单数据,特别是如果它保存在外部公共目录中,因为在删除应用程序时不会删除该菜单数据,或者用户选择从内部删除保存的菜单该应用程序。我以为我可以将文件图像URI或整个保存菜单的位置存储在数据库中,但不确定这是否是一种好方法

  4. 我在不同的来源和答案中读到,在这个用例中只是将图像缓存到SD卡等,我应该专门使用网络库这样做,以避免将位图分配给堆内存。我目前在我的应用程序中使用OK HTTP。

7 个答案:

答案 0 :(得分:3)

我正在使用ormlite来存储带有网址的对象,我在我的应用上的“登录”屏幕后进行了同步,根据我的经验,我真的推荐这个库https://github.com/thest1/LazyList

这很简单:

player.leftKey(p1, evt)

此库使用外部sd上的url保存图像,其中包含有关内存问题的基本和简单配置,因此,如果您实际上有两个或更多具有相同URL的项目,则此库可以正常工作,url和imageView是参数,如果手机上没有图像,则开始新任务,并在下载完成后将图像放入视图中,并且此图书馆还会保存编码的图像,因此这些图片不会显示在图库中。 实际上,您只需要这些文件来实现库:https://github.com/thest1/LazyList/tree/master/src/com/fedorvlasov/lazylist

如果您想操作某些文件,可以在FileCache类中更改文件夹名称:

ImageLoader imageLoader=new ImageLoader(context);
imageLoader.DisplayImage(url, imageView);

其中“LazyList”是文件夹名称,您可以删除,移动等。 删除样本:

 public FileCache(Context context){
    //Find the dir to save cached images
    ...
        cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"LazyList");
    ...
}

现在我学到了更多关于内存缓存和堆内存分配的知识,第一次在线和离线操作图像,我推荐这个库,当你了解更多关于它的时候,你可以实现和编辑库满足您的需求。

答案 1 :(得分:2)

如果您希望即使用户退出也能继续下载,使用服务也是一个不错的选择。卸载应用程序时,将自动删除存储在使用getExternalStorageDirectory()创建的目录中的图像。此外,您可以检查内部存储器是否足够大以存储图像。如果您使用此方法,则在卸载应用程序时将删除这些图像。

答案 2 :(得分:2)

1:使用IntentService进行下载。

http://developer.android.com/reference/android/app/IntentService.html

2:使用AlarmManager设置您的IntentService,使其即使运行也能运行    应用程序未运行。您使用AlarmManager注册

http://developer.android.com/reference/android/app/AlarmManager.html

您可以通过多种方式启动AlarmManager    意图。

例如:    //注册首次运行,然后重复周期的间隔。                                                                                 alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,                 SystemClock.elapsedRealtime()+ DEFAULT_INITIAL_RUN,                 DEFAULT_RUN_INTERVAL,pi);

3:存储数据    这里有几个选项取决于您想要的公众    图片/数据。

http://developer.android.com/reference/android/os/Environment.html

示例:外部公共存储    文件dirBackup = Environment.getExternalStoragePublicDirectory(                                “YourDirectory”);

4:正在下载

你的选择在这里。您可以使用当前API中的任何内容    基本的URLConnection。

您可能需要查看:

http://developer.android.com/reference/android/app/DownloadManager.html

另外,请注意您需要添加的权限    和  

希望这能为您指明一个有用的方向。

答案 3 :(得分:2)

试用此library来管理图片加载。

  

使用服务下载图像。我觉得这是强制性的,因为   我不知道会有多少图像,我想要下载   即使用户退出应用程序也要继续

所有下载都在工作线程中完成,因此在应用程序进程处于活动状态时它仍处于活动状态。可能会出现问题:应用程序在加载过程中死亡。要解决此问题,我建议将AlarmManagerService结合使用。将其设置为以计时器开始,检查数据库或UIL缓存中是否有未加载的图像文件,然后重新开始加载。

  

Glide只提供图片下载选项,您可以进行配置   它的缓存所在的位置(内部私有或外部公共)就像我一样   在这里和这里阅读。问题是我对设置感到不舒服   缓存大小,因为我不知道需要什么。我想设置   无限制。

UIL有几个开箱即用的光盘缓存实现,包括无限制的。它还为您提供缓存接口,以便您可以实现自己的缓存接口。

  

我需要能够删除保存的菜单数据,特别是如果它   保存在外部公共目录中,因为这不会被删除   应用程序已删除等,或者如果用户选择从中删除已保存的菜单   在应用程序内。我以为我可以存储文件图像URI或   数据库中整个保存菜单的位置,但不确定是否   这是一个好方法

UIL使用提供的文件链接为每个加载的文件生成唯一的文件名。您可以使用JSON中的链接删除任何已加载的图像或取消任何下载。

  

我在不同的来源和答案中读到了这个用例   缓存图像到SD卡等,我应该专门使用   网络库这样做是为了避免将位图分配给堆   记忆。我目前在我的应用程序中使用OK HTTP。

UIL没事。它非常准确地管理内存,还为内存管理配置提供了几个选项。例如,您可以选择开箱即用的多个内存缓存实现。

总之,我建议您访问上面的链接并自己阅读库文档/描述。它非常灵活,并且具有许多有用的功能。

答案 4 :(得分:2)

我在下载图像时使用此类,它会缓存图像,下次下载它们时它只会从外部存储器加载,它也会为您管理缓存,因此您不必担心将缓存设置为有限或无限制,非常高效和快速。

public class ImageLoader {

MemoryCache memoryCache = new MemoryCache();
 FileCache fileCache;
private Map<ImageView, String> imageViews = Collections
        .synchronizedMap(new WeakHashMap<ImageView, String>());
ExecutorService executorService;
// Handler to display images in UI thread
Handler handler = new Handler();

public ImageLoader(Context context) {
    fileCache = new FileCache(context);
    executorService = Executors.newFixedThreadPool(5);
}

final int stub_id = R.drawable.placeholder;

public void DisplayImage(String url, ImageView imageView) {
    imageViews.put(imageView, url);



    Bitmap bitmap = memoryCache.get(url);
    if (bitmap != null)
        imageView.setImageBitmap(bitmap);
    else {
        queuePhoto(url, imageView);
        imageView.setImageResource(stub_id);
    }
}

private void queuePhoto(String url, ImageView imageView) {
    PhotoToLoad p = new PhotoToLoad(url, imageView);
    executorService.submit(new PhotosLoader(p));
}

private Bitmap getBitmap(String url) {
    File f = fileCache.getFile(url);

    Bitmap b = decodeFile(f);
    if (b != null)
        return b;

    // Download Images from the Internet
    try {
        Bitmap bitmap = null;
        URL imageUrl = new URL(url);
        HttpURLConnection conn = (HttpURLConnection) imageUrl
                .openConnection();
        conn.setConnectTimeout(30000);
        conn.setReadTimeout(30000);
        conn.setInstanceFollowRedirects(true);
        InputStream is = conn.getInputStream();
        OutputStream os = new FileOutputStream(f);
        Utils.CopyStream(is, os);
        os.close();
        conn.disconnect();
        bitmap = decodeFile(f);
        return bitmap;
    } catch (Throwable ex) {
        ex.printStackTrace();
        if (ex instanceof OutOfMemoryError)
            memoryCache.clear();
        return null;
    }
}

// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        FileInputStream stream1 = new FileInputStream(f);
        BitmapFactory.decodeStream(stream1, null, o);
        stream1.close();

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

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        FileInputStream stream2 = new FileInputStream(f);
        Bitmap bitmap = BitmapFactory.decodeStream(stream2, null, o2);
        stream2.close();
        return bitmap;
    } catch (FileNotFoundException e) {
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

// Task for the queue
private class PhotoToLoad {
    public String url;
    public ImageView imageView;

    public PhotoToLoad(String u, ImageView i) {
        url = u;
        imageView = i;
    }
}

class PhotosLoader implements Runnable {
    PhotoToLoad photoToLoad;

    PhotosLoader(PhotoToLoad photoToLoad) {
        this.photoToLoad = photoToLoad;
    }

    @Override
    public void run() {
        try {
            if (imageViewReused(photoToLoad))
                return;
            Bitmap bmp = getBitmap(photoToLoad.url);
            memoryCache.put(photoToLoad.url, bmp);
            if (imageViewReused(photoToLoad))
                return;
            BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
            handler.post(bd);
        } catch (Throwable th) {
            th.printStackTrace();
        }
    }
}

boolean imageViewReused(PhotoToLoad photoToLoad) {
    String tag = imageViews.get(photoToLoad.imageView);
    if (tag == null || !tag.equals(photoToLoad.url))
        return true;
    return false;
}

// Used to display bitmap in the UI thread
class BitmapDisplayer implements Runnable {
    Bitmap bitmap;
    PhotoToLoad photoToLoad;

    public BitmapDisplayer(Bitmap b, PhotoToLoad p) {
        bitmap = b;
        photoToLoad = p;
    }

    public void run() {
        if (imageViewReused(photoToLoad))
            return;
        if (bitmap != null)
            photoToLoad.imageView.setImageBitmap(bitmap);
        else
            photoToLoad.imageView.setImageResource(stub_id);
    }
}

public void clearCache() {
    memoryCache.clear();
    fileCache.clear();
}

}

使用它只需创建一个像

的实例
 ImageLoader Imageloaer = new ImageLoader(getBaseContext());
    Imageloaer.DisplayImage(imageUrl, imageView);

答案 5 :(得分:1)

你应该试着用这个下载,

  class DownloadFile extends AsyncTask<String,Integer,Long> {
    ProgressDialog mProgressDialog = new ProgressDialog(MainActivity.this);// Change Mainactivity.this with your activity name. 
    String strFolderName;
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mProgressDialog.setMessage("Downloading Image ...");
        mProgressDialog.setIndeterminate(false);
        mProgressDialog.setMax(100);
        mProgressDialog.setCancelable(false);
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.show();
    }
    @Override
    protected Long doInBackground(String... aurl) {
        int count;
        try {
            URL url = new URL((String) aurl[0]);
            URLConnection conexion = url.openConnection();
            conexion.connect();
            String targetFileName="downloadedimage.jpg";//Change name and subname

            int lenghtOfFile = conexion.getContentLength();
            String PATH = Environment.getExternalStorageDirectory()+"/myImage/";
            File folder = new File(PATH);
            if(!folder.exists()){
                folder.mkdir();//If there is no folder it will be created.
            }
            InputStream input = new BufferedInputStream(url.openStream());
            OutputStream output = new FileOutputStream(PATH+targetFileName);
            byte data[] = new byte[1024];
            long total = 0;
            while ((count = input.read(data)) != -1) {
                total += count;
                       publishProgress ((int)(total*100/lenghtOfFile));
                output.write(data, 0, count);
            }
            output.flush();
            output.close();
            input.close();
        } catch (Exception e) {}
        return null;
    }
    protected void onProgressUpdate(Integer... progress) {
         mProgressDialog.setProgress(progress[0]);
         if(mProgressDialog.getProgress()==mProgressDialog.getMax()){
            mProgressDialog.dismiss();

            Toast.makeText(getApplicationContext(), "Download Completed !", Toast.LENGTH_LONG).show();

         }
    }
    protected void onPostExecute(String result) {
    }
}

此代码可让您下载所有图片网址

  new DownloadFile().execute("https://i.stack.imgur.com/w4kCo.jpg");

.....

  <uses-permission android:name="android.permission.INTERNET"/>
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

根据需要更改文件夹名称,并尝试将此图像设置为app位图,并使用此功能避免图像旋转错误。

答案 6 :(得分:0)

这里要考虑的重要事项是

ThreadServiceFileJsonContextReceiver&amp; if else&amp; for

也许我不明白你的问题,但这不是什么大不了的先生, 你的应用程序以你的应用程序在os广播onBootCompleted时开始运行的方式工作,然后创建一个Thread,你将在那里做很多代码 - 获取你的json文件 - (当你需要时)它),因为它是一个数组,你得到你的jsonObject图像,无论是一千或几百万你只是迭代它并使用任何方法下载它,我说使用传统的方式下载你的图像所以因为你更好地控制它。 在File课程的帮助下,您将其与Context一起保存,您可以获取应用程序的缓存目录,这是一个内部存储器,将其保存在那里并在数据库中创建一个列您可以在其中将数据库的文件路径保存为String。

当您的应用程序在onPrepareOptionsMenu()开始时,请检查您应用的缓存目录是否为空 - 如果没有,您有一些文件,现在因为您有每个文件及其各自的路径,您可以检查如果它不存在,则File.exist()存在。

如果你需要节奏,你总是可以创建新的线程。 Reciever是在你的设备启动时得到通知的人,如果需要进行大量的逻辑检查,for用于你的循环,服务能够做长时间运行的工作并有办法在UI和后台线程之间进行通信。

对不起我刚试图购买空间的最后一段:)