MVP模式——Okhttp实现下载图片并带有进度 【Android Demo】

前言

MVP 是 MVC 的变种,其实是一种升级。要说 MVP 就要说说 MVC,在 MVC 中 Activity 其实是 View层级,但是通常在使用中 Activity即是View也是Controller,并没有将 View层 和 Controller层 进行分离, 耦合度大大提高,非常不利于项目的管理。这时候 MVP 就应运而生了。

核心思想

MVP:Model,View,Presenter
MVP 把 Activity 中的 UI逻辑 抽象成 View接口,把 业务逻辑 抽象成 Presenter接口,Model类 还是原来的 Model。在 MVP 模式中 Activity 的功能就是响应生命周期和显示界面,具体其他的工作都丢到了 Presenter层 中进行完成,Presenter 其实是 Model层 和 View层 的桥梁。

Demo

MVP 模式所做的事情很简单,就是将业务逻辑和视图逻辑抽象到接口中。

怎么理解呢,我们就根据此次要实现的下载功能,用代码说话。

定义Model,View,Presenter 接口

IDownloadModel

Model层也可以叫做数据提供层
在我们的下载任务中,业务逻辑只有一个,就是下载

public interface IDownloadModel {
    /**
     * 下载操作
     * @param url
     */
    void download(String url);
}

IDownloadView

View 接口定义所有需要实现的视图逻辑

public interface IDownloadView {
    /**
     * 显示进度条
     * @param show
     */
    void showProgressBar(boolean show);

    /**
     * 设置进度条进度
     * @param progress
     */
    void setProcessProgress(int progress);

    /**
     * 根据数据设置view
     * @param result
     */
    void setView(Bitmap result);

    /**
     * 设置请求失败时的view
     */
    void showFailToast();
}

IDownloadPresenter

因为在之前写的时候,定义了四个方法。后来考虑到model和presenter并不是双向持有,model不会 持有presenter,所以定义了一个主逻辑就是下载,在model中采用接口回调的方式通知presenter。

public interface IDownloadPresenter {
    /**
     * 下载
     *
     * @param url
     */
    void download(String url);

}

Model,View,Presenter 的具体实现

DownloadModel

采用了okhttp的异步get请求网络,因为回调方法是在子线程中的,所以做最好要回到主线程传递信息

public class DownloadModel implements IDownloadModel {


    private DownloadListener listener;
    private Handler mHandler = new Handler(Looper.getMainLooper());

    public DownloadModel(DownloadListener listener) {
        this.listener = listener;
    }

    @Override
    public void download(String url) {


        OkHttpClient okHttpClient = new OkHttpClient();

        Request request = new Request.Builder()
                .get().url(url).build();

        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        listener.failed();
                    }
                });
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                File file = new File("/data/data/com.example.mvp_okhttp", "hh.png");
                InputStream in = response.body().byteStream();
                long total = response.body().contentLength();
                WriteInputStreamUntils.WriteStreamToFile(in, file, total, listener);

                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        listener.downloadSuccess(file.getAbsolutePath());
                    }
                });
            }
        });

    }


    public interface DownloadListener {
        void downloadSuccess(String path);

        void failed();

        void refreshProgress(int progress);
    }


}

DownloadPresenter

在Presenter具体实现中,业务相关的操作由Model去完成(例如download),视图相关的操作由View去完成
(如setView等)。Presenter 作为桥梁的作用就这样体现出来了,巧妙的将View和Model的具体实现连接了起来。

public class DownloadPresenter implements IDownloadPresenter, DownloadModel.DownloadListener {


    private IDownloadView miDownloadView;
    private final IDownloadModel downloadModel;

    public DownloadPresenter(IDownloadView iDownloadView) {

        downloadModel = new DownloadModel(this);
        miDownloadView = iDownloadView;
    }

    @Override
    public void download(String url) {
        downloadModel.download(url);
    }

    @Override
    public void downloadSuccess(String result) {

        miDownloadView.showProgressBar(false);
        Bitmap bitmap = BitmapFactory.decodeFile(result);
        miDownloadView.setView(bitmap);
    }

    @Override
    public void failed() {
        miDownloadView.showProgressBar(false);
        miDownloadView.showFailToast();
    }

    @Override
    public void refreshProgress(int progress) {
        miDownloadView.showProgressBar(true);
        miDownloadView.setProcessProgress(progress);
    }


}

MainActivity

1.Button的click方法负责发起下载任务,但又不负责具体实现,而是由Presenter转接给Model去实现
2.Activity 什么时候显示ProgressDialog,什么时候显示Toast直接由Presenter告诉他,他只做一个View想做的事情
3.Activity里没有任何逻辑处理,所有的逻辑判断都在Model中完成了

public class MainActivity extends AppCompatActivity implements IDownloadView {

    private IDownloadPresenter downloadPresenter;
    private Button button;
    private ImageView imageView;


    private String path1="https://cdn.wallpaperhub.app/cloudcache/e/f/1/6/c/3/ef16c3d491975175f2fbb1e24415724371c1c119.png";
    private ProgressDialog progressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();

    }


    private void init() {
        downloadPresenter = new DownloadPresenter(this);
        button = (Button) findViewById(R.id.btn);
        imageView = (ImageView) findViewById(R.id.iv);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                downloadPresenter.download(path1);
            }
        });
        

        progressDialog = new ProgressDialog(this);
        progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                progressDialog.dismiss();
            }
        });
        progressDialog.setCanceledOnTouchOutside(false);
        progressDialog.setTitle("下载文件");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    }

    @Override
    public void showProgressBar(boolean show) {
        if (show){
            progressDialog.show();
        }else {
            progressDialog.dismiss();
        }
    }

    @Override
    public void setProcessProgress(int progress) {
        progressDialog.setProgress(progress);
    }

    @Override
    public void setView(Bitmap result) {
        imageView.setImageBitmap(result);
        Toast.makeText(MainActivity.this, "下载成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showFailToast() {
                Toast.makeText(MainActivity.this, "下载错误", Toast.LENGTH_SHORT).show();

            }
}

