如何使用Retrofit或Picasso通过HTTP POST方法下载图像

3

我有一个HTTP post请求,它需要一个JSON作为请求体,具体内容如下:

{
  "UserName":"ApiService", 
  "Password":"BandarAndroid",
  "AndroidId":"15df3b3a90XXXXX",
  "ContentId":"704",
  "frame":"1"
}

向服务器发送请求后,我只收到一张图片作为响应(而不是任何其他内容,比如JSON)。 这张图片是在请求时动态生成的,没有网址。 我的服务地址是:

http://xyz.website.com/api/DownloadFileForAndroid

我的响应头是:

cache-control →no-cache
content-length →29837
content-type →image/png
date →Mon, 09 Sep 2019 08:42:23 GMT
expires →-1
pragma →no-cache
server →Microsoft-IIS/8.5
x-aspnet-version →4.0.30319
x-powered-by →ASP.NET

我不知道是否应该使用Retrofit还是Picasso来获取这张照片。
在Picasso中:我不能在请求主体中发送JSON的数量。
在Retrofit中:如果没有URL(指向照片的地址,如www.site.com/a.jpg),我无法获取该照片。

如果您添加一个 OkHttpDownloader,我认为您可以使用 Picasso 来完成此操作。https://dev59.com/warka4cB1Zd3GeqPc2E2 - coroutineDispatcher
除非我没有理解你的问题,但是你期望的响应类型是什么?你的“content-type”响应是“image/png”,这是正常的。 - Harry Coder
我不知道是使用Retrofit还是Picasso来获取这张照片,@HarryCoder - sajjad Yosefi
Picasso不理解我想要的是post请求,它给出了一个错误405方法不允许get。@coroutineDispatcher - sajjad Yosefi
你能提供一个有效的URL和请求参数,以便我们检查并为您找到解决方案吗? - Roaim
显示剩余2条评论
2个回答

1
如您所请求,我正在将之前的解决方案(Kotlin)转换为 Java。

示例1:Picasso

public void loadImageWithPicasso(ImageView imageView) {
    Picasso.Builder builder = new Picasso.Builder(imageView.getContext());
    RequestCreator picassoImageLoader = createPicassoLoader(
            builder,
            ImageRequest.DEFAULT_JSON_BODY,
            "http://shop.atiafkar.ir/api/DownloadFileForAndroid"
    );
    picassoImageLoader.into(imageView);
}

public RequestCreator createPicassoLoader(Picasso.Builder builder, String body, String url) {
    return builder.downloader(new OkHttp3Downloader(createPicassoCallFactory(body)))
            .build()
            .load(url);
}

private okhttp3.Call.Factory createPicassoCallFactory(String jsonBody) {
    final OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .build();
    final RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), jsonBody);
    return new okhttp3.Call.Factory() {
        @Override
        public okhttp3.Call newCall(Request request) {
            Request.Builder builder = request.newBuilder();
            builder.post(requestBody);
            builder.addHeader("Content-Type", "application/json");
            return okHttpClient.newCall(builder.build());
        }
    };
}

例子 2:Retrofit

public void loadImageWithRetrofit(ImageView imageView) {
        final RetrofitImageLoader imageLoader = new RetrofitImageLoader(imageView);
        RemoteApi api = RemoteApi.Factory.create();

        api.getImage(ImageRequest.DEFAULT_BODY).enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                ResponseBody body = response.body();
                if (response.isSuccessful() && body != null) {
                    imageLoader.execute(body.byteStream());
                } else {
                    Log.d(TAG, "Retrofit onResponse(): CODE = [" + response.code() + "], MESSAGE = [" + response.message() + "]");
                }
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.d(TAG, "Retrofit onFailure(): t = [" + t + "]");
            }
        });
}

RetrofitImageLoader

public class RetrofitImageLoader extends AsyncTask<InputStream, Integer, Bitmap> {
        private ImageView imageView;

        private RetrofitImageLoader(ImageView imageView) {
            this.imageView = imageView;
        }

