編輯:關於android開發
經常會用到 網絡文件 比如查看大圖片數據 資源優化的問題,當然用開源的項目 Android-Universal-Image-Loader 或者 ignition 都是個很好的選擇。
在這裡把原來 寫過的優化的代碼直接拿出來,經過測試千張圖片效果還是不錯的。
免費培訓課:http://www.jinhusns.com/Products/Curriculum/?type=xcj
工程目錄
至於 Activity 就是加載了 1個網格布局
01./**
02.* 實現 異步加載 和 2級緩存
03.*/
04.public class ImagedownActivity extends Activity {
05.
06.public static String filepath;
07.@Override
08.public void onCreate(Bundle savedInstanceState) {
09.super.onCreate(savedInstanceState);
10.setContentView(R.layout.main);
11.filepath = this.getCacheDir().getAbsolutePath();
12.GridView gv=(GridView)findViewById(R.id.gridview01);
13.//設置列數
14.gv.setNumColumns(3);
15.//配置適配器
16.gv.setAdapter(new Myadapter(this));
17.}
18.
19.@Override
20.protected void onDestroy() {
21.// TODO Auto-generated method stub
22.//activity 銷毀時,清除緩存
23.MyImageLoader.removeCache(filepath);
24.super.onDestroy();
25.}
26.
27.}
接下來 Myadapter.java(給網格每個item塞入圖片 )在生成每個 item 異步請求網絡獲取image
01.public class Myadapter extends BaseAdapter {
02.private Context context;
03.private String root ="http://192.168.0.100:8080/Android_list/";
04.private String[] URLS;
05.private final MyImageLoader myImageLoader = new MyImageLoader(context);;
06.
07./**
08.* adapter 初始化的時候早一堆數據
09.* 這裡我請求的是自己搭的服務器
10.* @param context
11.*/
12.public Myadapter(Context context){
13.this.context =context;
14.URLS = new String[999];
15.for (int i = 0; i < 999; i++) {
16.URLS[i] = root + (i+1)+".jpg";
17.}
18.}
19.
20.
21.@Override
22.public int getCount() {
23.return URLS.length;
24.}
25.
26.@Override
27.public Object getItem(int position) {
28.return URLS[position];
29.}
30.
31.@Override
32.public long getItemId(int position) {
33.return URLS[position].hashCode();
34.}
35.
36.
37.
38.@Override
39.public View getView(int position, View view, ViewGroup parent) {
40.ImageView imageView;
41.if(view==null){
42.imageView=new ImageView(context);
43.imageView.setLayoutParams(new GridView.LayoutParams(200,190));
44.imageView.setAdjustViewBounds(false);
45.imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
46.imageView.setPadding(5, 5, 5, 5);
47.}else{
48.imageView=(ImageView)view;
49.}
50.myImageLoader.downLoad(URLS[position], (ImageView)imageView , context);
51.return imageView;
52.}
53.}
MyImageLoader.java
001.public class MyImageLoader {
002.
003.//最大內存
004.final static int memClass = (int) Runtime.getRuntime().maxMemory();
005.private Context context;
006.
007.// 是否緩存到硬盤
008.private boolean diskcache = true;
009.
010.// 定義一級 緩存的圖片數
011.private static final int catch_num = 10;
012.
013.// 定義二級緩存 容器 軟引用
014.private static ConcurrentHashMap<String, SoftReference<Bitmap>> current_hashmap = new ConcurrentHashMap<String, SoftReference<Bitmap>>();
015.
016.// 定義一級緩存容器 強引用 (catch_num ,0.75f,true) 默認參 數 2.加載因子默認 3.排序模式 true
017.private static LinkedHashMap<String, Bitmap> link_hashmap = new LinkedHashMap<String, Bitmap>(catch_num ,0.75f,true) {
018.
019.// 必須實現的方法
020.protected boolean removeEldestEntry(java.util.Map.Entry<String, Bitmap> eldest) {
021./** 當一級緩存中 圖片數量大於 定義的數量 放入二級緩存中
022.*/
023.if (this.size() > catch_num) {
024.// 軟連接的方法 存進二級緩存中
025.current_hashmap.put(eldest.getKey(), new SoftReference<Bitmap>(
026.eldest.getValue()));
027.//緩存到本地
028.cancheToDisk(eldest.getKey(),eldest.getValue() );
029.
030.return true;
031.}
032.return false;
033.};
034.};
035.
036.public MyImageLoader(Context context) {
037.
038.}
039.
040.
041./**
042.* 外部調用此方法 進行下載圖片
043.*/
044.public void downLoad(String key , ImageView imageView,Context context){
045.// 先從緩存中找 。
046.context = this.context;
047.
048.Bitmap bitmap = getBitmapFromCache(key);
049.if( null!= bitmap){
050.imageView.setImageBitmap(bitmap);
051.cancleDownload(key, imageView); //取消下載
052.return ;
053.}
054.
055.// 緩存中 沒有 把當前的 imageView 給他 得到 task
056.if(cancleDownload(key, imageView)){ //沒有任務進行。,。。開始下載
057.ImageDownloadTask task = new ImageDownloadTask(imageView);
058.Zhanwei_Image zhanwei_image = new Zhanwei_Image(task);
059.//先把占位的圖片放進去
060.imageView.setImageDrawable(zhanwei_image);
061.// task執行任務
062.task.execute(key);
063.}
064.}
065.
066.
067./** 此方法 用於優化 : 用戶直接 翻到 哪個 就先加載 哪個、
068.* @param key - URL
069.* @param imageView - imageView
070.* core: 給當前的 imageView 得到給他下載的 task
071.*/
072.
073.private boolean cancleDownload(String key,ImageView imageView){
074.// 給當前的 imageView 得到給他下載的 task
075.ImageDownloadTask task = getImageDownloadTask(imageView);
076.if(null != task){
077.String down_key = task.key;
078.if( null == down_key || !down_key.equals(key)){
079.task.cancel(true); // imageview 和 url 的key不一樣 取消下載
080.}else{
081.return false; //正在下載:
082.}
083.}
084.return true; //沒有正在下載
085.}
086.
087.
088.
089.// public void getThisProcessMemeryInfo() {
090.// int pid = android.os.Process.myPid();
091.// android.os.Debug.MemoryInfo[] memoryInfoArray = activityManager.getProcessMemoryInfo(new int[] {pid});
092.// System.out.println("本應用當前使用了" + (float)memoryInfoArray[0].getTotalPrivateDirty() / 1024 + "mb的內存");
093.// }
094.
095.
096.
097./**
098.* 從緩存中得到 圖片的方法 1.先從一級 緩存找 linkhashmap 不是線程安全的 必須要加同步
099.*/
100.public Bitmap getBitmapFromCache(String key) {
101.//1.先在一級緩存中找
102.synchronized (link_hashmap) {
103.Bitmap bitmap = link_hashmap.get(key);
104.if (null != bitmap) {
105.link_hashmap.remove(key);
106.// 按照 LRU是Least Recently Used 近期最少使用算法 內存算法 就近 就 原則 放到首位
107.link_hashmap.put(key, bitmap);
108.System.out.println(" 在緩存1中找圖片了 =" +key);
109.return bitmap;
110.}
111.}
112.
113.// 2. 到二級 緩存找
114.SoftReference<Bitmap> soft = current_hashmap.get(key);
115.if (soft != null) {
116.//得到 軟連接 中的圖片
117.Bitmap soft_bitmap = soft.get();
118.if (null != soft_bitmap) {
119.System.out.println(" 在緩存2中找圖片了 =" +key);
120.return soft_bitmap;
121.}
122.} else {
123.// 沒有圖片的話 把這個key刪除
124.current_hashmap.remove(key);
125.}
126.
127.
128.//3.都沒有的話去從外部緩存文件讀取
129.if(diskcache){
130.Bitmap bitmap = getBitmapFromFile(key);
131.if(bitmap!= null){
132.link_hashmap.put(key, bitmap); //將圖片放到一級緩存首位
133.return bitmap;
134.}
135.}
136.
137.return null;
138.}
139.
140.
141./**
142.* 緩存到本地文件
143.* @param key
144.* @param bitmap
145.*/
146.public static void cancheToDisk(String key ,Bitmap bitmap ){
147.//2.緩存bitmap至/data/data/packageName/cache/文件夾中
148.try {
149.String fileName = getMD5Str(key);
150.String filePath = ImagedownActivity.filepath + "/" + fileName;
151.System.out.println("緩存到本地===" + filePath);
152.FileOutputStream fos = new FileOutputStream(filePath);
153.bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
154.
155.} catch (Exception e) {
156.
157.}
158.}
159.
160.
161./**
162.* 從外部文件緩存中獲取bitmap
163.* @param url
164.* @return
165.*/
166.private Bitmap getBitmapFromFile(String url){
167.Bitmap bitmap = null;
168.String fileName = getMD5Str(url);
169.if(fileName == null){
170.return null;
171.}
172.String filePath = ImagedownActivity.filepath + "/" + fileName;
173.try {
174.FileInputStream fis = new FileInputStream(filePath);
175.bitmap = BitmapFactory.decodeStream(fis);
176.System.out.println("在本地緩存中找到圖片==="+ filePath);
177.} catch (FileNotFoundException e) {
178.System.out.println("getBitmapFromFile==="+ e.toString());
179.e.printStackTrace();
180.bitmap = null;
181.}
182.return bitmap;
183.}
184.
185.
186.
187./**
188.* 清理文件緩存
189.* @param dirPath
190.* @return
191.*/
192.public static boolean removeCache(String dirPath) {
193.File dir = new File(dirPath);
194.File[] files = dir.listFiles();
195.if(files == null || files.length == 0) {
196.return true;
197.}
198.int dirSize = 0;
199.//這裡刪除所有的緩存
200.int all_ = (int) ( 1 * files.length + 1);
201.//對files 進行排序
202.Arrays.sort(files, new FileLastModifiedSort());
203.for (int i = 0; i < all_ ; i++) {
204.files[i].delete();
205.}
206.return true;
207.}
208.
209.
210./**
211.* 根據文件最後修改時間進行排序
212.*/
213.private static class FileLastModifiedSort implements Comparator<File> {
214.@Override
215.public int compare(File lhs, File rhs) {
216.if(lhs.lastModified() > rhs.lastModified()) {
217.return 1;
218.} else if(lhs.lastModified() == rhs.lastModified()) {
219.return 0;
220.} else {
221.return -1;
222.}
223.}
224.}
225.
226.
227./**
228.* MD5 加密
229.*/
230.private static String getMD5Str(String str) {
231.MessageDigest messageDigest = null;
232.try {
233.messageDigest = MessageDigest.getInstance("MD5");
234.messageDigest.reset();
235.messageDigest.update(str.getBytes("UTF-8"));
236.} catch (NoSuchAlgorithmException e) {
237.System.out.println("NoSuchAlgorithmException caught!");
238.return null;
239.} catch (UnsupportedEncodingException e) {
240.e.printStackTrace();
241.return null;
242.}
243.
244.byte[] byteArray = messageDigest.digest();
245.StringBuffer md5StrBuff = new StringBuffer();
246.for (int i = 0; i < byteArray.length; i++) {
247.if (Integer.toHexString(0xFF & byteArray[i]).length() == 1)
248.md5StrBuff.append("0").append(Integer.toHexString(0xFF & byteArray[i]));
249.else
250.md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
251.}
252.return md5StrBuff.toString();
253.}
254.
255.
256.// ------------------------ 異步加載----------------------------
257./**
258.* 占位的 圖片 或者 顏色 用來綁定 相應的圖片
259.*/
260.class Zhanwei_Image extends ColorDrawable{
261.//裡面存放 相應 的異步 處理時加載好的圖片 ----- 相應的 task
262.private final WeakReference<ImageDownloadTask> taskReference;
263.public Zhanwei_Image(ImageDownloadTask task){
264.super(Color.BLUE);
265.taskReference = new WeakReference<MyImageLoader.ImageDownloadTask>(task);
266.}
267.// 返回去這個 task 用於比較
268.public ImageDownloadTask getImageDownloadTask(){
269.return taskReference.get();
270.}
271.}
272.
273.
274.// 根據 給 的 iamgeView、 得到裡面的 task 用於和當前的 task比較是不是同1個
275.private ImageDownloadTask getImageDownloadTask(ImageView imageView){
276.if( null != imageView){
277.Drawable drawable = imageView.getDrawable();
278.if( drawable instanceof Zhanwei_Image)
279.return ((Zhanwei_Image)drawable).getImageDownloadTask();
280.
281.}
282.return null;
283.}
284.
285.
286.
287./**
288.* 把圖片 添加到緩存中
289.*/
290.public void addBitmap(String key, Bitmap bitmap) {
291.if (null != bitmap) {
292.synchronized (link_hashmap) { // 添加到一級 緩存中
293.link_hashmap.put(key, bitmap);
294.}
295.}
296.}
297.
298.
299./** 在後台 加載每個圖片
300.* 第一個參數 第2個要進度條不 第三個返回結果 bitmap
301.*/
302.class ImageDownloadTask extends AsyncTask<String, Void, Bitmap> {
303.
304.private String key;
305.private WeakReference<ImageView> imgViReference;
306.
307.public ImageDownloadTask(ImageView imageView) {
308.//imageView 傳進來 。。要給哪個iamgeView加載圖片
309.imgViReference = new WeakReference<ImageView>(
310.imageView);
311.}
312.
313.@Override
314.protected Bitmap doInBackground(String... params){
315.key = params[0];
316.//調用下載函數 根據 url 下載
317.return downloadBitmap(key);
318.}
319.
320.@Override
321.protected void onPostExecute(Bitmap result) {
322.if(isCancelled()){
323.result = null;
324.}
325.
326.System.out.println("result=="+ result.getByteCount()+"---memClassmemery="+memClass);
327.
328.if(null!= result){
329.//保存到緩存中
330.addBitmap(key, result);
331.ImageView imageView = imgViReference.get();
332.if( null != imageView){
333.//向 imageView 裡面放入 bitmap
334.ImageDownloadTask task = getImageDownloadTask(imageView);
335.
336./**
337.* 判斷 是不是 同一個 task( )
338.* 如果當前這個 task == imageView 裡面的那個 task 就是同1個
339.*/
340.if( this == task ){
341.imageView.setImageBitmap(result);
342.
343.}
344.}
345.}
346.}
347.}
348.
349.
350./**
351.* 連接網絡 客戶端 下載圖片
352.*/
353.private Bitmap downloadBitmap(String url) {
354.
355.final HttpClient client = AndroidHttpClient.newInstance("Android");
356.final HttpGet getRequest = new HttpGet(url);
357.try {
358.HttpResponse response = client.execute(getRequest);
359.final int statusCode = response.getStatusLine().getStatusCode();
360.
361.if (statusCode != HttpStatus.SC_OK) {
362.
363.Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url);
364.return null;
365.}
366.
367.final HttpEntity entity = response.getEntity();
368.if (entity != null) {
369.InputStream inputStream = null;
370.try {
371.
372.inputStream = entity.getContent();
373./**
374.* 1.沒有壓縮直接將生成的bitmap返回去
375.*/
376.// return BitmapFactory.decodeStream(inputStream);
377.
378./**
379.* 2.得到data後在這裡把圖片進行壓縮
380.*/
381.byte[] data = StreamTool.read(inputStream);
382.return BitmapManager.scaleBitmap(context, data, 0.3f);
383.// return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
384.} finally {
385.if (inputStream != null) {
386.inputStream.close();
387.}
388.entity.consumeContent();
389.}
390.}
391.} catch (IOException e) {
392.getRequest.abort();
393.} catch (IllegalStateException e) {
394.getRequest.abort();
395.} catch (Exception e) {
396.getRequest.abort();
397.} finally {
398.if ((client instanceof AndroidHttpClient)) {
399.((AndroidHttpClient) client).close();
400.}
401.}
402.return null;
403.}
404.
405.}
StreamTool.java
01.public class StreamTool {
02.
03.public static byte[] read(InputStream in) throws Exception{
04.ByteArrayOutputStream out_byte = new ByteArrayOutputStream();
05.byte[] buff = new byte[1024];
06.int len=0;
07.while((len = in.read(buff))!= -1){
08.//寫到內存中 字節流
09.out_byte.write( buff, 0 , len);
10.}
11.out_byte.close();
12.// 把內存數據返回
13.return out_byte.toByteArray();
14.}
15.}
BitmapManager.java ( 這個類裡面對 網絡資源的圖片 進行了優化)
001.public class BitmapManager {
002.
003./**
004.* 按屏幕適配Bitmap
005.*/
006.public static Bitmap scaleBitmap(Context context, byte[] data , float percent) {
007.
008.//這裡我不獲取了,假設是下面這個分辨率
009.int screenWidth = 540;
010.int screenrHeight = 950;
011.//設置 options
012.BitmapFactory.Options options = new BitmapFactory.Options();
013./**
014.* BitmapFactory.Options這個類,有一個字段叫做 inJustDecodeBounds.SDK中對這個成員的說明是這樣的:
015.* If set to true, the decoder will return null (no bitmap), but the out…
016.* 也就是說,如果我們把它設為true,那麼BitmapFactory.decodeFile(String path, Options opt)並不會真的返回一個Bitmap給你,
017.* 它僅僅會把它的寬,高取回來給你,這樣就不會占用太多的內存,也就不會那麼頻繁的發生OOM了。
018.*/
019.options.inJustDecodeBounds = true;
020.
021.//讀取
022.Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
023.
024.int imgWidth = options.outWidth;
025.int imgHeight = options.outHeight;
026.
027.//如果比你設置的寬高大 就進行縮放,
028.if(imgWidth > screenWidth * percent || imgHeight > screenrHeight * percent) {
029.options.inSampleSize = calculateInSampleSize(options, screenWidth, screenrHeight, percent);
030.}
031.
032.
033./**
034.* If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller
035.* to query the bitmap without having to allocate the memory for its pixels.
036.*
037.* 如果設置成 true,這個編碼將會返回1個null , 但是那個區域仍將被設置(也就是存在),允許(調用者)去查詢那個沒有分配 內存的像素 bitmap
038.*/
039.options.inJustDecodeBounds = false;
040.
041./**
042.* Android的Bitmap.Config給出了bitmap的一個像素所對應的存儲方式,
043.* 有RGB_565,ARGB_8888,ARGB_4444,ALPHA_8四種。RGB_565表示的是紅綠藍三色分別用5,6,5個比特來存儲,
044.* 一個像素占用了5+6+5=16個比特。ARGB_8888表示紅綠藍和半透明分別用8,8,8,8個比特來存儲,
045.* 一個像素占用了8+8+8+8=32個比特。這樣的話如果圖片是以RGB_8888讀入的,那麼占用內存的大小將是RGB_565讀入方式的2倍。
046.* 通常我們給Imagview加載圖片是通過setDrawable或者在xml文件中用android:src來設置
047.* 默認的加載圖片大小的方式是以RGB_8888讀入的。
048.*
049.*/
050.options.inPreferredConfig = Bitmap.Config.RGB_565;
051.
052./**
053.* If this is set to true, then the resulting bitmap will allocate its pixels such that they can be purged
054.* if the system needs to reclaim memory.
055.*
056.* 如果設置成 true, 這個結果bitmap 將會被分配像素,這樣他們就能被 系統回收了,當系統需要回收內存的時候
057.*/
058.options.inPurgeable = true;
059.
060./**
061.* This field works in conjuction with inPurgeable.
062.* 這個方法是在 inPurgeable 的基礎上工作的
063.*/
064.options.inInputShareable = true;
065.
066.
067.bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
068.
069.System.out.println("data==="+ data.length +" change == bitmap byte "+ bitmap.getByteCount());
070.return bitmap;
071.}
072.
073.
074.
075.// options reqWidth 屏幕寬 reqHeight屏幕高 你的view是屏幕的多大
076.public static int calculateInSampleSize(BitmapFactory.Options options, int screenWidth, int screenHeight ,float percent) {
077.
078.// 原始圖片寬高
079.final int height = options.outHeight;
080.final int width = options.outWidth;
081.// 倍數
082.int inSampleSize = 1;
083.
084.if (height > screenHeight * percent || width > screenWidth * percent) {
085.
086.// 計算目標寬高與原始寬高的比值
087.final int inSampleSize_h = Math.round((float) height / (float)( screenHeight * percent));
088.
089.final int inSampleSize_w = Math.round((float) width / (float)( screenWidth * percent));
090.
091.// 選擇兩個比值中較小的作為inSampleSize的
092.inSampleSize = inSampleSize_h < inSampleSize_w ? inSampleSize_h : inSampleSize_w;
093.
094.System.out.println("inSampleSize===="+ inSampleSize);
095.//
096.if(inSampleSize < 1) {
097.inSampleSize = 1;
098.}
099.}
100.//簡單說這個數字就是 縮小為原來的幾倍,根據你的image需要占屏幕多大動態算的(比如你用的權重設置layout)
101.return inSampleSize;
102.}
103.}
這個是代碼輸出的最多給這個進程分配的內存 128M
可以看到我上面的bitmapManager 裡面有個 options.inPreferredConfig 注釋寫的很清楚,可以上去看一下,接下來貼幾種格式的效果圖
rgb565 和 argb_444 所占的內存 (54000)
看一下 argb_8888 ( 108000)
當然可能仔細看的人會看到我一開始截的 鳴人的效果圖 上半部分 和 下半部分的顏色會有點問題。上面的rgb_565 生成的,和原圖色彩可能會有點出入。
但是內存真心少了一半,所以各種取捨就看個人了,代碼注釋都謝的很清楚了。
至於 : MyImageLoaderLru.java 其實就是 MyImageLoader.java
先貼出代碼不同地方的代碼 : 就是在強引用的地方 把 LinkedHashMap 換成了 LruCache
01.// 獲取單個進程可用內存的最大值
02.// 方式一:使用ActivityManager服務(計量單位為M)
03./*int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();*/
04.// 方式二:使用Runtime類(計量單位為Byte)
05.final static int memClass = (int) Runtime.getRuntime().maxMemory();
06.// 3. 定義一級緩存容器 強引用 (catch_num /2,0.75f,true) 默認參 數 2.加載因子默認 3.排序模式 true
07.final static int max = memClass/5;
08.
09.// LruCache 用強引用將 圖片放入 LinkedHashMap
10.private static LruCache<String, Bitmap> lrucache = new LruCache<String, Bitmap>(max) {
11.protected int sizeOf(String key, Bitmap value) {
12.if(value != null) {
13.// 計算存儲bitmap所占用的字節數
14.return value.getRowBytes() * value.getHeight();
15.} else {
16.return 0;
17.}
18.}
19.
20.@Override
21.protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
22.if(oldValue != null) {
23.// 當硬引用緩存容量已滿時,會使用LRU算法將最近沒有被使用的圖片轉入軟引用緩存
24.current_hashmap.put(key, new SoftReference<Bitmap>(oldValue));
25.}
26.}
27.};
1. 強引用:LruCache 後面再說,其實他內的內部封裝的就是1個 LinkedHashMap 。LinkedHashMap 是線程不安全的,所以上面都會用到同步。
2. 軟引用:ConcurrentHashMap 是線程安全的,並且支持高並發很有效率,這個後面也會說到,為什麼要用 軟引用 SoftReference,這個是在系統將要oom時,就會回收
軟引用的對象資源,所以才會用到他,防止程序出異常 。
3. 磁盤緩存: 這個經常會看到網易新聞等,應用有些界面你看了很多圖片,往上翻很多, 其實沒有再次訪問網絡,會將部分image緩存在sdcard裡。
4. 其中1個優化: 當比如用戶快速滑動到 最底部,其實是最先加載顯示給用戶的部分的內容的,這樣就是用戶看到哪加載哪,1個是快,1個是避免資源浪費。
原理: 當用戶進入界面加載圖片 ,首先會從1級緩存強引用中找,找不到回去2級緩存軟引用中找,找不到再去sdcard中找,再找不到才會去請求網絡加載資源。
當然sdcard的緩存 看個人需求是否需要。
注: android 4.0 後 對 SoftReference 的回收機制進行了改變,所以你是可以不用 2級緩存的,直接去掉就好了。
只要控制好你的 lrucache 或者 linkedhashmap就好了。
免費培訓課:http://www.jinhusns.com/Products/Curriculum/?type=xcj
Android 手把手帶你玩轉自定義相機
Android 手把手帶你玩轉自定義相機 概述 相機幾乎是每個APP都要用到的功能,萬一老板讓你定制相機方不方?反正我是有點方。關於相機的兩天奮斗總結免費送給你。 啟
為什麼 Android Studio 工程文件夾占用空間這麼大?我們來給它減減肥,androidstudio
為什麼 Android Studio 工程文件夾占用空間這麼大?我們來給它減減肥,androidstudio偶然中發現Android Studio的工程文件夾比ADT B
activity切換動畫特效,activity切換特效
activity切換動畫特效,activity切換特效效果圖: 結構圖: 測試代碼: 布局: 1 <?xml version=1.
AndroidAnnnotations注入框架使用之第三方框架集成RoboGuice(十三)
AndroidAnnnotations注入框架使用之第三方框架集成RoboGuice(十三) (一).前言: 前面我們已經對於AndroidA