总结

MVP固然好用
优点:

  • 结构十分清晰,实现解耦
  • 方便单元测试
  • 避免Activity内存泄露

APP发生 OOM 的最大原因就是出现内存泄露造成APP的内存不够用,而造成内存泄露的两大原因之一就是 Activity泄露(Activity Leak)(另一个原因是 Bitmap泄露(Bitmap Leak))。

转自郭霖:Activity 是有生命周期的,用户随时可能切换 Activity,当APP的内存不够用的时候,系统会回收处于后台的Activity的资源以避免 OOM。
采用传统的模式,一大堆异步任务和对UI的操作都放在 Activity 里面,比如你可能从网络下载一张图片,在下载成功的回调里把图片加载到 Activity 的 ImageView 里面,所以异步任务保留着对 Activity 的引用。这样一来,即使 Activity 已经被切换到后台(onDestroy 已经执行),这些 异步任务 仍然保留着对 Activity 实例的引用, 所以系统就无法回收这个 Activity 实例了,结果就是 Activity Leak。
Android 的组件中,Activity 对象往往是在堆(Java Heap)里占最多内存的,所以系统会优先回收 Activity 对象, 如果有 Activity Leak,APP很容易因为内存不够而 OOM。
采用 MVP模式,只要在当前的 Activity 的 onDestroy 里,分离异步任务对Activity 的引用,就能避免 Activity Leak。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Kotlin 中使用 OkHttp3 下载文件并带有下载进度,可以通过以下步骤实现: 1. 添加 OkHttp3 依赖 在 app module 的 build.gradle 文件中添加以下代码: ``` dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.0' } ``` 2. 创建 OkHttp3 客户端 在代码中创建一个 OkHttpClient 客户端: ``` val client = OkHttpClient() ``` 3. 创建下载请求 使用 OkHttp3 的 Request.Builder 创建一个下载请求,并设置下载 URL 和保存文件的路径: ``` val request = Request.Builder() .url("https://example.com/file.zip") .build() ``` 4. 创建下载监听器 定义一个回调接口,用于监听下载进度: ``` interface DownloadListener { fun onDownloadProgress(progress: Int) } ``` 在代码中实现这个接口,并在其中更新下载进度,例如: ``` val listener = object : DownloadListener { override fun onDownloadProgress(progress: Int) { runOnUiThread { // 更新下载进度条 progressBar.progress = progress } } } ``` 5. 发起下载请求 使用 OkHttpClient 的 newCall 方法发起下载请求,并在 enqueue 方法中传入一个 Callback 参数,该参数将在下载完成时回调: ``` client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { // 下载失败 } override fun onResponse(call: Call, response: Response) { val inputStream: InputStream = response.body?.byteStream() ?: return // 保存文件并更新下载进度 val totalSize: Long = response.body?.contentLength() ?: -1 var downloadedSize: Long = 0 val outputStream = FileOutputStream("/storage/emulated/0/Download/file.zip") val buffer = ByteArray(8192) while (true) { val bytes = inputStream.read(buffer) if (bytes == -1) break outputStream.write(buffer, 0, bytes) downloadedSize += bytes val progress = (downloadedSize * 100 / totalSize).toInt() listener.onDownloadProgress(progress) } outputStream.close() inputStream.close() // 下载完成 } }) ``` 这段代码中,我们首先从 response.body 中获取输入流并创建输出流,然后使用循环逐段读取输入流的数据,再将其写入输出流,并计算下载进度,最后调用 DownloadListener 的 onDownloadProgress 方法更新下载进度。在下载完成后,我们需要关闭输入流和输出流,以及在 onFailure 方法中处理下载失败的情况。 6. 完整代码 最终的代码应该类似于这样: ``` interface DownloadListener { fun onDownloadProgress(progress: Int) } val client = OkHttpClient() val request = Request.Builder() .url("https://example.com/file.zip") .build() val listener = object : DownloadListener { override fun onDownloadProgress(progress: Int) { runOnUiThread { // 更新下载进度条 progressBar.progress = progress } } } client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { // 下载失败 } override fun onResponse(call: Call, response: Response) { val inputStream: InputStream = response.body?.byteStream() ?: return // 保存文件并更新下载进度 val totalSize: Long = response.body?.contentLength() ?: -1 var downloadedSize: Long = 0 val outputStream = FileOutputStream("/storage/emulated/0/Download/file.zip") val buffer = ByteArray(8192) while (true) { val bytes = inputStream.read(buffer) if (bytes == -1) break outputStream.write(buffer, 0, bytes) downloadedSize += bytes val progress = (downloadedSize * 100 / totalSize).toInt() listener.onDownloadProgress(progress) } outputStream.close() inputStream.close() // 下载完成 } }) ``` 注意,这段代码中保存文件的路径是硬编码的,你需要根据实际需求修改它。另外,为了更新 UI,我们需要在 onDownloadProgress 方法中使用 runOnUiThread 方法,以确保在主线程中执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值