-
掃描圖片(接上一筆記) 2.線程中需要通過ContentResolver來掃描圖片 Uri mImgUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; ContentResolver cr = MainActivity.this.getContentResolver(); //使用ContentResolver Cursor cursor = cr.query(...); 3.遍歷cursor獲取每個圖片,通過圖片的位置獲取所在的文件夾 while(cursor.moveToNext()){ String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); //獲取當前圖片的路徑 File parentFile = new File(path).getParentFile(); //獲取當前圖片所在的目錄 if(parentFile == null) continue; String dirPath = parentFile.getAbsolutePath(); //當前圖片所在目錄字符串 4.需要將遍歷過的圖片目錄放在一個set集合中,避免重復遍歷 假設當前有1000張圖片,300+300+400分布于三個目錄中,同一個目錄下的所有圖片都會重復遍歷。 if(mDirPaths.contains(dirPath)) continue; //當前路徑已經掃描過了 else { //當前的圖片目錄是第一次出現,將相關的屬性存入folderBean中 mDirPaths.add(dirPath); folderBean = new FolderBean(); folderBean.setDir(dirPath); folderBean.setFirstImgPath(path); } 4.獲取當前圖片目錄中圖片的張數,但要過濾出圖片 int picSize = parentFile.list(new FilenameFilter() { }).length; 5.注意: (1)ContentProvider在四大組件中學過 (2)看完整的代碼查看全部
-
重點。查看全部
-
一、初始化控件 1.掃描完存儲卡中的圖片后會得到ListView的數據源集合,需要為這個ListView的item創建一個bean模型 參考:http://www.xianlaiwan.cn/space/note/cid/365(BaseAdapter的使用) 如圖,每個item都是一個文件夾,所以創建一個FolderBean的類 2.getter和setter方法 注意一點:setName()可以簡化 public void setDir(String dir) { this.dir = dir; int lastIndexOf = this.dir.indexOf("/"); this.name = this.dir.substring(lastIndexOf); } 二、掃描圖片 開啟一個單獨的線程,通過ContentProvider去掃描手機中的所有圖片。 1.前期處理 private void initDatas() { if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { Toast.makeText(this, "當前存儲卡不可用!", Toast.LENGTH_SHORT).show(); return; } mProgressDialog = ProgressDialog.show(this, null, "正在加載..."); new Thread() { //掃描手機中的圖片查看全部
-
反射獲取寬和高最大值 1.imageView.getMaxWidth()和imageView.getMaxHeight()都只是API16才有的接口,如何兼容以下API版本? private static int getImageViewFieldValue(Object object, String fieldName){ int value = 0; try { Field field = ImageView.class.getDeclaredField(fieldName); field.setAccessible(true); int fieldValue = field.getInt(object); if(fieldValue > 0 && fieldValue < Integer.MAX_VALUE){ value = fieldValue; } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } return value; } 然后將對應接口替換。查看全部
-
利用信號量控制任務隊列 1.根據目前的邏輯,拿到一個task就會加入到taskQueue中,然后立即會sendMessage()給mPoolThreadHandler,它會將task加入到自己的任務隊列中執行。(如圖) 這樣做的結果是taskQzone中永遠只有一個任務,不能體現任務隊列的概念。 2.修改——使用信號量 (1)任務隊列mTaskQueue和線程池mThreadPool一定要分清楚 (2)當獲得一個Task之后,會調用addTasks(Runnable)將其加入到任務隊列mTaskQueue中,注意此時不會執行 它會立即sendMessage給mPoolThreadHandler,線程池將其加入到自己的任務隊列中(注意這時后臺線程池的),然后執行 mThreadPool.execute(getTask()); (3)注意,執行過程是個耗時操作,執行過程中,當前線程會獲取信號量 mSemaphoreThreadPool.acquire(); (4)假設當前線程池中指定為3個線程數,那么就會有另外兩個也會這么執行 如果此時又有一個也要執行,但是信號量已經沒了,就會阻塞。 (5)每個task執行完之后會釋放信號量 mSemaphoreThreadPool.release(); 這樣才能保證第四個task繼續執行 3.不知道這么理解是不是對的.....真TM faint!查看全部
-
后臺輪詢線程的并行操作 1.后臺輪詢線程handler是在init()函數中創建中,但注意它和其他函數是并行執行的,所以需要使用同步。 因為在創建一個task讓后臺輪詢線程執行時,它的handler可能還沒創建。 2.信號量 (1)新建一個信號量,默認為0個:private Semaphore mSemaphorePoolThreadHandler = new Semaphore(0); (2)在創建完mPoolThreadHandler之后,釋放一個信號量 mSemaphorePoolThreadHandler.release(); (3)最后在使用mPoolThreadHandler時,判斷是否已經存在,不存在的話就要阻塞等待 private synchronized void addTasks(Runnable runnable) { mTaskQueue.add(runnable); try { if (mPoolThreadHandler == null) //如果mPoolThreadHanlder為空,那就一直等待 mSemaphorePoolThreadHandler.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } mPoolThreadHandler.sendEmptyMessage(0x110); } 注意:為什么要加synchronized?因為可能同時其他線程也要獲取信號量,不同步的話可能造成死鎖。查看全部
-
壓縮圖片(接上一筆記) 3.獲取壓縮比例的操作 private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { //圖片實際寬和高 int width = options.outWidth; int height = options.outHeight; int inSampleSize = 1; if(width > reqWidth || height > reqHeight){ //不符比例,則調整尺寸 int widthRadio = Math.round(width*1.0f/reqWidth); int heightRadio = Math.round(height*1.0f/reqHeight); inSampleSize = Math.max(widthRadio, heightRadio); //去兩者大值,就會顯示完整圖片;如果取小值,圖片會超出顯示區域(較大) } return inSampleSize; } 注意:假如一個300*500的圖片,需要壓縮為100*100的圖片。如果取寬高筆記的較大值,那么最后效果是圖片會顯示完整,但會出現白邊;如果取小值,那么最后效果是圖片顯示不完整,但會填充完整個imageView 4.將壓縮后的圖片加入緩存,并通知UIthread來設置imageView //1.獲取圖片縮略圖需要顯示的寬和高 ImageSize imageSize = getImageViewSize(imageView); //2.根據上面獲得的圖片顯示寬高來壓縮圖片 Bitmap bm = decodeSampledBitmapFromPath(path, imageSize.width, imageSize.height); //3.將圖片加入到緩存 addBitmapToLruCache(path, bm); //需要使用path來對應 //4.sendMessage()通知UI線程設置給imageView查看全部
-
根據上一節獲取的圖片需要顯示的寬和高來壓縮圖片 1.上一節講了根據圖片顯示的imageView來獲取圖片需要顯示的寬和高 接下來根據獲得的圖片顯示寬高來壓縮圖片 2.創建一個函數來壓縮圖片,獲得壓縮后的bitmap Bitmap bm = decodeSampledBitmapFromPath(path, imageSize.width, imageSize.height); 實現: private Bitmap decodeSampledBitmapFromPath(String path, int width, int height) { //只是獲得圖片實際的寬和高,但并不加載圖片到內存 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(path, options); //獲得圖片實際的寬和高,保存在options中 options.inSampleSize = calculateInSampleSize(options, width, height); //使用獲取到的inSampleSize再次解析圖片,這一次加載圖片到內存 options.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeFile(path, options); return bitmap; } 注意:1.獲取實際圖片尺寸 2.獲取壓縮比例 3.最后根據比例再次加載圖片 3.獲取壓縮比例的操作 (下一筆記)查看全部
-
獲得圖片顯示的寬高 1.如果在LruCache中找不到path對應的bitmap,就會創建一個task添加到TaskQueue中。 addTasks(new Runnable(){ public void run() { //加載圖片,涉及到圖片的壓縮 //獲取圖片縮略圖需要顯示的寬和高 ImageSize imageSize = getImageViewSize(imageView); }}); mPoolThreadHandler.sendEmptyMessage(0x110);//通知其去執行 2.private void addTasks(Runnable runnable) { mTaskQueue.add(runnable); } 3.后臺輪詢線程handler的handleMessage() public void handleMessage(Message msg) { //線程池取出一個任務執行 mThreadPool.execute(getTask()); } 4.獲取圖片要顯示的寬和高 private ImageSize getImageViewSize(ImageView imageView) { ImageSize imageSize = new ImageSize(); DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics(); ViewGroup.LayoutParams lp = imageView.getLayoutParams(); int width = imageView.getWidth(); //獲取imageView的實際寬度 if(width <= 0){ width = lp.width; //獲取imageView在layout中聲明的寬度 } if(width <= 0){ width = imageView.getMaxWidth(); //最大值 } if(width <= 0){ width = displayMetrics.widthPixels; //屏幕寬度 } height同上查看全部
-
不能看查看全部
-
初始化UIThread——loadImage() 6.UIThead中需要設置對應的imageView public void handleMessage(Message msg) { //獲取到Bitmap格式的圖片,為ImageView回調設置圖片 ImageBeanHolder holder = (ImageBeanHolder) msg.obj; Bitmap bm = holder.bitmap; ImageView iamgeView = holder.imageView; String path = holder.path; //imageView和path是對應的,才會設置imageView if(iamgeView.getTag().toString().equals(path)){ imageView.setImageBitmap(bm); } } 為什么還要核對這里的imageView和path是否對應? 因為如果圖片列表已經滑動到第二頁,那么imageView沒變,但是圖片Bitmap已經變了,此時如果不判斷就直接設置的話,就會把第二頁的imageView也設置成第一張圖片的內容。查看全部
-
初始化UIThread——loadImage() 1.loadImage()根據圖片的路徑獲得到圖片的bitmap,然后設置到ImageView中 public void loadImage(String path, ImageView imageView) 2.ImageView和圖片的path相對應 imageView.setTag(path); 3.創建更新UI的handler if(mUIHandler == null){ mUIHandler = new Handler(){ public void handleMessage(Message msg) { //獲取到Bitmap格式的圖片,為ImageView回調設置圖片 } }; } 4.根據path在緩存中找到對應的緩存的圖片bitmap(可能找不到) Bitmap bm = getBitmapFromLruCache(path); if(bm != null){ //bitmap在cache中,直接發送message給UIThread,更新UI Message msg = Message.obtain(); ImageBeanHolder holder = new ImageBeanHolder(); //創建一個ImageBeanHolder(下5) holder.bitmap = bm; holder.imageView = imageView; holder.path = path; msg.obj = holder; mUIHandler.sendMessage(msg); }else 5.創建一個ImageBeanHolder對象? private class ImageBeanHolder{ Bitmap bitmap; ImageView imageView; String path; } 原因:在loadImage()根據path獲取的bitmap,可能是通過異步加載獲取到的(cache中不存),然后sendMessage()進行更新ImageView時,獲取到的這個bitmap是和path、ImageView是不能對應的。ImageBeanHolder可以讓他們對應起來。查看全部
-
ImageLoader類中的變量初始化 1.構造函數,傳入用于處理的線程數,以及任務隊列的調度方式 private ImageLoader(int threadCount, Type type){ init(threadCount, type); } 2.初始化后臺輪詢線程 輪詢線程需要一個Looper,還有一個用于處理消息的handleMessage() 參考:http://www.xianlaiwan.cn/space/note/cid/267 mPoolThread = new Thread(){ public void run() { Looper.prepare(); mPoolThreadHandler = new Handler(){ public void handleMessage(Message msg) { //線程池取出一個任務執行 } }; Looper.loop(); //loop()就是發給handler本身 }; }; 3..LruCache的初始化 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheMemory = maxMemory/8; mLruCache = new LruCache<String, Bitmap>(cacheMemory){ protected int sizeOf(String key, Bitmap value) { return value.getRowBytes()*value.getHeight(); //重寫sizeOf(),獲取一張圖片的占用大小 } }; 3.創建線程池,傳入線程數 mThreadPool = Executors.newFixedThreadPool(threadCount); mTaskQueue = new LinkedList<Runnable>(); mType = type;查看全部
-
(接上一筆記) (5)后臺輪詢線程 private Thread mPoolThread; private Handler mPoolThreadHandler; //這個handler專門給后臺輪詢線程的messageQueue發送消息 (6)UI線程中的Handler,用于更新ui 當傳入path獲取一個Bitmap圖片后,mUIHandler會發送一個message給UI線程用于更新圖片,這是一個關鍵變量。 private Handler mUIHandler;查看全部
-
ImageLoader單例、變量 1.ImageLoader單例模式 ImageLoader的實例在項目中只有一個,ImageLoader類中會維護一個LruCache,LruCache會占用固定大小的內存。這里使用單例。 (1)將構造函數私有化 private ImageLoader(){ } (2)getInstance(),通過類.方法名()來調用 public static ImageLoader getInstance(){ if(mInstance == null){ //不使用同步,判斷mInstance是否已存在,過濾掉存在的可能(這時可能多條線程在執行getInstance() synchronized (mInstance){ //做一下同步處理,多條線程進來 if(mInstance == null) //第一個線程滿足條件,創建了ImageLoader實例,第二個線程就不再會創建了。 mInstance = new ImageLoader(); } } return mInstance; } 通過兩個if判斷,可以提高效率。 2.ImageLoader的變量 (1)LruCache用于緩存圖片 private LruCache<String, Bitmap> mLruCache; (2)線程池,用于ImageLoader后臺線程將線程取出來放入線程池中進行處理 private ExecutorService mThreadPool; private static final int DEFAULT_THREAD_COUNT = 1; //默認線程數 (3)隊列調度方式 public enum Type{ FIFO, LIFO; } private Type mType = Type.LIFO; (4)任務隊列 使用LinkedList是因為它有首尾去元素的接口,以及它是鏈表存儲元素的方式(不占用連續內存) private LinkedList<Runnable> mTaskQueue; (下一筆記)查看全部
舉報
0/150
提交
取消