Android Glide拡張による画像ロード進捗バーの実現

58515 ワード

Androidでは、現在主流のロード画像は基本的にGlideフレームワークを使用しています.このフレームワークは、ネットワークのロードとキャッシュ画像の様々な機能を自動的に処理しますが、画像のアドレスと表示位置を与えるだけでいいです.しかし、画像をロードする過程で現在のロードの進捗状況が分からないため、Glideを拡張する必要があります.同じように、まず効果を見てみましょう.
今から始めましょう
依存の追加
implementation 'com.github.bumptech.glide:glide:3.7.0'
implementation 'com.squareup.okhttp3:okhttp:3.9.0'

権限の追加
<uses-permission android:name="android.permission.INTERNET" />

Glideのネットワーク通信コンポーネントHTTPをOkHttpに置き換えます.OkHttpFetcherクラスを新規作成し、DataFetcherインタフェースを実装します.コードは次のとおりです.
package cn.xie.fingerprinttest.http;

import com.bumptech.glide.Priority;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.util.ContentLengthInputStream;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 *  Glide  HTTP       OkHttp。
 * @author xiejinbo
 * @date 2019/11/12 0012 11:43
 */
public class OkHttpFetcher implements DataFetcher<InputStream> {

        private final OkHttpClient client;
        private final GlideUrl url;
        private InputStream stream;
        private ResponseBody responseBody;
        private volatile boolean isCancelled;

        public OkHttpFetcher(OkHttpClient client, GlideUrl url) {
            this.client = client;
            this.url = url;
        }

        @Override
        public InputStream loadData(Priority priority) throws Exception {
            Request.Builder requestBuilder = new Request.Builder()
                    .url(url.toStringUrl());
            for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
                String key = headerEntry.getKey();
                requestBuilder.addHeader(key, headerEntry.getValue());
            }
            Request request = requestBuilder.build();
            if (isCancelled) {
                return null;
            }
            Response response = client.newCall(request).execute();
            responseBody = response.body();
            if (!response.isSuccessful() || responseBody == null) {
                throw new IOException("Request failed with code: " + response.code());
            }
            stream = ContentLengthInputStream.obtain(responseBody.byteStream(),
                    responseBody.contentLength());
            return stream;
        }

        @Override
        public void cleanup() {
            try {
                if (stream != null) {
                    stream.close();
                }
                if (responseBody != null) {
                    responseBody.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public String getId() {
            return url.getCacheKey();
        }

        @Override
        public void cancel() {
            isCancelled = true;
        }
}

次にOkHttpGlideUrlLoaderクラスを新規作成し、ModelLoaderを実装します.
package cn.xie.fingerprinttest.http;

import android.content.Context;

import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GenericLoaderFactory;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;

import java.io.InputStream;

import okhttp3.OkHttpClient;

/**
 *  Glide  HTTP       OkHttp。
 * @author xiejinbo
 * @date 2019/11/12 0012 11:45
 */
public class OkHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {

        private OkHttpClient okHttpClient;

        public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {

            private OkHttpClient client;

            public Factory() {
            }

            public Factory(OkHttpClient client) {
                this.client = client;
            }

            private synchronized OkHttpClient getOkHttpClient() {
                if (client == null) {
                    client = new OkHttpClient();
                }
                return client;
            }

            @Override
            public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
                return new OkHttpGlideUrlLoader(getOkHttpClient());
            }

            @Override
            public void teardown() {
            }
        }

        public OkHttpGlideUrlLoader(OkHttpClient client) {
            this.okHttpClient = client;
        }

        @Override
        public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
            return new OkHttpFetcher(okHttpClient, model);
        }

}

次に、MyGlideModuleクラスを新規作成し、GlideModuleインタフェースを実装し、registerComponents()メソッドで作成したOkHttpGlideUrlLoaderとOkHttpFetcherをGlideに登録し、元のHTTP通信コンポーネントを次のように置き換えます.
package cn.xie.fingerprinttest.http;

import android.content.Context;

import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.GlideModule;

import java.io.InputStream;

import okhttp3.OkHttpClient;

/**
 *  Glide  HTTP       OkHttp。
 * @author xiejinbo
 * @date 2019/11/12 0012 11:47
 */
public class MyGlideModule implements GlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.addInterceptor(new ProgressInterceptor());
        OkHttpClient okHttpClient = builder.build();
        glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory(okHttpClient));

    }
}

最後に、GlideがカスタマイズしたMyGlideModuleを認識できるようにするには、Android Manifestでなければなりません.xmlファイルのアプリケーションには、次の構成が含まれています.
<meta-data
            android:name="cn.xie.fingerprinttest.http.MyGlideModule"
            android:value="GlideModule" />

Httpコンポーネントとokhttpに置き換えられ、ダウンロードの傍受を実現しました.
ProgressInterceptorクラスを新規作成し、Interceptorインタフェースを実装します.コードは次のとおりです.
package cn.xie.fingerprinttest.http;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 * OkHttp            
 * @author xiejinbo
 * @date 2019/11/12 0012 11:49
 */
public class ProgressInterceptor implements Interceptor {

