コンテンツのダウンロード  

最終更新: 2013/7

このチュートリアルでは、コンテンツのダウンロード方法について学びます。
使用するCGIは command.cgi です。
このチュートリアルは別項の android Tutorial 2:コンテンツリストの取得 に基づいています。

現在のコンテンツリストを取得し ListView に表示します。
リストに表示されたフォルダをクリック(タップ)した場合、そのフォルダの中身を表示します。イメージファイルをクリック(タップ)した場合は、そのファイルをダウンロードし、画面に表示します。

レイアウトは、コンテンツリストの上にコンテンツ数を表示し、その上に現在のフォルダパスを表示します。
また、一つ上のフォルダへ戻るための Button も配置します(例. 現在のフォルダが 'DCIM/106 _05/' である場合は、 'DCIM/' へ移動します)。

このようにレイアウトを設定します:

displayed directory content list

コンテンツリストのイメージファイルをクリックすると、 画像はこのように表示されます:

display image in ImageView

表示されているとおり、Android 端末に画像ファイルがダウンロードされます。

アプリケーションを作成するために、次のファイルを作成します :

  • MainActivity.java
  • activity_main.xml
  • ImageViewActivity.java
  • activity_image_view.xml

重要: Android application は、デフォルトの状態ではインターネットにアクセスすることができません。
そのため、設定ファイル AndroidManifest.xml を変更し、権限を与える必要があります。
パス: [Project_Folder]/AndroidManifest.xml
AndroidManifest.xml に以下のコードを追加してください:

    <uses-permission android:name="android.permission.INTERNET" />

リストのレイアウト作成

ではまず、 activity_main.xml を記述して、レイアウトを決定します。
このファイルは、layout フォルダに配置されています。
パス: [Project_Folder]/res/layout/activity_main.xml

activity_main.xml に以下のように記述してください:

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cacheColorHint="#00000000"
        android:text="Back"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:scaleType="centerInside"
        android:text="Directory Name"
        android:textAlignment="center"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:scaleType="centerInside"
        android:text="Number of files"
        android:textAlignment="center"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:textSize="20sp" />

    <ListView
        android:id="@+id/listView1"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:cacheColorHint="#00000000"
        android:clickable="true"
        android:headerDividersEnabled="false" />

</LinearLayout>

イメージビューのレイアウト作成

次に、 activity_image_view.xml を記述して、レイアウトを決定します。
このファイルは、layout フォルダに配置されています。
パス: [Project_Folder]/res/layout/activity_image_view.xml

activity_image_view.xml に以下のように記述してください:

activity_image_view.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".ImageViewActivity" >          

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cacheColorHint="#00000000"
        android:text="Back"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

    <ImageView
       android:id="@+id/imageView1"
       android:layout_width="match_parent"
       android:layout_height="fill_parent"
       android:scaleType="centerInside"
       android:contentDescription="view image"
       android:text="image would be here" />
</LinearLayout>

イメージビューのアクティビティ作成

次に、 AndroidManifest.xml を記述して、アクティビティを追加します。
パス: [Project_Folder]/AndroidManifest.xml

AndroidManifest.xml の application タグに以下のように記述してください:

AndroidManifest.xml

    <activity
        android:name="com.example.android_tutorial_03.ImageViewActivity"
        android:label="@string/app_name" >
    </activity>

コンテンツリストの作成

さて、 MainActivity.java を修正します。
デフォルトでは次のようになります:

MainActivity.java

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}

初期化

リストの項目をクリックした場合の処理を記述するため、クラス宣言にOnClickListenerを追加します。

MainActivity.java (1)

public class MainActivity extends Activity implements AdapterView.OnItemClickListener {

使用予定のビューのうちの1つの宣言をし、その他のクラス変数の宣言や画面の初期設定を行います。
また、 onCreate(Bundle savedInstanceState) をオーバーライドし、アクティビティクラスの初期化も行います。
コンテンツリストの初期設定や一つ上のフォルダへ戻るための Buttonのクリックリスナーも設定します。

MainActivity.java (2)

