android中圖片的三級緩存cache策略(內(nèi)存/文件/網(wǎng)絡(luò))
更新時間:2013年06月08日 15:34:39 作者:
實現(xiàn)圖片緩存也不難,需要有相應(yīng)的cache策略。這里我采用 內(nèi)存-文件-網(wǎng)絡(luò) 三層cache機制,其中內(nèi)存緩存包括強引用緩存和軟引用緩存(SoftReference),其實網(wǎng)絡(luò)不算cache,這里姑且也把它劃到緩存的層次結(jié)構(gòu)中
1.簡介
現(xiàn)在android應(yīng)用中不可避免的要使用圖片,有些圖片是可以變化的,需要每次啟動時從網(wǎng)絡(luò)拉取,這種場景在有廣告位的應(yīng)用以及純圖片應(yīng)用(比如百度美拍)中比較多。
現(xiàn)在有一個問題:假如每次啟動的時候都從網(wǎng)絡(luò)拉取圖片的話,勢必會消耗很多流量。在當(dāng)前的狀況下,對于非wifi用戶來說,流量還是很貴的,一個很耗流量的應(yīng)用,其用戶數(shù)量級肯定要受到影響。當(dāng)然,我想,向百度美拍這樣的應(yīng)用,必然也有其內(nèi)部的圖片緩存策略。總之,圖片緩存是很重要而且是必須的。
2.圖片緩存的原理
實現(xiàn)圖片緩存也不難,需要有相應(yīng)的cache策略。這里我采用 內(nèi)存-文件-網(wǎng)絡(luò) 三層cache機制,其中內(nèi)存緩存包括強引用緩存和軟引用緩存(SoftReference),其實網(wǎng)絡(luò)不算cache,這里姑且也把它劃到緩存的層次結(jié)構(gòu)中。當(dāng)根據(jù)url向網(wǎng)絡(luò)拉取圖片的時候,先從內(nèi)存中找,如果內(nèi)存中沒有,再從緩存文件中查找,如果緩存文件中也沒有,再從網(wǎng)絡(luò)上通過http請求拉取圖片。在鍵值對(key-value)中,這個圖片緩存的key是圖片url的hash值,value就是bitmap。所以,按照這個邏輯,只要一個url被下載過,其圖片就被緩存起來了。
關(guān)于Java中對象的軟引用(SoftReference),如果一個對象具有軟引用,內(nèi)存空間足夠,垃 圾回收器就不會回收它;如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現(xiàn)內(nèi)存敏感的高 速緩存。使用軟引用能防止內(nèi)存泄露,增強程序的健壯性。
從代碼上來說,采用一個ImageManager來負責(zé)圖片的管理和緩存,函數(shù)接口為public void loadBitmap(String url, Handler handler) ;其中url為要下載的圖片地址,handler為圖片下載成功后的回調(diào),在handler中處理message,而message中包含了圖片的信息以及bitmap對象。ImageManager中使用的ImageMemoryCache(內(nèi)存緩存)、ImageFileCache(文件緩存)以及LruCache(最近最久未使用緩存)會在后續(xù)文章中介紹。
3.代碼ImageManager.java
/*
* 圖片管理
* 異步獲取圖片,直接調(diào)用loadImage()函數(shù),該函數(shù)自己判斷是從緩存還是網(wǎng)絡(luò)加載
* 同步獲取圖片,直接調(diào)用getBitmap()函數(shù),該函數(shù)自己判斷是從緩存還是網(wǎng)絡(luò)加載
* 僅從本地獲取圖片,調(diào)用getBitmapFromNative()
* 僅從網(wǎng)絡(luò)加載圖片,調(diào)用getBitmapFromHttp()
*
*/
public class ImageManager implements IManager
{
private final static String TAG = "ImageManager";
private ImageMemoryCache imageMemoryCache; //內(nèi)存緩存
private ImageFileCache imageFileCache; //文件緩存
//正在下載的image列表
public static HashMap<String, Handler> ongoingTaskMap = new HashMap<String, Handler>();
//等待下載的image列表
public static HashMap<String, Handler> waitingTaskMap = new HashMap<String, Handler>();
//同時下載圖片的線程個數(shù)
final static int MAX_DOWNLOAD_IMAGE_THREAD = 4;
private final Handler downloadStatusHandler = new Handler(){
public void handleMessage(Message msg)
{
startDownloadNext();
}
};
public ImageManager()
{
imageMemoryCache = new ImageMemoryCache();
imageFileCache = new ImageFileCache();
}
/**
* 獲取圖片,多線程的入口
*/
public void loadBitmap(String url, Handler handler)
{
//先從內(nèi)存緩存中獲取,取到直接加載
Bitmap bitmap = getBitmapFromNative(url);
if (bitmap != null)
{
Logger.d(TAG, "loadBitmap:loaded from native");
Message msg = Message.obtain();
Bundle bundle = new Bundle();
bundle.putString("url", url);
msg.obj = bitmap;
msg.setData(bundle);
handler.sendMessage(msg);
}
else
{
Logger.d(TAG, "loadBitmap:will load by network");
downloadBmpOnNewThread(url, handler);
}
}
/**
* 新起線程下載圖片
*/
private void downloadBmpOnNewThread(final String url, final Handler handler)
{
Logger.d(TAG, "ongoingTaskMap'size=" + ongoingTaskMap.size());
if (ongoingTaskMap.size() >= MAX_DOWNLOAD_IMAGE_THREAD)
{
synchronized (waitingTaskMap)
{
waitingTaskMap.put(url, handler);
}
}
else
{
synchronized (ongoingTaskMap)
{
ongoingTaskMap.put(url, handler);
}
new Thread()
{
public void run()
{
Bitmap bmp = getBitmapFromHttp(url);
// 不論下載是否成功,都從下載隊列中移除,再由業(yè)務(wù)邏輯判斷是否重新下載
// 下載圖片使用了httpClientRequest,本身已經(jīng)帶了重連機制
synchronized (ongoingTaskMap)
{
ongoingTaskMap.remove(url);
}
if(downloadStatusHandler != null)
{
downloadStatusHandler.sendEmptyMessage(0);
}
Message msg = Message.obtain();
msg.obj = bmp;
Bundle bundle = new Bundle();
bundle.putString("url", url);
msg.setData(bundle);
if(handler != null)
{
handler.sendMessage(msg);
}
}
}.start();
}
}
/**
* 依次從內(nèi)存,緩存文件,網(wǎng)絡(luò)上加載單個bitmap,不考慮線程的問題
*/
public Bitmap getBitmap(String url)
{
// 從內(nèi)存緩存中獲取圖片
Bitmap bitmap = imageMemoryCache.getBitmapFromMemory(url);
if (bitmap == null)
{
// 文件緩存中獲取
bitmap = imageFileCache.getImageFromFile(url);
if (bitmap != null)
{
// 添加到內(nèi)存緩存
imageMemoryCache.addBitmapToMemory(url, bitmap);
}
else
{
// 從網(wǎng)絡(luò)獲取
bitmap = getBitmapFromHttp(url);
}
}
return bitmap;
}
/**
* 從內(nèi)存或者緩存文件中獲取bitmap
*/
public Bitmap getBitmapFromNative(String url)
{
Bitmap bitmap = null;
bitmap = imageMemoryCache.getBitmapFromMemory(url);
if(bitmap == null)
{
bitmap = imageFileCache.getImageFromFile(url);
if(bitmap != null)
{
// 添加到內(nèi)存緩存
imageMemoryCache.addBitmapToMemory(url, bitmap);
}
}
return bitmap;
}
/**
* 通過網(wǎng)絡(luò)下載圖片,與線程無關(guān)
*/
public Bitmap getBitmapFromHttp(String url)
{
Bitmap bmp = null;
try
{
byte[] tmpPicByte = getImageBytes(url);
if (tmpPicByte != null)
{
bmp = BitmapFactory.decodeByteArray(tmpPicByte, 0,
tmpPicByte.length);
}
tmpPicByte = null;
}
catch(Exception e)
{
e.printStackTrace();
}
if(bmp != null)
{
// 添加到文件緩存
imageFileCache.saveBitmapToFile(bmp, url);
// 添加到內(nèi)存緩存
imageMemoryCache.addBitmapToMemory(url, bmp);
}
return bmp;
}
/**
* 下載鏈接的圖片資源
*
* @param url
*
* @return 圖片
*/
public byte[] getImageBytes(String url)
{
byte[] pic = null;
if (url != null && !"".equals(url))
{
Requester request = RequesterFactory.getRequester(
Requester.REQUEST_REMOTE, RequesterFactory.IMPL_HC);
// 執(zhí)行請求
MyResponse myResponse = null;
MyRequest mMyRequest;
mMyRequest = new MyRequest();
mMyRequest.setUrl(url);
mMyRequest.addHeader(HttpHeader.REQ.ACCEPT_ENCODING, "identity");
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
myResponse = request.execute(mMyRequest);
is = myResponse.getInputStream().getImpl();
baos = new ByteArrayOutputStream();
byte[] b = new byte[512];
int len = 0;
while ((len = is.read(b)) != -1)
{
baos.write(b, 0, len);
baos.flush();
}
pic = baos.toByteArray();
Logger.d(TAG, "icon bytes.length=" + pic.length);
}
catch (Exception e3)
{
e3.printStackTrace();
try
{
Logger.e(TAG,
"download shortcut icon faild and responsecode="
+ myResponse.getStatusCode());
}
catch (Exception e4)
{
e4.printStackTrace();
}
}
finally
{
try
{
if (is != null)
{
is.close();
is = null;
}
}
catch (Exception e2)
{
e2.printStackTrace();
}
try
{
if (baos != null)
{
baos.close();
baos = null;
}
}
catch (Exception e2)
{
e2.printStackTrace();
}
try
{
request.close();
}
catch (Exception e1)
{
e1.printStackTrace();
}
}
}
return pic;
}
/**
* 取出等待隊列第一個任務(wù),開始下載
*/
private void startDownloadNext()
{
synchronized(waitingTaskMap)
{
Logger.d(TAG, "begin start next");
Iterator iter = waitingTaskMap.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry) iter.next();
Logger.d(TAG, "WaitingTaskMap isn't null,url=" + (String)entry.getKey());
if(entry != null)
{
waitingTaskMap.remove(entry.getKey());
downloadBmpOnNewThread((String)entry.getKey(), (Handler)entry.getValue());
}
break;
}
}
}
public String startDownloadNext_ForUnitTest()
{
String urlString = null;
synchronized(waitingTaskMap)
{
Logger.d(TAG, "begin start next");
Iterator iter = waitingTaskMap.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry) iter.next();
urlString = (String)entry.getKey();
waitingTaskMap.remove(entry.getKey());
break;
}
}
return urlString;
}
/**
* 圖片變?yōu)閳A角
* @param bitmap:傳入的bitmap
* @param pixels:圓角的度數(shù),值越大,圓角越大
* @return bitmap:加入圓角的bitmap
*/
public static Bitmap toRoundCorner(Bitmap bitmap, int pixels)
{
if(bitmap == null)
return null;
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
final float roundPx = pixels;
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
public byte managerId()
{
return IMAGE_ID;
}
}
現(xiàn)在android應(yīng)用中不可避免的要使用圖片,有些圖片是可以變化的,需要每次啟動時從網(wǎng)絡(luò)拉取,這種場景在有廣告位的應(yīng)用以及純圖片應(yīng)用(比如百度美拍)中比較多。
現(xiàn)在有一個問題:假如每次啟動的時候都從網(wǎng)絡(luò)拉取圖片的話,勢必會消耗很多流量。在當(dāng)前的狀況下,對于非wifi用戶來說,流量還是很貴的,一個很耗流量的應(yīng)用,其用戶數(shù)量級肯定要受到影響。當(dāng)然,我想,向百度美拍這樣的應(yīng)用,必然也有其內(nèi)部的圖片緩存策略。總之,圖片緩存是很重要而且是必須的。
2.圖片緩存的原理
實現(xiàn)圖片緩存也不難,需要有相應(yīng)的cache策略。這里我采用 內(nèi)存-文件-網(wǎng)絡(luò) 三層cache機制,其中內(nèi)存緩存包括強引用緩存和軟引用緩存(SoftReference),其實網(wǎng)絡(luò)不算cache,這里姑且也把它劃到緩存的層次結(jié)構(gòu)中。當(dāng)根據(jù)url向網(wǎng)絡(luò)拉取圖片的時候,先從內(nèi)存中找,如果內(nèi)存中沒有,再從緩存文件中查找,如果緩存文件中也沒有,再從網(wǎng)絡(luò)上通過http請求拉取圖片。在鍵值對(key-value)中,這個圖片緩存的key是圖片url的hash值,value就是bitmap。所以,按照這個邏輯,只要一個url被下載過,其圖片就被緩存起來了。
關(guān)于Java中對象的軟引用(SoftReference),如果一個對象具有軟引用,內(nèi)存空間足夠,垃 圾回收器就不會回收它;如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現(xiàn)內(nèi)存敏感的高 速緩存。使用軟引用能防止內(nèi)存泄露,增強程序的健壯性。
從代碼上來說,采用一個ImageManager來負責(zé)圖片的管理和緩存,函數(shù)接口為public void loadBitmap(String url, Handler handler) ;其中url為要下載的圖片地址,handler為圖片下載成功后的回調(diào),在handler中處理message,而message中包含了圖片的信息以及bitmap對象。ImageManager中使用的ImageMemoryCache(內(nèi)存緩存)、ImageFileCache(文件緩存)以及LruCache(最近最久未使用緩存)會在后續(xù)文章中介紹。
3.代碼ImageManager.java
復(fù)制代碼 代碼如下:
/*
* 圖片管理
* 異步獲取圖片,直接調(diào)用loadImage()函數(shù),該函數(shù)自己判斷是從緩存還是網(wǎng)絡(luò)加載
* 同步獲取圖片,直接調(diào)用getBitmap()函數(shù),該函數(shù)自己判斷是從緩存還是網(wǎng)絡(luò)加載
* 僅從本地獲取圖片,調(diào)用getBitmapFromNative()
* 僅從網(wǎng)絡(luò)加載圖片,調(diào)用getBitmapFromHttp()
*
*/
public class ImageManager implements IManager
{
private final static String TAG = "ImageManager";
private ImageMemoryCache imageMemoryCache; //內(nèi)存緩存
private ImageFileCache imageFileCache; //文件緩存
//正在下載的image列表
public static HashMap<String, Handler> ongoingTaskMap = new HashMap<String, Handler>();
//等待下載的image列表
public static HashMap<String, Handler> waitingTaskMap = new HashMap<String, Handler>();
//同時下載圖片的線程個數(shù)
final static int MAX_DOWNLOAD_IMAGE_THREAD = 4;
private final Handler downloadStatusHandler = new Handler(){
public void handleMessage(Message msg)
{
startDownloadNext();
}
};
public ImageManager()
{
imageMemoryCache = new ImageMemoryCache();
imageFileCache = new ImageFileCache();
}
/**
* 獲取圖片,多線程的入口
*/
public void loadBitmap(String url, Handler handler)
{
//先從內(nèi)存緩存中獲取,取到直接加載
Bitmap bitmap = getBitmapFromNative(url);
if (bitmap != null)
{
Logger.d(TAG, "loadBitmap:loaded from native");
Message msg = Message.obtain();
Bundle bundle = new Bundle();
bundle.putString("url", url);
msg.obj = bitmap;
msg.setData(bundle);
handler.sendMessage(msg);
}
else
{
Logger.d(TAG, "loadBitmap:will load by network");
downloadBmpOnNewThread(url, handler);
}
}
/**
* 新起線程下載圖片
*/
private void downloadBmpOnNewThread(final String url, final Handler handler)
{
Logger.d(TAG, "ongoingTaskMap'size=" + ongoingTaskMap.size());
if (ongoingTaskMap.size() >= MAX_DOWNLOAD_IMAGE_THREAD)
{
synchronized (waitingTaskMap)
{
waitingTaskMap.put(url, handler);
}
}
else
{
synchronized (ongoingTaskMap)
{
ongoingTaskMap.put(url, handler);
}
new Thread()
{
public void run()
{
Bitmap bmp = getBitmapFromHttp(url);
// 不論下載是否成功,都從下載隊列中移除,再由業(yè)務(wù)邏輯判斷是否重新下載
// 下載圖片使用了httpClientRequest,本身已經(jīng)帶了重連機制
synchronized (ongoingTaskMap)
{
ongoingTaskMap.remove(url);
}
if(downloadStatusHandler != null)
{
downloadStatusHandler.sendEmptyMessage(0);
}
Message msg = Message.obtain();
msg.obj = bmp;
Bundle bundle = new Bundle();
bundle.putString("url", url);
msg.setData(bundle);
if(handler != null)
{
handler.sendMessage(msg);
}
}
}.start();
}
}
/**
* 依次從內(nèi)存,緩存文件,網(wǎng)絡(luò)上加載單個bitmap,不考慮線程的問題
*/
public Bitmap getBitmap(String url)
{
// 從內(nèi)存緩存中獲取圖片
Bitmap bitmap = imageMemoryCache.getBitmapFromMemory(url);
if (bitmap == null)
{
// 文件緩存中獲取
bitmap = imageFileCache.getImageFromFile(url);
if (bitmap != null)
{
// 添加到內(nèi)存緩存
imageMemoryCache.addBitmapToMemory(url, bitmap);
}
else
{
// 從網(wǎng)絡(luò)獲取
bitmap = getBitmapFromHttp(url);
}
}
return bitmap;
}
/**
* 從內(nèi)存或者緩存文件中獲取bitmap
*/
public Bitmap getBitmapFromNative(String url)
{
Bitmap bitmap = null;
bitmap = imageMemoryCache.getBitmapFromMemory(url);
if(bitmap == null)
{
bitmap = imageFileCache.getImageFromFile(url);
if(bitmap != null)
{
// 添加到內(nèi)存緩存
imageMemoryCache.addBitmapToMemory(url, bitmap);
}
}
return bitmap;
}
/**
* 通過網(wǎng)絡(luò)下載圖片,與線程無關(guān)
*/
public Bitmap getBitmapFromHttp(String url)
{
Bitmap bmp = null;
try
{
byte[] tmpPicByte = getImageBytes(url);
if (tmpPicByte != null)
{
bmp = BitmapFactory.decodeByteArray(tmpPicByte, 0,
tmpPicByte.length);
}
tmpPicByte = null;
}
catch(Exception e)
{
e.printStackTrace();
}
if(bmp != null)
{
// 添加到文件緩存
imageFileCache.saveBitmapToFile(bmp, url);
// 添加到內(nèi)存緩存
imageMemoryCache.addBitmapToMemory(url, bmp);
}
return bmp;
}
/**
* 下載鏈接的圖片資源
*
* @param url
*
* @return 圖片
*/
public byte[] getImageBytes(String url)
{
byte[] pic = null;
if (url != null && !"".equals(url))
{
Requester request = RequesterFactory.getRequester(
Requester.REQUEST_REMOTE, RequesterFactory.IMPL_HC);
// 執(zhí)行請求
MyResponse myResponse = null;
MyRequest mMyRequest;
mMyRequest = new MyRequest();
mMyRequest.setUrl(url);
mMyRequest.addHeader(HttpHeader.REQ.ACCEPT_ENCODING, "identity");
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
myResponse = request.execute(mMyRequest);
is = myResponse.getInputStream().getImpl();
baos = new ByteArrayOutputStream();
byte[] b = new byte[512];
int len = 0;
while ((len = is.read(b)) != -1)
{
baos.write(b, 0, len);
baos.flush();
}
pic = baos.toByteArray();
Logger.d(TAG, "icon bytes.length=" + pic.length);
}
catch (Exception e3)
{
e3.printStackTrace();
try
{
Logger.e(TAG,
"download shortcut icon faild and responsecode="
+ myResponse.getStatusCode());
}
catch (Exception e4)
{
e4.printStackTrace();
}
}
finally
{
try
{
if (is != null)
{
is.close();
is = null;
}
}
catch (Exception e2)
{
e2.printStackTrace();
}
try
{
if (baos != null)
{
baos.close();
baos = null;
}
}
catch (Exception e2)
{
e2.printStackTrace();
}
try
{
request.close();
}
catch (Exception e1)
{
e1.printStackTrace();
}
}
}
return pic;
}
/**
* 取出等待隊列第一個任務(wù),開始下載
*/
private void startDownloadNext()
{
synchronized(waitingTaskMap)
{
Logger.d(TAG, "begin start next");
Iterator iter = waitingTaskMap.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry) iter.next();
Logger.d(TAG, "WaitingTaskMap isn't null,url=" + (String)entry.getKey());
if(entry != null)
{
waitingTaskMap.remove(entry.getKey());
downloadBmpOnNewThread((String)entry.getKey(), (Handler)entry.getValue());
}
break;
}
}
}
public String startDownloadNext_ForUnitTest()
{
String urlString = null;
synchronized(waitingTaskMap)
{
Logger.d(TAG, "begin start next");
Iterator iter = waitingTaskMap.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry) iter.next();
urlString = (String)entry.getKey();
waitingTaskMap.remove(entry.getKey());
break;
}
}
return urlString;
}
/**
* 圖片變?yōu)閳A角
* @param bitmap:傳入的bitmap
* @param pixels:圓角的度數(shù),值越大,圓角越大
* @return bitmap:加入圓角的bitmap
*/
public static Bitmap toRoundCorner(Bitmap bitmap, int pixels)
{
if(bitmap == null)
return null;
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
final float roundPx = pixels;
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
public byte managerId()
{
return IMAGE_ID;
}
}
相關(guān)文章
Android自定義ViewGroup實現(xiàn)朋友圈九宮格控件
在我們的實際應(yīng)用中,經(jīng)常需要用到自定義控件,比如自定義圓形頭像,自定義計步器等等,這篇文章主要給大家介紹了關(guān)于Android自定義ViewGroup實現(xiàn)朋友圈九宮格控件的相關(guān)資料,需要的朋友可以參考下2021-07-07Android TextView字體顏色設(shè)置方法小結(jié)
這篇文章主要介紹了Android TextView字體顏色設(shè)置方法,結(jié)合實例形式總結(jié)分析了Android開發(fā)中TextView設(shè)置字體顏色的常用技巧,需要的朋友可以參考下2016-02-02Android ContentProvider獲取手機聯(lián)系人實例
這篇文章主要介紹了Android ContentProvider獲取手機聯(lián)系人實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02Android設(shè)備上非root的抓包實現(xiàn)方法(Tcpdump方法)
通常我們在Android應(yīng)用中執(zhí)行某個命令時會使用“Runtime.getRuntime().exec("命令路徑")”這種方式,但是當(dāng)我們執(zhí)行抓包操作時,使用這條命令無論如何都不行,通過下面代碼打印結(jié)果發(fā)現(xiàn),該命令一定要在root權(quán)限下才能執(zhí)行,具體實現(xiàn)思路,請參考本教程2016-11-11Android RecycleView使用(CheckBox全選、反選、單選)
這篇文章主要為大家詳細介紹了Android RecycleView使用,CheckBox全選、反選、單選效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09淺析Android手機衛(wèi)士關(guān)閉自動更新
保存數(shù)據(jù)的四種方式,網(wǎng)絡(luò),廣播提供者,SharedPreferences,數(shù)據(jù)庫。接下來通過本文給大家介紹android手機衛(wèi)士關(guān)閉自動更新的相關(guān)知識,感興趣的朋友一起學(xué)習(xí)吧2016-04-04android通過servlet服務(wù)器保存文件到手機
這篇文章主要為大家詳細介紹了android通過servlet服務(wù)器保存文件到手機,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-06-06