    static final Map<String, ProgressListener> LISTENER_MAP = new HashMap<>();

    public static void addListener(String url, ProgressListener listener) {
        LISTENER_MAP.put(url, listener);
    }

    public static void removeListener(String url) {
        LISTENER_MAP.remove(url);
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        String url = request.url().toString();
        ResponseBody body = response.body();
        Response newResponse = response.newBuilder().body(new ProgressResponseBody(url, body)).build();
        return newResponse;
    }

}

カスタムブロッキングが有効になり、ダウンロードの進捗リスニングの具体的なロジックを実装できます.まず、次のように、進捗リスニングコールバックのツールとして使用するProgressListenerインタフェースを新規作成します.
package cn.xie.fingerprinttest.http;

/**
 * @author xiejinbo
 * @date 2019/11/12 0012 13:06
 */
public interface ProgressListener {

    void onProgress(int progress);

}

ダウンロードの進捗状況の具体的な計算が実現しました.ProgressResponseBodyクラスを新規作成し、OkHttpのResponseBodyから継承させ、このクラスでダウンロードの進行状況を監視する論理を次のように記述する必要があります.
package cn.xie.fingerprinttest.http;

import android.util.Log;

import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;

/**
 *          
 * @author xiejinbo
 * @date 2019/11/12 0012 13:08
 */
public class ProgressResponseBody extends ResponseBody {

    private static final String TAG = "ProgressResponseBody";

    private BufferedSource bufferedSource;

    private ResponseBody responseBody;

    private ProgressListener listener;

    public ProgressResponseBody(String url, ResponseBody responseBody) {
        this.responseBody = responseBody;
        listener = ProgressInterceptor.LISTENER_MAP.get(url);
    }

    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(new ProgressSource(responseBody.source()));
        }
        return bufferedSource;
    }

    private class ProgressSource extends ForwardingSource {

        long totalBytesRead = 0;

        int currentProgress;

        ProgressSource(Source source) {
            super(source);
        }

        @Override
        public long read(Buffer sink, long byteCount) throws IOException {
            long bytesRead = super.read(sink, byteCount);
            long fullLength = responseBody.contentLength();
            if (bytesRead == -1) {
                totalBytesRead = fullLength;
            } else {
                totalBytesRead += bytesRead;
            }
            int progress = (int) (100f * totalBytesRead / fullLength);
            Log.d(TAG, "download progress is " + progress);
            if (listener != null && progress != currentProgress) {
                listener.onProgress(progress);
            }
            if (listener != null && totalBytesRead == fullLength) {
                listener = null;
            }
            currentProgress = progress;
            return bytesRead;
        }
    }

}

これまで、すべての車輪と建造が終わり、次は必要な場所の間で呼び出しました.
私たちはMainActivityにButtonを追加し、このBtnをクリックしたときに画像のロードを開始しました.次に、読み込んだ画像をImageViewで表示します.xmlレイアウト:
 <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onGlideLoadImage"
        android:text="      " />

    <ImageView
        android:id="@+id/show_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

MainActivityでロードプログレスバーのポップアップボックスを初期化します.ここでProgressDialogはロードの選択を簡単に表示するために使用されています.最新バージョンでは廃棄されています.現在のProgressBarを選択してロードの進捗を表示することができます.よりクールなロード効果がほしい場合は、ここを参照して、本題に戻ります.
private ProgressDialog progressDialog;
private ImageView loadImage;

loadImage = findViewById(R.id.show_image);
progressDialog = new ProgressDialog(this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("   ");

最後にロードボタンをクリックしてロードを開始します.
/**
     *               
     * @param view
     */
    public void onGlideLoadImage(View view) {
        final String url = "http://image14.m1905.cn/uploadfile/2018/0907/thumb_1_1380_460_20180907013518839623.jpg";
        ProgressInterceptor.addListener(url, new ProgressListener() {
            @Override
            public void onProgress(int progress) {
                try {
                    //                   
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.e(TAG, "progress: " + progress);
                progressDialog.setProgress(progress);
                /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    progressBar.setProgress(progress, true);
                } else {
                    progressBar.setProgress(progress);
                }*/


            }
        });
        Glide.with(this)
                .load(url)
                .diskCacheStrategy(DiskCacheStrategy.NONE)
                .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
                .into(new GlideDrawableImageViewTarget(loadImage) {
                    @Override
                    public void onLoadStarted(Drawable placeholder) {
                        super.onLoadStarted(placeholder);
                        progressDialog.show();
                        //progressDialog.dismiss();
                    }

                    @Override
                    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
                        super.onResourceReady(resource, animation);
                        progressDialog.dismiss();
                        ProgressInterceptor.removeListener(url);
                    }
                });
    }

これはとても良い車輪で、前の進捗モニタリングはすでにパッケージされていて、私たちは直接呼び出すことができます.最後に郭霖大神に感謝します.私は完全に郭霖大神の「第1行コード」を見てandroid世界に入りました.郭霖大神の:Android画像のロードフレームワークの最も完全な解析(7)を参考にして、進度のGlide画像のロード機能を実現します