        @Override
        protected Bitmap doInBackground(InputStream... inputStreams) {
            return BitmapFactory.decodeStream(inputStreams[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            imageView.setImageBitmap(bitmap);
        }
}

远程API接口。
public interface RemoteApi {
    @Streaming  // Important
    @POST("/api/DownloadFileForAndroid")
    Call<ResponseBody> getImage(@Body ImageRequest body);

    class Factory {
        private static RemoteApi mInstance;

        public static RemoteApi create() {
            if (mInstance == null) {
                mInstance = new Retrofit.Builder()
                        .addConverterFactory(GsonConverterFactory.create())
                        .baseUrl("http://shop.atiafkar.ir")
                        .build()
                        .create(RemoteApi.class);
            }
            return mInstance;
        }
    }
}

ImageRequest 模型

public class ImageRequest{
    public static final ImageRequest DEFAULT_BODY;
    public static final String DEFAULT_JSON_BODY;

    static {
        DEFAULT_BODY = new ImageRequest();
        DEFAULT_BODY.setAndroidId("15df3b3a90dc5688");
        DEFAULT_BODY.setContentId("704");
        DEFAULT_BODY.setFrame("1");
        DEFAULT_BODY.setPassword("BandarAndroid");
        DEFAULT_BODY.setUserName("ApiService");

        DEFAULT_JSON_BODY = new Gson().toJson(DEFAULT_BODY, ImageRequest.class);
    }

    @SerializedName("UserName")
    private String userName;
    @SerializedName("ContentId")
    private String contentId;
    @SerializedName("AndroidId")
    private String androidId;
    @SerializedName("Password")
    private String password;
    @SerializedName("frame")
    private String frame;

    public void setUserName(String userName){
        this.userName = userName;
    }
    public void setContentId(String contentId){
        this.contentId = contentId;
    }
    public void setAndroidId(String androidId){
        this.androidId = androidId;
    }
    public void setPassword(String password){
        this.password = password;
    }
    public void setFrame(String frame){
        this.frame = frame;
    }
}

1
很高兴为您服务 :) - Roaim
如何解决此错误: 在调用Retrofit模式@Roaim时,使用JsonReader.setLenient(true)来接受第1行第1列路径$处的格式不正确的JSON。 - sajjad Yosefi
@sajjadYosefi 我认为你也可以使用square.okhttp。不过我不确定,你可以试一下。如果没有构建错误,那么你就可以开始了。 - Roaim
给定异常运行时,没有构建错误 @Roaim - sajjad Yosefi
在Retrofit的这一部分中: public void onFailure(Call<ResponseBody> call, Throwable t) { Log.d(TAG, "Retrofit onFailure(): t = [" + t + "]"); } - sajjad Yosefi
显示剩余6条评论

0
我不知道该使用Retrofit还是Picasso来获取这张照片。
最好使用Picasso,否则如果您使用Retrofit下载图片,您需要编写大量代码来高效地加载它们。
您会很高兴知道,您可以根据自己的选择使用Retrofit和Picasso来从API中加载图像。

在继续示例之前,我想澄清一件事情,你曾误解了需要将上述JSON数据作为标头发送,但是在尝试API后,我发现它将JSON作为请求主体


示例

RemoteApi.kt

interface RemoteApi {
    // Retrofit
    @Streaming  // Important
    @POST("/api/DownloadFileForAndroid")
    @Throws(Exception::class)
    suspend fun getImage(@Body body: ImageRequest): ResponseBody?

