天天看點

【Android】記憶體卡圖檔讀取器,圖庫app

上一篇《【Android】讀取sdcard卡上的全部圖檔而且顯示,讀取的過程有進度條顯示》(​​點選打開連結​​)在真機上測試非常有問題。常常遇到記憶體溢出。卡死的情況。由于如今真機上的記憶體上,2G已經非常少見了,基本上都8G的樣子了。

由于把讀取出來的圖檔一次性地放到app上,而且讀取的過程中,又沒有正在讀取到哪個檔案,盡管可以在AVD安卓模拟器上完畢主要的功能,可是這個app非常不友好。

是以採用Handler、Message配合線程等安卓消息機制。完畢讀取過程,而且利用GridView把讀取到的圖檔顯示出來。改進這個記憶體卡圖檔讀取器,詳細效果例如以下,基本是可以媲美部分安卓系統自帶的圖庫了吧……就差個對目錄的分類而已了……

【Android】記憶體卡圖檔讀取器,圖庫app

一開始。在讀取的過程。有讀取進度的顯示,詳細讀取到哪個目錄。

這對于大記憶體卡非常有意義。我的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);
  }

}