如何在通过Picasso下载图像之前通过Content-Length和Content-Type验证响应头?

时间:2016-08-09 15:16:02

标签: java android http picasso okhttp3

我处于这样一种情况,我想在实际决定通过Picasso下载或不将图像下载到ImageView之前验证HTTP响应头。具体原因是没有其他方法让我能够判断出正确格式和正确尺寸的图像是否没有下载,只是为了意识到我必须抛弃它,因为它不符合标准集。

流程如下:

// 1. Ask a specific Profiles Service for a given user-id
// 2. Process the response and get the avatar URI from it
// 3. Call the avatar URI to validate the headers
if (validate(response.headers)) {
    // 4. Download avatar image using Picasso into given ImageView
} else {
    // 5. Don't download anything but show a placeholder image.
}

首先,我知道Picasso已经内置了功能来显示占位符图像,但不是URI不会返回任何图像的情况 - 它确实如此,但对我们来说这不是正确的。因此,该功能对我们没有帮助。

其次,我知道这指向API中的设计失败,我们必须绕过这样做以了解根据我们的指南是否有效的头像图像响应,但是Profiles Service并不特定于我们的解决方案但是更宽泛,因此这种“hack-y”方式是现在的方式

有关如何使用Picasso或URI的任何其他库来验证标头的任何想法,以便不会出于任何目的下载图像而不浪费带宽?

1 个答案:

答案 0 :(得分:0)

如果您想跳到答案,请转到下面列表中的第3点!

经过一段时间的挖掘,我尝试了几种方法,找到了一种完成上面列出的所有方法,即使它不是最干净的方式:

  

<强> 1。尝试向毕加索添加OkHttp3拦截器以在传递之前修改响应

我的第一个想法是在OkHttp中使用Interceptor,我们通常在发送之前修改/添加标头到请求,而不是修改响应 - 你如果可能的话,不应该这样做。

拦截器如下所示:

private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
    @Override public okhttp3.Response intercept(Chain chain) throws IOException {
        okhttp3.Response originalResponse = chain.proceed(chain.request());

        if (validate(originalResponse.headers()) {
            return originalResponse.newBuilder()
                   .body(null) //remove the body, stupid
                   .build();
        }
        return originalResponse;
    }
};

然后我尝试使用Jake Wharton的com.jakewharton.picasso:picasso2-okhttp3-downloader库将此拦截器添加到Picasso实例中,该库为Picasso 2.5.2和Retrofit 2.0.2添加了OkHttp3拦截器支持。

问题是拦截器从未被调用过,我怀疑(在研究它之后)可能会导致毕加索使用的OkHttp版本与我所依赖的版本不同。从来没有弄明白为什么它没有被调用(甚至其他拦截器),但我很快意识到这种方法背后的概念被打破了,我转向了。

  

<强> 2。尝试使用Retrofit 2发出HEAD请求,以便在使用毕加索拨打电话之前验证标头

决定我必须进行2次单独调用才能实现此目的: 1.在步骤2之前单独验证响应标头的HEAD请求。 2. GET请求下载包含图像的整个响应主体(如果步骤1已返回有效)

在我之前想要捆绑在一起的方法中,这两个步骤在概念上是错误的,因为主体会以任何方式下载,但只有在满足条件时才会被解雇,因此无论如何都会浪费带宽。

我计划使用Retrofit 2来实现第1步和Picasso 2.5来实现第2步。

由于为这样一个简单的调用实现一个Retrofit rest接口会带来很多开销,所以我很快就转向了这一点。

  

第3。使用OkHttp3进行HEAD调用以验证标题,然后使用URI调用Picasso

类似于方法#2。但是在使用Picasso将图像下载到给定的ImageView之前,我没有使用Retrofit对给定的URI进行head()调用来检查/验证标题,而是决定使用OkHttp3(这是Picasso和Retrofit的基础)顺便说一句。)