    ListView listView;
    ImageView imageView;
    TextView currentDirText;
    TextView numFilesText;
    Button backButton;
    String rootDir = "DCIM";
    String directoryName = rootDir; // Initialize to rootDirectory
    ArrayAdapter<String> listAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Set buttons
        getWindow().setTitleColor(Color.rgb(65, 183, 216));
        backButton = (Button)findViewById(R.id.button1);
        backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), PorterDuff.Mode.SRC_IN);
        backButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(directoryName.equals(rootDir)) {
                    listRootDirectory();
                }
                else {
                    int index = directoryName.lastIndexOf("/");
                    directoryName = directoryName.substring(0, index);
                    listDirectory(directoryName);
                }
            }
        });     
        backButton.setEnabled(false); // Disable in root directory
        listRootDirectory();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);       
        return true;
    }
  • 17-32行目
    ユーザーが Button をクリックした際、一つ上のフォルダへ行けるように設定しています。 元々表示しているルートフォルダは、それより上のフォルダへは移動できないため、ボタンを無効にしています。

ルートのフォルダを"DCIM"としていますが(6行目)、こちらは同じように設定をする必要はありません。
販売されている多くのデジタルカメラが撮影した画像を "DCIM" フォルダへ格納しているので、ここでは "DCIM" を設定しました。

ルートフォルダのコンテンツ情報取得

上記で呼ばれている listRootDirectory()listDirectory(directoryName) は、FlashAir に処理を要求しコンテンツを取得しています。
listRootDirectory() は、単にクラス変数 directoryName にルートフォルダを設定し listDirectory(String dir) を呼び出しています:

MainActivity.java (3)

    public void listRootDirectory() {
        directoryName = rootDir;
        listDirectory(directoryName);
    }

その他のフォルダのコンテンツ情報取得

現在のフォルダがルートフォルダである場合に、back ボタンの制御を行います。

MainActivity.java (4)

    public void listDirectory(String dir)
    { // Prepare command directory path
        if(dir.equals(rootDir)) {
            backButton.setEnabled(false);
        }
        else {
            backButton.setEnabled(true);
        }
  • 3-8行目
    back ボタンの有効/無効を制御しています。
    ルートフォルダを表示中の場合は、それより上の階層へ移動できいないためです。

その結果、画面はこのようになります:

display image in ImageView

それ以外のフォルダを表示している場合は、ボタンは有効でありクリックすることができます。

ファイル数を取得するためには、以下のコマンドを使用します。

  • ファイル数は、 command.cgiop=101 とフォルダパスを指定することで取得できます。
    • コマンド: http://flashair/command.cgi?op=101&DIR=/DCIM
    • コマンドが返す情報: <NumberofItems>

ファイルデータを取得するためには、以下のコマンドを使用します。

  • ファイルデータは、 command.cgiop=100 とフォルダパスを指定することで取得できます。
    • コマンド: http://flashair/command.cgi?op=100&DIR=/DCIM
    • コマンドが返す情報: <Directory>,<Filename>,<Size>,<Attribute>,<Date>,<Time>

コマンドの実行には前項 Android Tutorial 2:コンテンツリストの取得 と同じく、 FlashAirRequest.java を使用します。

MainActivity.java (5)

        currentDirText = (TextView)findViewById(R.id.textView1);
        currentDirText.setText(dir + "/");
        // Fetch number of items in directory and display in a TextView
        dir = "/" + dir;
        ArrayList <NameValuePair> httpParams = new  ArrayList <NameValuePair> ();
        httpParams.add(new BasicNameValuePair("DIR", dir));
        dir = URLEncodedUtils.format (httpParams, "UTF-8" );
        numFilesText = (TextView)findViewById(R.id.textView2);
        // Fetch number of items in directory and display in a TextView
        new AsyncTask<String, Void, String>(){
            @Override
            protected String doInBackground(String... params) {
                String dir = params[0];
                String fileCount =  FlashAirRequest.getString("http://flashair/command.cgi?op=101&" + dir);
                return fileCount;
            }
            @Override
            protected void onPostExecute(String fileCount) {
                numFilesText.setText("Items Found: " + fileCount);                
            }
        }.execute(dir);
  • 7行目
    返される文字のエンコードは、ここでは UTF-8 を使用します。

command.cgiop=100 は指定したフォルダの以下の情報を返します:
<Directory>,<Filename>,<Size>,<Attribute>,<Date>,<Time>
今回使用するのは、ファイル名のみです。
CGIコマンド実行後、ファイル名のみを取り出すよう調整する必要があります。

また、クラスが現在とルートのフォルダを管理しているので、手動でフォルダ名をCGIに渡す必要はなく、代わりに関数から渡されたフォルダ名を使用します。

MainActivity.java (6)

        // Fetch list of items in directory and display in a ListView
        new AsyncTask<String, Void, ListAdapter>(){
            @Override
            protected ListAdapter doInBackground(String... params) {
                String dir = params[0];
                ArrayList <String> fileNames = new ArrayList <String>();                
                String files = FlashAirRequest.getString("http://flashair/command.cgi?op=100&" + dir);
                String[] allFiles = files.split("([,\n])"); // split by newline or comma
                for(int i = 2; i < allFiles.length; i= i + 6) {
                    if(allFiles[i].contains(".")) {
                        // File
                        fileNames.add(allFiles[i]);
                    }
                    else { // Directory, append "/"
                        fileNames.add(allFiles[i] + "/");
                    }
                }

                listAdapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, fileNames);

                return listAdapter;
            }
            @Override
            protected void onPostExecute(ListAdapter listAdapter) {
                // Set the file list to a widget
                listView = (ListView)findViewById(R.id.listView1);
                ColorDrawable divcolor = new ColorDrawable(Color.rgb(17, 19, 58));
                listView.setDivider(divcolor);
                listView.setDividerHeight(1);
                listView.setAdapter(listAdapter);
                listView.setOnItemClickListener(MainActivity.this);
            }
        }.execute(dir);
  • 8-17行目
    フォルダであるか、ファイルであるのかをチェックしています。 フォルダの場合は、表示する際最後に "/" を付加しています。これはフォルダの場合の標準的な記述方法です。
    さらにこの仕組は、コンテンツリストをクリックした際の、フォルダとファイルで処理を分けるためにも使用します。

