Android用(ちょっと)本格的なミュージックプレーヤの開発 part1

ミュージックプレーヤ開発 part1 です。

今回はコンテントプロバイダを利用して端末に保存されている音楽ファイル
を取得してListViewに全て表示してみます。

コンテントプロバイダとは?

Androidでは様々なアプリケーションが独自にデータベースを
管理したり、ファイルを作成したりします。通常はこれらの
ファイルやデータベースはそれぞれのアプリケーションが
管理するので、他のアプリからアクセスするのは難しくなっています。

ですが、
連絡先や音楽データの管理情報などは多くのアプリケーションで
共有できたほうが便利です。 このために用意されている仕組みが
 コンテントプロバイダ です。

有益なデータを持っているアプリケーションがコンテントプロバイダを
設定すると、他のアプリからアクセスが可能になり、有用なデータを
一元的に管理できるようになるわけです。

コンテントプロバイダはアプリケーションからも登録できますが、
Androidでは共通データとして音楽や画像、動画や個人情報の一部を
コンテントプロバイダから取得可能になっています。
今回はこの機能を利用して音楽の一覧を取得します。



Android4.1以降では以下の権限に許可が必要です。
AndroidManifest.xml に以下を追加すれば問題なく動作します。
EXTERNAL_STORAGE(=SDカード)へのアクセス権限です。

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

製作開始

まっさらな状態からいきます。

音楽データ用クラス

まずはトラックを管理するためのクラスを作成していきます。
class Track を作成します。
このクラスが持つべきメンバは以下のとおりです。

        public long     id;             //コンテントプロバイダに登録されたID
        public long     albumId;        //同じくトラックのアルバムのID
        public long     artistId;       //同じくトラックのアーティストのID
        public String   path;           //実データのPATH
        public String   title;          //トラックタイトル
        public String   album;          //アルバムタイトル
        public String   artist;         //アーティスト名
        public Uri      uri;            // URI
        public long     duration;       // 再生時間(ミリ秒)
        public int      trackNo;        // アルバムのトラックナンバ

コンテントプロバイダの利用

コンテントプロバイダはデータベースに似たデータの管理を行なっています。
コンテントプロバイダにアクセスするには 事前に格納されているデータの仕様を
確認してなければいけません

 こちら で調べることができます

コンテントプロバイダにはレコード単位でアクセスして必要なデータをそれぞれ
取得しています。


_ID TITLE ARTIST ALBUM その他・・・
XX タイトル1 アーティスト1 アルバム1
YY タイトル2 アーティスト2 アルバム2
ZZ タイトル3 アーティスト3 アルバム3

この表の横一列ごとにアクセスできるようなイメージです。

では実際にアクセスしていきます。
アクセスにはデータベースと同様にクエリを用いて行います。

class Track に List<Track> を返すメソッド getItems() を作成します。

今回はContentResolver.query を用いて検索してみます。
ですが、その前に取得したいカラムのリストを作っておきます。

public static final String[] COLUMNS = {
                MediaStore.Audio.Media._ID,
                MediaStore.Audio.Media.DATA,
                MediaStore.Audio.Media.TITLE,
                MediaStore.Audio.Media.ALBUM,
                MediaStore.Audio.Media.ARTIST,
                MediaStore.Audio.Media.ALBUM_ID,
                MediaStore.Audio.Media.ARTIST_ID,
                MediaStore.Audio.Media.DURATION,
                MediaStore.Audio.Media.TRACK,
         };

以下、実際に取得します。

public static List getItems(Context activity) {

        List tracks = new ArrayList();
        ContentResolver resolver = activity.getContentResolver();
        Cursor cursor = resolver.query(
                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 
                        Track.COLUMNS, 
                        null,
                        null,
                        null
                                );
        while( cursor.moveToNext() ){
                if( cursor.getLong(cursor.getColumnIndex( MediaStore.Audio.Media.DURATION)) < 3000 ){continue;}
                tracks.add(new Track(cursor));
        }
        cursor.close();
        return tracks;
}

resolver.query には
 1:検索したいデータのURI(今回は外部ストレージの音楽データ)
 2:取得するカラム(先ほど指定)
 3~5:条件指定などに用いる(今回は使用しないため null)
を引数として与えます。

Cursor は データベースから帰ってくる 検索結果1レコードと対応します。
Cursorインターフェイスには検索結果のカーソルを次へ次へと移動する機能があるので
これを用いて検索結果をwhileで全て取得していきます。

cursor.get( String | Int | Long )(index)

で、実際にレコードから値を取得出来ます。
また getColumnIndexにて調べたいカラムのindexも取得できるので少し長いですが
上記のような書き方になります。

while の中ではまず 再生時間が3秒に満たないものを無視して、
次々と (List) tracks に 新しい Track を追加しています。

Trackのコンストラクタとして 以下のコードを追加します。

