上一篇《【Android】讀取sdcard卡上的全部圖檔而且顯示,讀取的過程有進度條顯示》(點選打開連結)在真機上測試非常有問題。常常遇到記憶體溢出。卡死的情況。由于如今真機上的記憶體上,2G已經非常少見了,基本上都8G的樣子了。
由于把讀取出來的圖檔一次性地放到app上,而且讀取的過程中,又沒有正在讀取到哪個檔案,盡管可以在AVD安卓模拟器上完畢主要的功能,可是這個app非常不友好。
是以採用Handler、Message配合線程等安卓消息機制。完畢讀取過程,而且利用GridView把讀取到的圖檔顯示出來。改進這個記憶體卡圖檔讀取器,詳細效果例如以下,基本是可以媲美部分安卓系統自帶的圖庫了吧……就差個對目錄的分類而已了……
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5iN4QjN1YmYzQmM1EWNmFGZlZGNmRzYkNmNhRDM4gjNh9CXxAzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL4M3Lc9CX6MHc0RHaiojIsJye.gif)
一開始。在讀取的過程。有讀取進度的顯示,詳細讀取到哪個目錄。
這對于大記憶體卡非常有意義。我的8G記憶體卡親測,僅30秒完畢讀取。
之後,用網格視圖把圖檔顯示出來。點選圖檔能夠檢視圖檔的大圖。與圖檔的路徑。
在app的右上角能夠退出。
當然本app也完畢對傳回鍵的監聽。
傳回鍵也可以退出。
這個在《【Android】各式各樣的彈出框與對菜單鍵、傳回鍵的監聽》(點選打開連結)已經說過了,這裡不再贅述。
1、首先改動res\values\strings.xml,設定各個字元,例如以下。作者什麼的,請忽略這些細節!
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">記憶體卡圖檔檢視器</string>
<string name="menu_author">作者:yongh701</string>
<string name="menu_exit">退出</string>
<string name="img_description">大圖</string>
<string name="view_button1">傳回</string>
</resources>
2、之後改動菜單的字元檔案res\menu\main.xml例如以下,一個配置到MainActivity.java其中,這個在《【Android】日期拾取器、時間拾取器與菜單》(點選打開連結)已經說過了。這裡不再贅述。
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/menu_exit"
android:title="@string/menu_exit"/>
<item android:title="@string/menu_author"/>
</menu>
3、随後完畢AndroidManifest.xml的改動。要求系統對本app賦予SDCard的讀寫權限。同一時候。由于app檢視大圖要用還有一個Activity,這裡同一時候在這裡注冊檢視大圖的ViewActivity。該檔案改動例如以下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sdcard_read_all"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 要求向SDCard讀取資料權限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 要求向SDCard寫入資料權限 -->
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.sdcard_read_all.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.sdcard_read_all.ViewActivity"
android:label="@string/img_description" > <!-- 注冊ViewActivity -->
</activity>
</application>
</manifest>
4、事實上之後的過程,與《【Android】圖檔資源的訪問與網格式圖檔浏覽器》(點選打開連結)是一樣的。先改動MainActivity的布局檔案res\layout\activity_main.xml,布置一個網格布局GridView。當然,同一時候布置一個TextView,用于在讀取完畢之前。顯示讀取資訊。此檔案改動之後例如以下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="36sp" />
<GridView
android:id="@+id/gridView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="2" >
</GridView>
</LinearLayout>
5、同一時候在res\layout\建立一個檢視大圖的的ViewActivity的布局檔案activity_view.xml。這裡和《【Android】畫廊式的圖檔浏覽器,使用HorizontalScrollView代替Gallery,OnClickListener的參數傳遞》(點選打開連結)同理。僅僅隻是是在一個垂直滾動布局下。建立一個線性子布局LinearLayout。這樣就不用操心元件的超過一屏,能夠達到滾動效果。線性布局的卷軸框就是這樣做的。三個元件皆沾滿螢幕寬度,同一時候分别賦予id,一會兒通過ViewActivity.java賦予對應的值。當中圖檔視圖ImageView配置的adjustViewBounds與scaleType。讓其自己主動縮放大小。
android:contentDescription="@string/img_description"是圖檔的描寫叙述,不賦予。Eclipse會出現警告。
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="centerInside"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:contentDescription="@string/img_description" />
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/view_button1"
android:textSize="24sp" />
</LinearLayout>
</ScrollView>
6、在src\目前project包下(這裡是com.sdcard_read_all)中。如《【Android】多個Activity之間利用bundle傳遞數值》(點選打開連結)一樣,建立一個繼承android.app.Activity的ViewActivity.java。裡面代碼例如以下:
package com.sdcard_read_all;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
public class ViewActivity extends Activity {
private TextView textView1;
private ImageView imageView1;
private Button button1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view);
//擷取元件
textView1 = (TextView) findViewById(R.id.textView1);
imageView1 = (ImageView) findViewById(R.id.imageView1);
button1 = (Button) findViewById(R.id.button1);
//得到MainActivity傳遞過來的圖檔路徑,進行對應的處理。
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
String imgPath = bundle.getString("imgPath");
textView1.setText("圖檔路徑:" + imgPath);
Bitmap bm = BitmapFactory.decodeFile(imgPath);
imageView1.setImageBitmap(bm);
//按鈕的傳回事件
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
finish();
}
});
}
// 對實體按鈕的監聽
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
finish();// 關閉這個Activity。
break;
}
return super.onKeyDown(keyCode, event);
}
}
7、完畢了檢視大圖ViewActivity之後,便是我們的壓軸大戲MainActivity,MainActivity也是整個程式的核心,代碼例如以下。代碼在結構上分為五部分:1、周遊sdcard的方法。2、載入圖檔的緩存工具方法,這兩個方法與《【Android】讀取sdcard卡上的全部圖檔而且顯示。讀取的過程有進度條顯示》(點選打開連結)是相同的。
3、程式的運作onCreate方法,4、app的菜單,這《【Android】日期拾取器、時間拾取器與菜單》(點選打開連結)已經說過了。
5、對實體按鍵的監聽。這能夠參考《【Android】各式各樣的彈出框與對菜單鍵、傳回鍵的監聽》(點選打開連結)。
程式的入口與其他安卓程式相同為onCreate方法。
程式的運作将會分成兩部分:
1、讀取時候。開一條讀取進行,利用安卓的線程消息傳遞機制。不停在更新textView1。這與《【Android】進度條與線程之間的消息處理》(點選打開連結)事實上是同理。僅僅能這樣完畢更新,通過安卓的線程消息傳遞機制,不至于app會出現當機的線程。安卓系統會自己主動對程式上的線程進行資源良好排程與配置設定。
之前的程式就由于對線程的處理不好,導緻讀大記憶體卡會卡死。如今通過安卓的線程消息傳遞機制讀多大的記憶體卡也是沒事的!
2、讀取完成,與《【Android】圖檔資源的訪問與網格式圖檔浏覽器》(點選打開連結)相同。利用擴充卡與網格視圖,把全部圖檔顯示。這裡因為網格視圖自帶的擴充卡是自己主動會配置設定好資源的,是以占用系統資源也不大的。同一時候對網格視圖賦予監聽,點選圖檔傳遞圖檔路徑,并打開檢視大圖ViewActivity。
package com.sdcard_read_all;
import java.io.File;
import java.util.ArrayList;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.TextView;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
public class MainActivity extends Activity {
private TextView textView1;
private GridView gridView1;
private static Handler handler;// 線程消息處理器
// 用于存放sdcard卡上的全部圖檔路徑
public static ArrayList<String> dirAllStrArr = new ArrayList<String>();
// 用于周遊sdcard卡上全部檔案的類
public static void DirAll(File dirFile) throws Exception {
if (dirFile.exists()) {
File files[] = dirFile.listFiles();
for (File file : files) {
String fileName = file.getName();
String filePath = file.getPath();
// 讀取檔案路徑,作為資訊發送給handler進行處理
Message msg = new Message();
msg.obj = "正在讀取:" + filePath;
handler.sendMessage(msg);
if (file.isDirectory()) {
// 除sdcard上Android這個目錄以外。
if (!fileName.endsWith("Android")) {
// 假設遇到目錄則遞歸調用。
DirAll(file);
}
} else {
// 假設是圖檔檔案壓入數組
if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")
|| fileName.endsWith(".bmp")
|| fileName.endsWith(".gif")
|| fileName.endsWith(".png")) {
if (dirFile.getPath().endsWith(File.separator)) {
dirAllStrArr
.add(dirFile.getPath() + file.getName());
} else {
dirAllStrArr.add(dirFile.getPath() + File.separator
+ file.getName());
}
}
}
}
}
}
// 圖檔載入的緩存工具類,利用安卓自帶的方法,依據大小壓縮圖檔
public static BitmapFactory.Options getHeapOpts(File file) {
BitmapFactory.Options opts = new BitmapFactory.Options();
// 數字越大讀出的圖檔占用的記憶體必須越小,不然總是溢出
if (file.length() < 20480) { // 0-20k
opts.inSampleSize = 1;// 這裡意為縮放的大小 ,數字越多縮放得越厲害
} else if (file.length() < 51200) { // 20-50k
opts.inSampleSize = 2;
} else if (file.length() < 307200) { // 50-300k
opts.inSampleSize = 4;
} else if (file.length() < 819200) { // 300-800k
opts.inSampleSize = 6;
} else if (file.length() < 1048576) { // 800-1024k
opts.inSampleSize = 8;
} else {
opts.inSampleSize = 10;
}
return opts;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 擷取元件
textView1 = (TextView) findViewById(R.id.textView1);
gridView1 = (GridView) findViewById(R.id.gridView1);
// 線程開始之前。先把網格視圖是以
gridView1.setVisibility(View.INVISIBLE);
/* 周遊sdcard旗下的全部目錄的線程開始 */
Thread readSdcard = new Thread() {
private String sdpath = Environment.getExternalStorageDirectory()
.getAbsolutePath();// 擷取sdcard的根路徑
private File dirFile = new File(sdpath);
public void run() {
try {
DirAll(dirFile);// 周遊sdcard旗下的全部目錄
// 完畢之後,線上程自身的消亡之前,發送一條“0”的标志資訊。給handler
Message msg = new Message();
msg.obj = "0";
handler.sendMessage(msg);
} catch (Exception e) {
e.printStackTrace();
}
};
};
textView1.setVisibility(View.VISIBLE);
readSdcard.start();// 線程開始,預設不開始。
/* 周遊sdcard旗下的全部目錄結束 */
// 不停在接受線性的消息,依據消息。進行處理
// 随着線程readSdcard的開始而出現。随其死亡而消亡。與線程是一對的。
handler = new Handler(new Handler.Callback() {// 這樣寫。就不彈出什麼洩漏的警告了
@Override
public boolean handleMessage(Message msg) {
textView1.setText(msg.obj + "");
if (msg.obj.equals("0")) {// 完畢讀取之後
textView1.setVisibility(View.GONE);// 隐藏讀取資訊
gridView1.setVisibility(View.VISIBLE);// 顯示網格視圖
}
return false;
}
});
// 網格視圖擴充卡
BaseAdapter baseAdapter = new BaseAdapter() {
@Override
public View getView(int position, View convertView, ViewGroup arg2) {
ImageView imageView1;
if (convertView == null) {
imageView1 = new ImageView(MainActivity.this);
imageView1.setAdjustViewBounds(true);// 自己主動縮放為寬高比
imageView1.setScaleType(ScaleType.CENTER_INSIDE);// 設定圖檔保持寬高比顯示
imageView1.setPadding(5, 5, 5, 5);
} else {
imageView1 = (ImageView) convertView;
}
// 把圖檔載入到網格視圖
String filePath = dirAllStrArr.get(position);
File file = new File(filePath);
Bitmap bm = BitmapFactory.decodeFile(filePath,
getHeapOpts(file));
imageView1.setImageBitmap(bm);
return imageView1;
}
// 擷取目前選項
@Override
public long getItemId(int position) {
return position;
}
@Override
public Object getItem(int position) {
return position;
}
// 擷取數量
@Override
public int getCount() {
return dirAllStrArr.size();
}
};
gridView1.setAdapter(baseAdapter);// 把擴充卡與網格視圖連結起來
gridView1.setOnItemClickListener(new OnItemClickListener() {// 點選網格元件的随意一張圖檔時候的事件
@Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int position,// position為點選的id
long arg3) {
Intent intent = new Intent(MainActivity.this,
ViewActivity.class);// 激活ViewActivity
Bundle bundle = new Bundle();
bundle.putString("imgPath", dirAllStrArr.get(position));// 傳遞點選的圖檔的id到ViewActivity
intent.putExtras(bundle);
startActivity(intent);
}
});
}
// 建立menu的方法,沒有該方法。不會在右上角設定菜單。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// 設定menu界面為res\menu\menu.xml
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
// 處理菜單事件
public boolean onOptionsItemSelected(MenuItem item) {
// 得到目前選中的MenuItem的ID,
int item_id = item.getItemId();
switch (item_id) {
// 設定id為menu_exit的菜單子項所要運作的方法。
case R.id.menu_exit:
System.exit(0);// 結束程式
break;
}
return true;
}
// 對實體button的監聽
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
finish();// 關閉這個Activity。
break;
}
return super.onKeyDown(keyCode, event);
}
}