コンテンツリストのクリックリスナー設定

MainActivity クラスは現時点では、コンテンツリストは表示されますがクリックすることができません。
このため、 OnItemClickListenerMainActivity に定義します。
この特別なクリックリスナーは、単にリストをクリックしたことを把握するだけではなく、どのコンテンツをクリックしたのかを識別します。

リストは次のように設定します:

  • フォルダをクリックした場合は、選択したフォルダのコンテンツを表示した画面を表示します。
  • ファイルをクリックした場合は、ファイルをダウンロードし、ダウンロードした画像を表示しした画面を表示します。

元の onItemClick は、 < AdapterView<?> l, View v, int position, long id > といった引数です。
オーバーライドするため、これらの引数も渡します。

MainActivity.java (7)

    @Override
    public void onItemClick(AdapterView<?> l, View v, int position, long id) {
        Object downloadFile = l.getItemAtPosition(position); // get item at clicked position in list of files
        if(downloadFile.toString().endsWith("/")) { // Directory, remove "/" and show content list
            String dirName = downloadFile.toString().substring(0, downloadFile.toString().length()-1); // all but the "/"
            directoryName = directoryName + "/" + dirName;
            listDirectory(directoryName);
        }
        else if( downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpg") || downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg")
            || downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpe") || downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".png") )
        { // Image file, download using ImageViewActivity
            Intent viewImageIntent = new Intent(this, ImageViewActivity.class);
            viewImageIntent.putExtra("downloadFile", downloadFile.toString());
            viewImageIntent.putExtra("directoryName", directoryName);
            MainActivity.this.startActivity(viewImageIntent); 
        }
    }
} // End MainActivity class
  • 12行目
    実際にファイルをダウンロードし表示するのは2つめのクラス ( class ImageViewActivity)なので、 Intent を作成します class ImageViewActivity は、ファイルをダウンロードし表示するので、ファイル名 ( downloadFile.toString()) と フォルダパス ( directoryName) にアクセス可能である必要があります。
  • 13-14行目
    Intentに、上記の値を設定します。これにより、 ImageViewActivity 生成時に、この値が渡されます。

イメージビューの作成

初期化

このクラスもまた Activity クラスを拡張したクラスです。
画像を表示するための ImageView と コンテンツリスト画面へ戻るための Button を設定します。
クラス宣言は次のようになります:

ImageViewActivity.java (1)