    companion object {
        // Retrofit
        fun create(): RemoteApi {
            val retrofit = Retrofit.Builder()
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl("http://shop.atiafkar.ir")
                    .build()
            return retrofit.create(RemoteApi::class.java)
        }
        // Picasso
        fun getPicassoImageRequest(
                builder : Picasso.Builder,
                body: String,
                url: String = "http://shop.atiafkar.ir/api/DownloadFileForAndroid"
        ) = builder.downloader(OkHttp3Downloader(getPicassoCallFactory(body)))
                        .build()
                        .load(url)
        // Picasso
        private fun getPicassoCallFactory(jsonBody : String): Call.Factory {
            return Call.Factory { request ->
                OkHttpClient().run {
                    RequestBody.create(MediaType.parse("application/json"), jsonBody).let {
                        newCall(request.newBuilder()
                                .post(it)
                                .addHeader("Content-Type", "application/json")
                                .build()
                        )
                    }
                }
            }
        }
    }
}

ImageRepository.kt

class ImageRepository(private val api: RemoteApi = RemoteApi.create()) {
    companion object {
        fun get() = ImageRepository()
    }
    // Retrofit
    suspend fun downloadImage(body : ImageRequest = ImageRequest.default): Bitmap? {
        return api.getImage(body)?.run {
            withContext(Dispatchers.IO) {
                bytes().let {
                    // to load bitmap efficiently follow the guideline provided by -
                    //  https://developer.android.com/topic/performance/graphics/load-bitmap
                    // otherwise you may experience OutOfMemoryException
                    BitmapFactory.decodeByteArray(it, 0, it.size)
                }
            }
        }
    }
    // Picasso
    fun getPicassoImageLoader(
            builder : Picasso.Builder,
            body: ImageRequest = ImageRequest.default
    ) = RemoteApi.getPicassoImageRequest(builder, body.toJson())
}

ImageViewModel.kt

class ImageViewModel(private val repository: ImageRepository) : ViewModel() {
    private val _progress = MutableLiveData<Boolean>()
    val progress = _progress
    // Retrofit
    val liveImage = liveData {
        _progress.value = true
        emit(repository.downloadImage())
        _progress.value = false
    }
    // Picasso
    fun getPicassoImageLoader(builder: Picasso.Builder) = repository.getPicassoImageLoader(builder)
}

最后,

ImageActivity.kt

class ImageActivity : AppCompatActivity() {
    private lateinit var dataBinding : ActivityImageBinding
    private val imageViewModel by lazy { ViewModelProviders.of(this, ImageViewModelFactory()).get(ImageViewModel::class.java) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_image)
        dataBinding.lifecycleOwner = this
        dataBinding.viewModel = imageViewModel
        // Retrofit
        imageViewModel.liveImage.observe(this, Observer {
            dataBinding.imageView.setImageBitmap(it)
        })
        // Picasso
        imageViewModel.getPicassoImageLoader(Picasso.Builder(this)).into(dataBinding.imageView2)
    }
}

ImageRequest.kt

data class ImageRequest(
        @field:SerializedName("UserName")
        val userName: String? = null,
        @field:SerializedName("ContentId")
        val contentId: String? = null,
        @field:SerializedName("AndroidId")
        val androidId: String? = null,
        @field:SerializedName("Password")
        val password: String? = null,
        @field:SerializedName("frame")
        val frame: String? = null
) {
    companion object {
        val default = ImageRequest(
                "ApiService",
                "704",
                "15df3b3a90dc5688",
                "BandarAndroid",
                "1"
        )
    }
}

fun ImageRequest.toJson() : String {
    return Gson().toJson(this, ImageRequest::class.java)
}

@Farid 感谢你的建议。很抱歉我写了一篇很长的回答。我会尝试让它更简洁,并记住这一点。但是,当你说我的回答完全无关时,我感到很失望。基本上,我认为如果我给他完整的代码,他就可以复制并运行,同时将代码分成几个部分,使其容易理解,这将有所帮助。但我想我错了。 - Roaim
可以的。你能否列出你难以理解的那些函数,让我把它们转换成Java?还是你想让我把所有东西都转换一遍? - Roaim
我对Kotlin一无所知,我不理解@Roaim的代码。 - sajjad Yosefi
@sajjadYosefi,我已经提交了另一个Java解决方案。请看一下,让我知道它是否解决了你的问题? - Roaim

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