很快,我实现了一个AsyncTask,它会在给定的链接上运行一个简单的OkHttp head()调用,这将返回一个包含头像和相关链接的枚举类型的项目。此调用将在不同的线程上发生。

初始化另一个调用的线程,一旦它从OkHttp调用接收反馈(使用简单模式通知UI线程),它将处理该信息并在需要时启动Picasso调用。 / p>

以下是代码:

package com.example;

import android.net.Uri;

public class Avatar {
    AvatarType _avatarType;
    Uri _uri;

    public Avatar(AvatarType type, Uri uri) {
        _avatarType = type;
        _uri = uri;
    }

    public AvatarType getType() {
        return _avatarType;
    }

    public Uri getUri() {
        return _uri;
    }
}
package com.example;

public enum AvatarType {
    TYPE_X,
    TYPE_Y
}
package com.example;

import android.net.Uri;
import android.os.AsyncTask;

import java.io.IOException;

import okhttp3.OkHttpClient;
import okhttp3.Request;

public class AvatarDownloaderTask extends AsyncTask<String, String, Avatar> {

    private OnTaskCompleted _listener;

    public AvatarDownloaderTask(OnTaskCompleted listener){
        _listener = listener;
    }


    @Override
    protected Avatar doInBackground(String... params) {
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
                .url(params[0])
                .head()
                .build();

        try {
            okhttp3.Response response = client.newCall(request).execute();

            if (Integer.parseInt(response.header("Content-Length")) > 100) {
                return new Avatar(AvatarType.TYPE_Y, Uri.parse(params[0]));
            }
        } catch (IOException e) {
            cancel(true);
        }
        return new Avatar(AvatarType.TYPE_X, Uri.parse(params[0]));
    }

    @Override
    protected void onPostExecute(Avatar s) {
        super.onPostExecute(s);
        _listener.onTaskCompleted(s);
    }

    @Override
    protected void onCancelled(Avatar s) {
        super.onCancelled(s);
        _listener.onTaskCompleted(s);
    }

    public interface OnTaskCompleted{
        void onTaskCompleted(Avatar avatar);
    }
}
public class ProfileFragment extends Fragment implements AvatarDownloaderTask.OnTaskCompleted{

    /* -stripped out code- */

    // Use this call in your activity or fragment that also implements the OnTaskCompleted interface
    private void loadAvatar(String avatarUrl) {
        new AvatarDownloaderTask(this).execute(avatarUrl);
    }

    @Override
    public void onTaskCompleted(Avatar avatar) {
        switch (avatar.getType()) {
            case TYPE_X:
                _avatarProgressBar.setVisibility(View.GONE);
                _avatarImageView.setVisibility(View.GONE);
                _avatarImageViewPlaceholder.setVisibility(View.VISIBLE);
                break;
            case TYPE_Y:
                // Just a helper class with a shared Picasso instance
                ImageHandler.sharedHandler(getContext()).bypassImageResizer(avatar.getUri(), _avatarImageView, new com.squareup.picasso.Callback() {
                    @Override
                    public void onSuccess() {
                        _avatarProgressBar.setVisibility(View.GONE);
                        _avatarImageViewPlaceholder.setVisibility(View.GONE);
                        _avatarImageView.setVisibility(View.VISIBLE);
                    }

                    @Override
                    public void onError() {
                        _avatarProgressBar.setVisibility(View.GONE);
                        _avatarImageView.setVisibility(View.GONE);
                        _avatarImageViewPlaceholder.setVisibility(View.VISIBLE);
                    }
                });
                break;
        }
    }
}

这绝对不是一种干净的方法,强制我这样做的API设计开始时很糟糕。我很想听听别人的反馈,并建议如何改进这个/以另一种方式解决这个问题。 AsyncTask的任何其他轻量级方法?如何将数据传回主/ UI线程 - 可以修复/改进/消除吗?

干杯!