public class ImageViewActivity extends Activity {
    ImageView imageView;
    Button backButton;

onCreate(Bundle savedInstanceState)onCreateOptionsMenu(Menu menu) をオーバーライドする必要もあります。
onCreate(Bundle savedInstanceState) にて、 back ボタンの動作を設定します。

ImageViewActivity.java (2)

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image_view);
        getIntent();
        imageView = (ImageView)findViewById(R.id.imageView1);
        backButton = (Button)findViewById(R.id.button2);
        getWindow().setTitleColor(Color.rgb(65, 183, 216));
        backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), PorterDuff.Mode.SRC_IN);
        Bundle extrasData = getIntent().getExtras();
        String fileName = extrasData.getString("downloadFile");
        String directory = extrasData.getString("directoryName");
        downloadFile(fileName, directory);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.image_view, menu);
        return true;
    }
  • 10行目
    Intentを宣言した際 class MainActivity から渡したデータを読み込んでいます。
  • 11-12行目
    読み込んだ各値を保存しています。

ファイルのダウンロード

次に, FlashAir からファイルをダウンロードする機能を実装します。

CGIコマンドを実行し結果の Bitmap を返す、 getBitmap() を前項 Android Tutorial 2:コンテンツリストの取得 で作成した FlashAirRequest.java に追加します。

FlashAirRequest.java

    static public Bitmap getBitmap(String command) {            
        Bitmap resultBitmap = null;
        try{
            URL url = new URL(command);
            URLConnection urlCon = url.openConnection();
            urlCon.connect();
            InputStream inputStream = urlCon.getInputStream();
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] byteChunk = new byte[1024];
            int bytesRead = 0;
            while( (bytesRead = inputStream.read(byteChunk)) != -1) {
                byteArrayOutputStream.write(byteChunk, 0, bytesRead);
            }
            byte[] byteArray = byteArrayOutputStream.toByteArray();
            BitmapFactory.Options bfOptions = new BitmapFactory.Options();
            bfOptions.inPurgeable = true;
            resultBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, bfOptions);
            byteArrayOutputStream.close();
            inputStream.close();
        }catch(MalformedURLException e) {
            Log.e("ERROR", "ERROR: " + e.toString());
            e.printStackTrace();
        }
        catch(IOException e) {
            Log.e("ERROR", "ERROR: " + e.toString());
            e.printStackTrace();
        }
        return resultBitmap;                        
    }
  • 15-16行目
    メモリーを有効活用するために、解放可能な Bitmap を生成しています。

Intent により渡したファイル名とフォルダパスからパスを作成し、ファイルをダウンロードします。
ファイルのダウンロードには、 FlashAirRequest.java に追加した getBitmap()を使用します。

ImageViewActivity.java (3)

    void downloadFile(String downloadFile, String directory) {
        final ProgressDialog waitDialog;        
        // Setting ProgressDialog
        waitDialog = new ProgressDialog(this);
        waitDialog.setMessage("Now downloading...");
        waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        waitDialog.show();                
        // Download file
        new AsyncTask<String, Void, Bitmap>(){
            @Override
            protected Bitmap doInBackground(String... params) {
                String fileName = params[0];
                return FlashAirRequest.getBitmap(fileName);
            }
            @Override
            protected void onPostExecute(Bitmap resultBitmap) {
                waitDialog.dismiss();                                
                viewImage(resultBitmap);
            }
        }.execute("http://flashair/" + directory + "/" + downloadFile.toString());    
    }

画像を表示する

Bitmap イメージを読み込んだ後、上記の関数で viewImage() を呼び出すと、画像が表示されます。
この関数では画像の表示に ImageView を使用しています。

ImageViewActivity.java (4)

lined:

    void viewImage(Bitmap imageBitmap) {
        // Show image in ImageView
        backButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ImageViewActivity.this.finish();
            }
        });     
        if (imageBitmap == null) {
            imageView.setImageResource(R.drawable.ic_launcher);
        }
        else {
            imageView.setImageBitmap(imageBitmap);
        }
    }
} // End ImageViewActivity class

実行結果

画像はこのように表示されます:

This image shows image view

サンプルコード

android_tutorial_03.zip (539KB)

このサイトのサンプルコードは 二条項BSDライセンスで提供されています。