從零實(shí)現(xiàn)ImageLoader(二)—— 基本實(shí)現(xiàn)

目錄

從零實(shí)現(xiàn)ImageLoader(一)—— 架構(gòu)
從零實(shí)現(xiàn)ImageLoader(二)—— 基本實(shí)現(xiàn)
從零實(shí)現(xiàn)ImageLoader(三)—— 線程池詳解
從零實(shí)現(xiàn)ImageLoader(四)—— Handler的內(nèi)心獨(dú)白
從零實(shí)現(xiàn)ImageLoader(五)—— 內(nèi)存緩存LruCache
從零實(shí)現(xiàn)ImageLoader(六)—— 磁盤緩存DiskLruCache

ImageLoader類

我們今天先從ImageLoader類入手,由于是鏈?zhǔn)降恼{(diào)用方式,ImageLoader以單例的方式實(shí)現(xiàn),下面是代碼:

public class ImageLoader {
    private static volatile ImageLoader mSingleton;
    private final Context mContext;

    private ImageLoader(Context context) {
        mContext = context;
    }

    public static ImageLoader with(Context context) {
        if(mSingleton == null) {
            synchronized (ImageLoader.class) {
                if(mSingleton == null) {
                    mSingleton = new ImageLoader(context);
                }
            }
        }
        return mSingleton;
    }
}

至于單例為什么要這么實(shí)現(xiàn),不清楚的同學(xué)可以看一下這篇文章:如何正確地寫出單例模式 | Jark's Blog。單例如何實(shí)現(xiàn),為什么要這么實(shí)現(xiàn),在這篇文章中都有詳細(xì)的介紹,這里就不再贅述了。

內(nèi)存泄露!

不過,上面的代碼看似沒有問題,但細(xì)心的同學(xué)可能已經(jīng)發(fā)現(xiàn)了,ImageLoader類持有了Context對(duì)象,而ImageLoader作為一個(gè)單例持有Context對(duì)象是很有可能造成內(nèi)存泄露的。

我們可以用LeakCanary檢測(cè)一下,使用起來也很簡單, 在build.gradle中加入以下代碼:

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.2'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.2'
 }

接著創(chuàng)建自己的Application類:

public class App extends Application {
  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

然后隨便在MainActivity的什么地方調(diào)用ImageLoader:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageLoader.with(this);
    }
}

現(xiàn)在只需要打開應(yīng)用、退出、再打開就會(huì)看到下面的畫面:

LeakCanary提示

這是LeakCanary在導(dǎo)出內(nèi)存信息,過一會(huì)等待LeakCanary分析完成就會(huì)出現(xiàn)一條通知:

LeakCanary通知

可以看到LeakCanary提示MainActivity產(chǎn)生內(nèi)存泄露了,點(diǎn)進(jìn)去有更詳細(xì)的情況:

MainActivity內(nèi)存泄露詳情

可以很明顯看到MainActivity的引用被ImageLoader的單例持有,由于單例的生命周期是伴隨整個(gè)應(yīng)用的,當(dāng)Activity調(diào)用onDestory()方法時(shí),依然被ImageLoader引用而無法釋放,這就造成了內(nèi)存泄露。

這給了我們一個(gè)很重要的警示:不要在單例中持有Activity對(duì)象的Context。

可這就產(chǎn)生了一個(gè)問題,我們必須要用Context,這可怎么辦呢?其實(shí)解決方法也很簡單,既然ImageLoader的生命周期是整個(gè)應(yīng)用,那我們使用生命周期同樣是整個(gè)應(yīng)用的ApplicationContext不就可以了嗎?于是代碼變成了這樣:

public class ImageLoader {
    private static volatile ImageLoader mSingleton;
    private final Context mContext;

    private ImageLoader(Context context) {
        //防止單例持有Activity的Context導(dǎo)致內(nèi)存泄露
        mContext = context.getApplicationContext();
    }

    public static ImageLoader with(Context context) {
        if(mSingleton == null) {
            synchronized (ImageLoader.class) {
                if(mSingleton == null) {
                    mSingleton = new ImageLoader(context);
                }
            }
        }
        return mSingleton;
    }
}

再打開應(yīng)用,已經(jīng)沒有內(nèi)存泄露了。

同步實(shí)現(xiàn)

解決了內(nèi)存泄露問題,接著實(shí)現(xiàn)其他功能。我們使用load(String url)傳入需要加載的圖片路徑:

public class ImageLoader {
    ...

    public Dispatcher load(String url) {
        return new Dispatcher(url);
    }
}

由于是鏈?zhǔn)秸{(diào)用,所以返回了Dispatcher類:

public class Dispatcher {
    private final String mUrl;
    public Bitmap get() throws IOException {
        URL realUrl = new URL(mUrl);
        HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
        try(InputStream in = connection.getInputStream()) {
            return BitmapFactory.decodeStream(in);
        } finally {
            connection.disconnect();
        }
    }
}

這里只做一個(gè)簡單的同步加載實(shí)現(xiàn),也就是get()方法,其他功能等之后再慢慢添加。

到這里,ImageLoader就已經(jīng)有了加載圖片的功能了,先測(cè)試一下:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageView imageView = findViewById(R.id.image);
        new Thread(() -> {
            Bitmap bitmap;
            try {
                bitmap = ImageLoader.with(this)
                        .load("https://i.redd.it/20mplvimm8ez.jpg")
                        .get();
                MainActivity.this.runOnUiThread(() -> {
                    imageView.setImageBitmap(bitmap);
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
效果圖

代碼優(yōu)化

上面的get()方法實(shí)現(xiàn)現(xiàn)在看起來好像沒什么問題,不過Dispatcher類是用來進(jìn)行線程切換以及緩存加載的,如果再將網(wǎng)絡(luò)下載放在這里,Dispatcher類就會(huì)顯得過于臃腫,為了保持Dispatcher類功能的單一性,這里選擇將網(wǎng)絡(luò)下載功能抽出來單獨(dú)做一個(gè)類:

public class NetworkUtil {
    private NetworkUtil() {}

    public static Bitmap getBitmap(String url) throws IOException {
        URL realUrl = new URL(url);
        HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
        try(InputStream in = connection.getInputStream()) {
            return BitmapFactory.decodeStream(in);
        } finally {
            connection.disconnect();
        }
    }
}

這樣做還有一個(gè)好處,那就是以后想要使用其他的網(wǎng)絡(luò)下載框架比如OkHttp或者Volley,只需要在NetworkUtil中修改而不影響其他類了。

現(xiàn)在,Dispatcher的get()方法也變得非常簡潔:

public class Dispatcher {
    private final String mUrl;
    public Bitmap get() throws IOException {
        return NetworkUtil.getBitmap(mUrl);
    }
}

總結(jié)

在這篇文章里我們實(shí)現(xiàn)了基本的同步加載,同時(shí)解決了Context的內(nèi)存泄露問題,下一篇,我們將要實(shí)現(xiàn)的是異步圖片加載。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容