Android圖片加載的緩存類(lèi)
本文為大家分享了Android圖片加載的緩存類(lèi),供大家參考,具體內(nèi)容如下
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Handler;
import android.text.TextUtils;
/**
* 圖片加載器,主要功能是從網(wǎng)絡(luò)中下載圖片并緩存。這里之所以另寫(xiě)一個(gè)功能類(lèi)似重復(fù)的原因是 之前舊的圖片加載邏輯感覺(jué)非常復(fù)雜,我這里寫(xiě)個(gè)輕量級(jí)的
*
* @author H3c
*
*/
public class ImageLoaderEngine {
public static final int LOAD_IMG_SUCCESS = 2010;
private final int MAX_CAPACITY = Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1 ? 50 : 10;// 一級(jí)緩存緩存圖片數(shù)
private static ImageLoaderEngine instance;
private static Handler mHandler;
private ExecutorService pool;// 后臺(tái)線(xiàn)程池
// 這里用LinkedHashMap不用LruCache的原因是LruCache直接申請(qǐng)內(nèi)存大小而不是圖片個(gè)數(shù)。此App已經(jīng)有一個(gè)全局的LruCache了,重復(fù)申請(qǐng)內(nèi)存大小對(duì)應(yīng)用不利
private LinkedHashMap<String, Bitmap> mFirstLevelCache;// <momentId>一級(jí)緩存,硬鏈接bitmap,只保留最近用的圖片。
private ConcurrentHashMap<String, SoftReference<Bitmap>> mSecondLevelCache;// <momentId>
public static ImageLoaderEngine getInstance(Handler handler) {
if (instance == null) {
instance = new ImageLoaderEngine();
}
if(handler != null) {
mHandler = handler;
}
return instance;
}
private ImageLoaderEngine() {
pool = Executors.newFixedThreadPool(4);// 默認(rèn)線(xiàn)程池大小為6
initCache();
}
private void initCache() {
mFirstLevelCache = new LinkedHashMap<String, Bitmap>(MAX_CAPACITY / 2,
0.75f, true) {
private static final long serialVersionUID = 1L;
protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
if (size() > MAX_CAPACITY) {// 超過(guò)一級(jí)緩存大小后會(huì)挪到二級(jí)緩存中
mSecondLevelCache.put(eldest.getKey(),
new SoftReference<Bitmap>(eldest.getValue()));
return true;
}
return false;
};
};
mSecondLevelCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>();// <momentId>
}
/**
* 移除緩存
* @param key
*/
public void deleteCacheByKey(String key) {
String sdCacheingPath = IOHelper.getCachedPicturePath(
Global.packageName, key);
String sdCacheedPath = sdCacheingPath +".png";
File file = new File(sdCacheingPath);
if(file.exists()) {
file.delete();
}
file = new File(sdCacheedPath);
if(file.exists()) {
file.delete();
}
mFirstLevelCache.remove(key);
mSecondLevelCache.remove(key);
}
/**
* 釋放資源
*/
public void recycleImageLoader() {
new Thread(new Runnable() {
@Override
public void run() {
if (pool != null) {
pool.shutdownNow();
}
if (mFirstLevelCache != null) {
for (Bitmap bmp : mFirstLevelCache.values()) {
if (bmp != null) {
bmp.recycle();
bmp = null;
}
}
mFirstLevelCache.clear();
mFirstLevelCache = null;
}
if (mSecondLevelCache != null) {
mSecondLevelCache.clear();
}
mHandler = null;
}
}).start();
}
/**
* 后臺(tái)請(qǐng)求圖片
*
* @param item
*/
public void loadImageByMoment(final NMoment moment,String photoTag) {
if (moment.isPicture()
|| moment.isVideo()) {
String id = moment.id + photoTag;
loadImageByUrl(id+"", moment.getPicture(Global.widthPixels/3*2),moment.orientation);
}
}
/**
* 后臺(tái)請(qǐng)求圖片
* @param key
* @param url
*/
public void loadImageByUrl(final String key,final String url,final int orientation) {
pool.submit(new Runnable() {
public void run() {
LogHelper.e("ImageLoaderEngine","從網(wǎng)絡(luò)中下載");
// 如果內(nèi)存中有就算了
if (mFirstLevelCache.get(key) != null
|| mSecondLevelCache.get(key) != null) {// 如果圖片已經(jīng)緩存了
LogHelper.e("ImageLoaderEngine","下載圖片錯(cuò)誤 1");
return;
}
// 如果SD卡緩存中有就算了
final String sdCacheingPath = IOHelper.getCachedPicturePath(
Global.packageName, key);
File cacheingFile = new File(sdCacheingPath);
if (cacheingFile.exists()) {// 如果正在緩存就算了
long currentTime = System.currentTimeMillis();
if((currentTime - cacheingFile.lastModified()) >2 * 60 * 1000) {
LogHelper.e("ImageLoaderEngine","2分鐘都還沒(méi)下載完,準(zhǔn)備刪除它.."+currentTime+"="+cacheingFile.lastModified());
cacheingFile.delete();
} else {
getBitmapFromNetworkAndAddToMemory(url, key, orientation);
LogHelper.e("ImageLoaderEngine","第二次進(jìn)來(lái)應(yīng)該走這里..");
return;
}
}
String sdCacheedPath = sdCacheingPath + ".png";// 緩存完成后會(huì)改名字,否則會(huì)導(dǎo)致緩存錯(cuò)誤,圖片變黑
File cacheedFile = new File(sdCacheedPath);
if (cacheedFile.exists()) {// 如果緩存了就算了
LogHelper.e("ImageLoaderEngine","下載圖片錯(cuò)誤 2");
return;
}
getBitmapFromNetworkAndAddToMemory(url, key, orientation);
}
});
}
private void getBitmapFromNetworkAndAddToMemory(String url,String key,int orientation) {
Bitmap bmp = getBitmapFromUrl(url);
if(bmp!= null) {
LogHelper.e("ImageLoaderEngine","下載網(wǎng)絡(luò)圖片成功");
if(key.endsWith("_DetailDaily")) {
bmp = scaledBitmap(bmp, Global.getThumbWidth());
}
if(orientation != 0) {
mFirstLevelCache.put(key, ViewHelper.rotateBitmap(orientation, bmp));// 從網(wǎng)絡(luò)下載后直接顯示
} else {
mFirstLevelCache.put(key, bmp);// 從網(wǎng)絡(luò)下載后直接顯示
}
if (mHandler != null) {
mHandler.removeMessages(LOAD_IMG_SUCCESS);
mHandler.sendEmptyMessageDelayed(
LOAD_IMG_SUCCESS, 600);// 延時(shí)提示沒(méi)有數(shù)據(jù)了
}
final String sdCacheingPath = IOHelper.getCachedPicturePath(
Global.packageName, key);
saveBitmapToFile(sdCacheingPath, bmp);
} else {
LogHelper.e("ImageLoaderEngine","下載網(wǎng)絡(luò)圖片失敗...");
}
}
/**
* 直接從網(wǎng)絡(luò)中獲取
* @param url
* @return
*/
public Bitmap getBitmapFromUrl(String url) {
URL myFileUrl = null;
Bitmap bitmap = null;
InputStream is = null;
try {
if (!UIUtils.isNetworkAvailable(MyApplication.getInstance())) {
return null;
}
myFileUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) myFileUrl
.openConnection();
conn.setDoInput(true);
conn.connect();
is = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
} catch (Exception e) {
try {
if(is != null) {
is.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
return bitmap;
}
public Bitmap getImageInMemory(NMoment moment) {
return getImageInMemory(moment, "");
}
/**
* 新增接口,可以根據(jù)tag重新標(biāo)識(shí)Moment,這樣可以擴(kuò)展應(yīng)用場(chǎng)景,比如首頁(yè)需要大圖,進(jìn)入相集頁(yè)需要小圖
* @param moment
* @param photoTag
* @return
*/
public Bitmap getImageInMemory(NMoment moment, String photoTag) {
String id = moment.id + photoTag;
Bitmap bmp = null;
// 1. 從一級(jí)緩存中獲取
bmp = getFromFirstLevelCache(id);
if (bmp != null && !bmp.isRecycled()) {
LogHelper.e("ImageLoaderEngine","一級(jí)緩存獲取:"+id);
return bmp;
}
// 2. 從二級(jí)緩存中獲取
bmp = getFromSecondLevelCache(id);
if (bmp != null && !bmp.isRecycled()) {
LogHelper.e("ImageLoaderEngine","二級(jí)緩存獲取:"+id);
return bmp;
}
if(bmp != null && bmp.isRecycled()) {
return null;
} else {
return bmp;
}
}
public void setImage(String key,Bitmap picture) {
mFirstLevelCache.put(key, picture);
}
/**
* 獲取圖片
*/
public Bitmap getImage(NMoment moment) {
return getImage(moment, "");
}
public Bitmap getImage(NMoment moment, String photoTag) {
String id = moment.id + photoTag;
Bitmap bmp = null;
// 1. 從一級(jí)緩存中獲取
bmp = getFromFirstLevelCache(id);
if (bmp != null && !bmp.isRecycled()) {
LogHelper.e("ImageLoaderEngine","一級(jí)緩存獲取:"+id);
return bmp;
}
// 2. 從二級(jí)緩存中獲取
bmp = getFromSecondLevelCache(id);
if (bmp != null && !bmp.isRecycled()) {
LogHelper.e("ImageLoaderEngine","二級(jí)緩存獲取:"+id);
return bmp;
}
// 3. 從SD卡緩存中獲取
bmp = getFromSDCache(moment, photoTag);
if (bmp != null && !bmp.isRecycled()) {
LogHelper.e("ImageLoaderEngine","SD卡緩存獲取:"+id);
return bmp;
}
// 4. 從網(wǎng)絡(luò)中獲取
loadImageByMoment(moment, photoTag);
// LogHelper.e("ImageLoaderEngine","本地獲取圖片失敗:"+moment.id+"="+moment.getPicture());
if(bmp != null && bmp.isRecycled()) {
return null;
} else {
return bmp;
}
}
public Bitmap getImage(String key,String url) {
Bitmap bmp = null;
// 1. 從一級(jí)緩存中獲取
bmp = getFromFirstLevelCache(key);
if (bmp != null && !bmp.isRecycled()) {
return bmp;
}
// 2. 從二級(jí)緩存中獲取
bmp = getFromSecondLevelCache(key);
if (bmp != null && !bmp.isRecycled()) {
return bmp;
}
// 3. 從SD卡緩存中獲取
bmp = getFromSDCacheByKey(key,0);
if (bmp != null && !bmp.isRecycled()) {
return bmp;
}
// 4. 從網(wǎng)絡(luò)中獲取
loadImageByUrl(key, url,0);
if(bmp != null && bmp.isRecycled()) {
return null;
} else {
return bmp;
}
}
/**
* 一級(jí)緩存獲取圖片
*
* @param imgId
* @return
*/
private Bitmap getFromFirstLevelCache(String imgId) {
Bitmap bitmap = null;
synchronized (mFirstLevelCache) {
bitmap = mFirstLevelCache.get(imgId);
if (bitmap != null) {
mFirstLevelCache.remove(imgId);
mFirstLevelCache.put(imgId, bitmap);
}
}
return bitmap;
}
/**
* 二級(jí)緩存獲取圖片
*
* @param url
* @return
*/
private Bitmap getFromSecondLevelCache(String imgId) {
Bitmap bitmap = null;
SoftReference<Bitmap> softReference = mSecondLevelCache.get(imgId);
if (softReference != null) {
bitmap = softReference.get();
if (bitmap == null) {
mSecondLevelCache.remove(imgId);
}
}
return bitmap;
}
/**
* 從SD卡緩存獲取圖片,并放入一級(jí)緩存中
*
* @param moment
* @return
* @throws IOException
*/
private Bitmap getFromSDCache(final NMoment moment,final String photoTag) {
Bitmap drawable = null;
String id = moment.id + photoTag;
String sdCacheingPath = IOHelper.getCachedPicturePath(Global.packageName,
id);
String sdCacheedPath = sdCacheingPath + ".png";
if(moment.isLocal){
if(moment.isVideo()) {
//獲取本地路徑
sdCacheedPath = moment.getPicture(Global.widthPixels/3*2);
} else {
sdCacheedPath = moment.local_res_path;
}
}
File cacheFile = new File(sdCacheedPath);
if (!cacheFile.exists()) {// 如果沒(méi)有緩存完成就退出
LogHelper.e("ImageLoaderEngine","找不到緩存文件:"+sdCacheedPath);
if(!TextUtils.isEmpty(moment.local_res_path)) {// 如果本地有圖片,就先用本地圖片代替
sdCacheedPath = moment.local_res_path;
cacheFile = new File(sdCacheedPath);
if (cacheFile.exists() && !GlobalData.PHONE_MANUFACTURER.equalsIgnoreCase("samsung")) {
LogHelper.e("ImageLoaderEngine","AK47...:"+GlobalData.PHONE_MANUFACTURER);// 先從本地找替代圖片..
new Thread(new Runnable() {// 從網(wǎng)絡(luò)下載
@Override
public void run() {
loadImageByMoment(moment, photoTag);
}
}).start();
return getFitPhoto(sdCacheedPath, moment, cacheFile);
} else {
return null;
}
} else {
return null;
}
}
drawable = getFitPhoto(sdCacheedPath, moment, cacheFile);
if (drawable != null) {
if (moment.orientation != 0) {
drawable = ViewHelper
.rotateBitmap(moment.orientation, drawable);
}
if(mFirstLevelCache != null) {
mFirstLevelCache.put(id, drawable);
}
} else {
cacheFile.delete();
}
return drawable;
}
private Bitmap getFitPhoto(String sdCacheedPath,NMoment moment,File cacheFile) {
FileInputStream fs = null;
Bitmap result;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(sdCacheedPath, options);
int hRatio = (int) Math.ceil(options.outHeight
/ (float) moment.picture_height); // 算高度
int wRatio = (int) Math.ceil(options.outWidth
/ (float) Global.widthPixels); // 算寬度
if (hRatio > 1 || wRatio > 1) {
if (hRatio > wRatio) {
options.inSampleSize = hRatio;
} else
options.inSampleSize = wRatio;
}
options.inPurgeable = true;
options.inInputShareable = true;
options.inDither = false;
options.inJustDecodeBounds = false;
try {
fs = new FileInputStream(cacheFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
result = BitmapFactory.decodeFileDescriptor(fs.getFD(), null,
options);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (fs != null) {
try {
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
private Bitmap getFromSDCacheByKey(String key,int orientation) {
Bitmap drawable = null;
FileInputStream fs = null;
String sdCacheedPath = IOHelper.getCachedPicturePath(
Global.packageName, key) + ".png";
File cacheFile = new File(sdCacheedPath);
if (!cacheFile.exists()) {// 如果沒(méi)有緩存完成就退出
return null;
}
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(sdCacheedPath, options);
int wRatio = (int) Math.ceil(options.outWidth
/ (float) Global.widthPixels); // 算寬度
options.inSampleSize = wRatio;
options.inPurgeable = true;
options.inInputShareable = true;
options.inDither = false;
options.inJustDecodeBounds = false;
try {
fs = new FileInputStream(cacheFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
drawable = BitmapFactory.decodeFileDescriptor(fs.getFD(), null,
options);
if (drawable != null) {
if(orientation != 0) {
drawable = ViewHelper.rotateBitmap(orientation, drawable);
}
mFirstLevelCache.put(key, drawable);
} else {
cacheFile.delete();
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (fs != null) {
try {
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return drawable;
}
/**
* 創(chuàng)建一個(gè)灰色的默認(rèn)圖
* @param moment
* @return
*/
public Bitmap getDefaultBitmap(NMoment moment) {
return ImageHelper.createBitmap(moment.picture_width, moment.picture_height,
R.color.image_bg_daily);
}
/**
* 保存Bitmap文件到sd卡,傳入jpg結(jié)尾的路徑
* @param filePath
* @param mBitmap
*/
public void saveBitmapToFile(String filePath, Bitmap mBitmap) {
try {
File file = new File(filePath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
if (file.exists() && file.length() > 0) {
long currentTime = System.currentTimeMillis();
if ((currentTime - file.lastModified()) > 2 * 60 * 1000) {
LogHelper.e("ImageLoaderEngine",
"2分鐘都還沒(méi)下載完,準(zhǔn)備刪除它.." + currentTime + "="
+ file.lastModified());
file.delete();
} else {
return;
}
} else {
file.createNewFile();
}
FileOutputStream fOut = null;
fOut = new FileOutputStream(file);
mBitmap.compress(Bitmap.CompressFormat.JPEG, 80, fOut);
fOut.flush();
fOut.close();
file.renameTo(new File(filePath+".png"));
} catch (Exception e) {
e.printStackTrace();
LogHelper.e("ImageLoaderEngine","保存圖片錯(cuò)誤:"+e);
}
LogHelper.e("ImageLoaderEngine","保存網(wǎng)絡(luò)圖片成功"+filePath+".png");
}
/**
* 保存文件至緩存,這里重寫(xiě)而不用IOHelper里面的原因是IOHelper里面過(guò)于復(fù)雜
*
* @param url
* @param filePath
* @return
*/
public boolean saveUrlBitmapToFile(String url, String filePath) {
if (TextUtils.isEmpty(filePath)) {
return false;
}
File iconFile = new File(filePath);
if (iconFile.getParentFile() == null) {
return false;
}
if (!iconFile.getParentFile().exists()) {
iconFile.getParentFile().mkdirs();
}
if (iconFile.exists() && iconFile.length() > 0) {
long currentTime = System.currentTimeMillis();
if((currentTime - iconFile.lastModified()) >2 * 60 * 1000) {
LogHelper.e("ImageLoaderEngine","2分鐘都還沒(méi)下載完,準(zhǔn)備刪除它.."+currentTime+"="+iconFile.lastModified());
iconFile.delete();
} else {
return true;
}
}
FileOutputStream fos = null;
InputStream is = null;
try {
fos = new FileOutputStream(filePath);
is = new URL(url).openStream();
int data = is.read();
while (data != -1) {
fos.write(data);
data = is.read();
}
} catch (IOException e) {
LogHelper.e("ImageLoaderEngine", "ImageLoaderEngine 下載圖片錯(cuò)誤" + e);
iconFile.delete();
e.printStackTrace();
return false;
} finally {
try {
if (is != null) {
is.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
iconFile.renameTo(new File(filePath+".png"));
return true;
}
/**
* 縮放bitmap
* @param bmp
* @param scaledValue縮放值
* @return
*/
public Bitmap scaledBitmap(Bitmap bmp,int scaledValue) {
int bmpWidth = bmp.getWidth();
int bmpHeight = bmp.getHeight();
if(bmpWidth >= bmpHeight) {// 橫圖
bmpWidth = (bmpWidth * scaledValue / bmpHeight);
bmpHeight = scaledValue;
} else {
bmpHeight = (bmpHeight * scaledValue / bmpWidth);
bmpWidth = scaledValue;
}
Bitmap scaledBmp = Bitmap.createScaledBitmap(bmp,bmpWidth,bmpHeight,true);
bmp.recycle();
bmp = null;
return scaledBmp;
}
}
以上就是一個(gè)完整的Android圖片加載緩存類(lèi),希望對(duì)大家的學(xué)習(xí)有所幫助。
- Android中使用Bitmap類(lèi)將矩形圖片轉(zhuǎn)為圓形的方法
- 非常實(shí)用的Android圖片工具類(lèi)
- Android開(kāi)發(fā)之多媒體文件獲取工具類(lèi)實(shí)例【音頻,視頻,圖片等】
- Android開(kāi)發(fā)之圖片壓縮工具類(lèi)完整實(shí)例
- Android開(kāi)發(fā)實(shí)現(xiàn)的IntentUtil跳轉(zhuǎn)多功能工具類(lèi)【包含視頻、音頻、圖片、攝像頭等操作功能】
- Android開(kāi)發(fā)之超強(qiáng)圖片工具類(lèi)BitmapUtil完整實(shí)例
- Android圖片處理工具類(lèi)BitmapUtils
- Android開(kāi)發(fā)之圖片切割工具類(lèi)定義與用法示例
- Android編程圖片加載類(lèi)ImageLoader定義與用法實(shí)例分析
- Android編程圖片操作類(lèi)定義與用法示例【拍照,相冊(cè)選圖及裁剪】
相關(guān)文章
Android實(shí)現(xiàn)萬(wàn)能自定義陰影控件實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于Android實(shí)現(xiàn)萬(wàn)能自定義陰影控件的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Android中Glide實(shí)現(xiàn)超簡(jiǎn)單的圖片下載功能
本篇文章主要介紹了Android中Glide實(shí)現(xiàn)超簡(jiǎn)單的圖片下載功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03
android中ViewPager結(jié)合Fragment進(jìn)行無(wú)限滑動(dòng)
本篇文章中主要介紹了android中ViewPager結(jié)合Fragment進(jìn)行無(wú)限滑動(dòng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03
Flutter啟動(dòng)頁(yè)(閃屏頁(yè))的具體實(shí)現(xiàn)及原理詳析
這篇文章主要給大家介紹了關(guān)于Flutter啟動(dòng)頁(yè)(閃屏頁(yè))的具體實(shí)現(xiàn)及原理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
基于Android自定義控件實(shí)現(xiàn)刮刮樂(lè)效果
這篇文章主要介紹了基于Android自定義控件實(shí)現(xiàn)刮刮樂(lè)效果 的相關(guān)資料,需要的朋友可以參考下2015-12-12
android原生實(shí)現(xiàn)多線(xiàn)程斷點(diǎn)續(xù)傳功能
這篇文章主要為大家詳細(xì)介紹了android原生實(shí)現(xiàn)多線(xiàn)程斷點(diǎn)續(xù)傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
幾個(gè)Android編程時(shí)需要注意的 web 問(wèn)題
這篇文章主要介紹了幾個(gè)Android編程時(shí)需要注意的 web 問(wèn)題,需要的朋友可以參考下2014-12-12
Retrofit 創(chuàng)建網(wǎng)絡(luò)請(qǐng)求接口實(shí)例過(guò)程
這篇文章主要為大家介紹了Retrofit 創(chuàng)建網(wǎng)絡(luò)請(qǐng)求接口實(shí)例過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Android自定義view實(shí)現(xiàn)太極效果實(shí)例代碼
這篇文章主要介紹了Android自定義view實(shí)現(xiàn)太極效果實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-05-05