public  Track(Cursor cursor)
{       
        id              = cursor.getLong( cursor.getColumnIndex( MediaStore.Audio.Media._ID ));
        path            = cursor.getString( cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
        title           = cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.TITLE ));
        album           = cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.ALBUM ));
        artist          = cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.ARTIST ));
        albumId         = cursor.getLong( cursor.getColumnIndex( MediaStore.Audio.Media.ALBUM_ID ));
        artistId        = cursor.getLong( cursor.getColumnIndex( MediaStore.Audio.Media.ARTIST_ID ));
        duration        = cursor.getLong( cursor.getColumnIndex( MediaStore.Audio.Media.DURATION ));
        trackNo         = cursor.getInt( cursor.getColumnIndex( MediaStore.Audio.Media.TRACK ));
        uri             = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
}

以上で端末に保存されたトラック一覧を取得する部分が完成です。

続いて、カスタマイズしたListViewにて取得したトラックを一覧として
表示してみます。

ListViewに表示する内容ですが
今回は
 タイトル、アルバム、再生時間
とシンプルなものにします。

ListViewの要素用レイアウトを作成します。

res/layout/item_track.xml
に保存しました。

次にListViewをカスタムするためのアダプタを作成します。
ArrayAdapterを継承した ListTrackAdapter というクラスを作成して
以下のように実装します。 この部分は様々な入門書で取り上げられて
いるので詳しい解説は割愛します。

public class ListTrackAdapter extends ArrayAdapter<Track>{

        LayoutInflater mInflater;

        public ListTrackAdapter(Context context, List item){
                super(context, 0, item);
                mInflater =  (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );
        }

        @Override
        public View getView(int position, View convertView,ViewGroup parent){

                Track item = getItem(position); 
                ViewHolder holder;

                if(convertView==null){
                        convertView = mInflater.inflate(R.layout.item_track, null);
                        holder = new ViewHolder();
                        holder.trackTextView    = (TextView)convertView.findViewById(R.id.title);
                        holder.artistTextView   = (TextView)convertView.findViewById(R.id.artist);
                        holder.durationTextView = (TextView)convertView.findViewById(R.id.duration);
                        convertView.setTag(holder);
                }else{
                        holder = (ViewHolder) convertView.getTag();
                }

                long dm = item.duration/60000;
                long ds = (item.duration-(dm*60000))/1000;

                holder.artistTextView.setText(item.artist);
                holder.trackTextView.setText(item.title);
                holder.durationTextView.setText(String.format("%d:%02d",dm,ds));

                return convertView;     
        }

        static class ViewHolder{
                TextView  trackTextView;
                TextView  artistTextView;
                TextView  durationTextView;
        }

}

ArrayAdapterは getViewメソッドでListViewの各行に表示するアイテムを返すので
この部分をオーバーライドしよう。 ということを行なっています。
その際に先ほど作成したレイアウトファイルとList\の対応するアイテム から
カスタムしたViewを作成するようにしています。

また小ネタとしては Track の duration が ミリ秒なので 分:秒 の形になおしています。

これで全ての準備が整いました。
ここまで Hallo World のまま放置していたメインアクティビティ用のレイアウトファイル
(activity_main.xml)にListView配置します。

ここまで来たらあとは簡単です。
今までの準備が上手く行っていれば呼び出すのはとても簡単になっています。
自動で出力されているコードに必要な追加は たったの4行 です。

List tracks = Track.getItems(this);
ListView trackList = (ListView)findViewById(R.id.list);
ListTrackAdapter adapter = new ListTrackAdapter(this, tracks);
trackList.setAdapter(adapter);

1:Track.getItem() でトラック一覧を取得
2:最後に作成したListView用のViewを取得
3:tracks の情報からカスタムListView用のアダプタを作成
4:ListViewにアダプタをセット

以上です。

最後にエミュレータで実際に起動してみましょう。

はいこの通り、端末に保存されているトラックが一覧表示されました。
音楽プレーヤー用のライブラリ機能の基本のキ が出来ました。

次回は アルバムの一覧とアルバムアートの処理を扱います。

お疲れ様です。今日はここまでです!

おまけ

実際に音楽が保存された実機にてテストする場合は問題ありませんが、
エミュレータで動作を確かめる場合、そのままでは音楽データは保存されて
おりませんので、なにも表示されません。

これを防ぐためにはエミュレータ環境にインストールされているDev toolsから
事前に ダミーの音楽情報を登録しておく必要があります。

Dev Tools の Media Providerから
Insert 20 albums を選択するとランダムな名前のダミートラックが
端末に登録されて 試すことができるようになります。
注意が必要なのは実際のデータはもちろんないので再生部分などを
確かめようとするとエラーが起きるようになります。

実際に音が出る段階まで作ったら実機で確かめるか、
エミュレータにmp3ファイルを配置することになります。

コメントを追加する