Software/Android/アプリケーション開発テキスト

Chapter 5 : サービスを利用したアプリケーションの作成

 この章ではサービスを利用して簡易RSSリーダーを拡張していきます。サービスはAndroidが提供するアプリケーションコンポーネントの一つで、アクティビティのように画面表示を持たずバックグラウンドで動作します。

 Chapter 4で作成した簡易RSSリーダーでは取得ボタンのタップでRSSフィードを取得していましたが、サービスを利用することでバックグラウンドで定期的にRSSフィードを取得することが出来ます。

5-1. サービスクラスの作成

 まずはRSSReaderプロジェクトにバックグラウンドでRSSフィードを取得するサービスを追加します。

 android.app.ServiceをベースクラスとしたFeedServiceを生成し、スレッドの処理をこのクラスで行うためRunnableのimplementsといくつかの定数、メンバ変数を追加します(リスト5-1)。

  • リスト5-1 :
    public class FeedService extends Service implements Runnable {
         private static final String SERVICE_ACTION = "com.beatcraft.rssreader.SERVICE_ACTION";
         private static final String BROADCAST_MSG = "com.beatcraft.rssreader.BROADCAST_MSG";
    
         private boolean mWorking = false;
         private FeedList mListFG = null;
         private FeedList mListBG = null;

 定数SERVICE_ACTIONはこのサービスで取り扱うアクションを定義しています。実際にサービスがこのアクションを取り扱い出来るようにするためにはマニフェストファイルに記述する必要があります(後述)。定数BROADCAST_MSGはこのサービスが発行するブロードキャストインテントのアクションを定義しています。

 バックグラウンドでのフィード取得中に外部からリストを参照する可能性があるため、FeedListを二つ用意し、これを切り替える際の保護用にmWorkingというフラグも追加しています。

 続いてonStartメソッドを追加します(リスト5-2)。

  • リスト5-2 :
         @Override
         public void onStart(Intent intent, int startID) {
              super.onStart(intent, startID);
              Thread thread = new Thread(this);
              thread.run();
         }

 onStartはサービスが開始されると呼び出されるメソッドで、今回作成するサービスではこのタイミングでスレッドを作成し、このクラスをRunnableとして実行しています。

 アクティビティなどのコンポーネントから直接サービスの機能を呼び出して利用するには、サービスをバインドする必要があります。サービスをバインドするとonBindメソッドが呼ばれます(リスト5-3)。

  • リスト5-3 :
         @Override
         public IBinder onBind(Intent arg0) {
              return mIFeedServiceBinder;
         }

 IBinder型のメンバ変数mIFeedServiceBinderを返すことで、このサービスへの直接アクセスを提供しています。mIFeedServiceBinderは後ほど実装します。

 前述の通り、このクラスにRunnableインターフェースを実装してスレッドを処理するためrunメソッドを追加します(リスト5-4)。

  • リスト5-4 :
         @Override
         public void run() {
              RSSReaderApplication app = (RSSReaderApplication) getApplication();
              mListBG = new FeedList();
              if (mListBG.get(app) > 0) {
                   lock();
                   mListFG = mListBG;
                   unlock();
                   Intent bcast = new Intent(BROADCAST_MSG);
                   getBaseContext().sendBroadcast(bcast);
              }
    
              Intent ac = new Intent(SERVICE_ACTION);
              PendingIntent pi = PendingIntent.getService(this, 0, ac, 0);
              AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
              long at = System.currentTimeMillis() + 60000;
              am.set(AlarmManager.RTC, at, pi);
         }

 runメソッドではまず、RSSReaderApplicationクラスのインスタンスを取得し、バックグラウンド用のFeedListに引渡してRSSフィードの取得を行います。フィードの取得に成功したらフォアグラウンド用FeedListを置き換えますが、他のコンポーネントがサービスバインダーを利用してRSSフィードの取得を行う際、参照中にリストが置き換わることのないよう、lock/unlockで排他制御を行なっています。

 置き換えが終わったら定数BROADCAST_MSGをアクションに設定したインテントを生成し、sendBroadcastメソッドでブロードキャストを発行します。

 続いて定数SERVICE_ACTIONをアクションとして設定したインテントを基にペンディングインテント(PendingIntent)を生成しています。ペンディングインテントとは、通常のインテントのように即時ではなく、指定したタイミングで発行するためのインテントです。
 ペンディングインテントの発行先としてこのサービスを指定し、現時刻から60秒後に発行されるよう、アラームマネージャ(AlarmManager)にセットします。

 これによりこのサービスが60秒毎に呼び出され、定期的なRSSフィードの受信を実現しています。

 サービスバインダーを追加します(リスト5-5)。IFeedServiceはこのサービスが外部コンポーネントに提供するメソッドインターフェースで、後ほど作成するAIDLファイルで定義したメソッドの実装をこれに含めます。

  • リスト5-5 :
         // service binder
         private final IFeedService.Stub mIFeedServiceBinder = new IFeedService.Stub() {
              @Override
              public void lockList() {
                   lock();
              }
             
              @Override
              public void unlockList() {
                   unlock();
              }
    
              @Override
              public int getFeedCount() throws RemoteException {
                   if (mListFG != null) {
                        return mListFG.count();
                   }
                   return 0;
              }
    
              @Override
              public int getItemType(int index) {
                   if (mListFG != null) {
                        ArrayList<FeedItem> list = mListFG.getList();
                        return list.get(index).itemType();
                   }
                   return 0;
              }
    
              @Override
              public String getFeedTitle(int index) {
                   if (mListFG != null) {
                        ArrayList<FeedItem> list = mListFG.getList();
                        return list.get(index).feedTitle();
                   }
                   return "";
              }
    
              @Override
              public String getArticleTitle(int index) {
                   if (mListFG != null) {
                        ArrayList<FeedItem> list = mListFG.getList();
                        return list.get(index).articleTitle();
                   }
                   return "";
              }
    
              @Override
              public String getPubDate(int index) {
                   if (mListFG != null) {
                        ArrayList<FeedItem> list = mListFG.getList();
                        return list.get(index).pubDate();
                   }
                   return "";
              }
    
              @Override
              public String getDescription(int index) {
                   if (mListFG != null) {
                        ArrayList<FeedItem> list = mListFG.getList();
                        return list.get(index).description();
                   }
                   return "";
              }
    
              @Override
              public String getLink(int index) {
                   if (mListFG != null) {
                        ArrayList<FeedItem> list = mListFG.getList();
                        return list.get(index).link();
                   }
                   return "";
              }
         };

 lockList/unlockListはRSSフィードリストを参照する際に排他制御を行うために使用します。getFeedCountはフォラグラウンドになっているRSSフィードリストに含まれるフィード数を返し、その他の各種getメソッドで指定されたインデックスのフィード情報を外部コンポーネントから取得出来るようにしています。

  • リスト5-6 :
         private synchronized void lock() {
              while (mWorking) {
                   try {
                        wait();
                   }
                   catch (Exception e) {
                   }
              }
              mWorking = true;
         }
    
         private synchronized void unlock() {
              mWorking = false;
         }

 最後に排他制御に使用するlock/unlockメソッドを追加します(リスト5-6)。lockではmWorkingがtrueの間waitループを行い、他スレッドで参照している間は以後の処理を行わないようブロックします。

 以上でサービスクラスの作成は完了です。引き続きAIDLファイルの作成とマニフェスト修正を行います。AIDLはAndroid Interface Definition Language(Androidインターフェース定義言語)の略で、プロセス間通信に使用するインターフェースを定義するものです。

 Package ExplorerでRSSReaderのsrc/com.beatcraft.rssreaderを右クリックし、メニューから「New > File」を選択して下さい。"New File"ダイアログが表示されたら、File nameに「IFeedService.aidl」と入力して「Finish」ボタンをクリックします。

  • リスト5-7 IFeedService.aidl :
    package com.beatcraft.rssreader;
    
    interface IFeedService {
         void lockList();
         void unlockList();
         int getFeedCount();
         int getItemType(int index);
         String getFeedTitle(int index);
         String getArticleTitle(int index);
         String getPubDate(int index);
         String getDescription(int index);
         String getLink(int index);
    }

 作成されたIFeedService.aidlファイルを開き、リスト5-7のように編集します。先ほど追加したmIFeedServiceBinderで実装しているメソッドの宣言をここで行なっています。

 最後にRSSReaderのマニフェストファイル(AndroidManifest.xml)を開いて、application要素の子要素として今回作成したサービスを追加します(リスト5-8)。

  • リスト5-8 :
            <service android:name=".FeedService" >
                <intent-filter >
                    <action android:name="com.beatcraft.rssreader.SERVICE_ACTION" />
                </intent-filter>
            </service>

 インテントフィルタとして、サービスクラス内で定義した定数SERVICE_ACTIONと同じ内容を保つアクションがリストされていることに注目して下さい。このフィルタを追加することで、AlarmMangerを利用して時間指定をしたペンディングインテントをこのサービスが受け取ることが可能になります。

5-2. ブロードキャストレシーバの作成

 続いて、サービスが発行したブロードキャストを受信するブロードキャストレシーバを作成していきます。

 android.content.BroadcastReceiverをベースクラスとしてFeedReceiverを生成し、メンバ変数とコンストラクタ、onReceiveメソッドを追加します(リスト5-9)。

  • リスト5-9 FeedReceiver.java :
    public class FeedReceiver extends BroadcastReceiver {
         IFeedReceiver mReceiver;
    
         public FeedReceiver(IFeedReceiver receiver) {
              mReceiver = receiver;
         }
    
         @Override
         public void onReceive(Context context, Intent intent) {
              mReceiver.feedReceived();
         }
    }

 IFeedReceiverは受信したブロードキャストを実際に処理するためのインターフェースクラスで、コンストラクタで受け取ったIFeedReceiverのfeedReceivedメソッドをonReceiveで呼び出すようにしています。

 続いてIFeedReceiverを作成するため、Package ExplorerでRSSReaderのsrc/com.beatcraft.rssreaderを右クリックし、メニューから「New > Interface」を選択して下さい。"New Java Interface"ダイアログが表示されたら、Nameに「IFeedReceiver」と入力して「Finish」ボタンをクリックします。

  • リスト5-10 IFeedReceiver.java :
    package com.beatcraft.rssreader;
    
    public interface IFeedReceiver {
        void feedReceived();
    }

 作成されたIFeedReceiver.javaを開き、feedReceivedメソッドの宣言を追加します(リスト5-10)。

 以上でブロードキャストレシーバは完成です。

5-3. サービスを利用する

 サービスとブロードキャストレシーバが完成したので、ここからはRSSReaderActivityをサービスを利用する形に修正していきます。

  • リスト5-11 :
    public class RSSReaderActivity extends ListActivity implements View.OnClickListener, IFeedReceiver, ITaskEntity {
         private static final String BROADCAST_MSG = "com.beatcraft.rssreader.BROADCAST_MSG";
    
         private FeedList mList = null;
         private IntentFilter mIFilter;
         private IFeedService mIFeedServiceBind = null;
         private ServiceConnection mServiceConnection = new ServiceConnection() {
              @Override
              public void onServiceConnected(ComponentName name, IBinder service) {
                   // インターフェースを取得
                   mIFeedServiceBind = IFeedService.Stub.asInterface(service);
              }
              @Override
              public void onServiceDisconnected(ComponentName name) {
                   //
              }
         };

 IFeedReceiverのimplementsを追加し、ブロードキャスト受信用のアクション文字列定数BROADCAST_MSG、インテントフィルタとサービスバインダー、サービスコネクションをメンバ変数に追加しています。サービスコネクションはサービスとの接続を管理する際に使用され、接続時にインターフェースバインダーを取得しメンバ変数に設定しています。
 引き続きITaskEntityがimplementsされていますが、これは従来のHttpAccessTaskではなく、今回新規に作成するListGenerateTaskで使用します。ListGenerateTaskについては後ほど解説します。

 ボタンによるフィードの手動取得を廃止するため、onCreateでボタンにリスナーを設定している箇所(リスト5-12)、onClickで取得ボタンを処理している箇所(リスト5-13)を削除します。これにあわせ、レイアウトファイルmain.xmlの「取得」ボタン、strings.xmlの文字列リソースget_feedも削除して下さい。

  • リスト5-12 :
            button = (Button) findViewById(R.id.Get);
            button.setOnClickListener(this);

  • リスト5-13 :
              case R.id.Get:
                   mList = null;
                   HttpAccessTask task = new HttpAccessTask(this);
                   task.execute(this);
                   break;

 続いてonCreateメソッドの最後に、ブロードキャストレシーバの生成と登録、サービスの開始とバインド処理を追加します(リスト5-14)。

  • リスト5-14 :
            FeedReceiver recv = new FeedReceiver(this);
            mIFilter = new IntentFilter(BROADCAST_MSG);
            registerReceiver(recv, mIFilter);       
    
            Intent intent = new Intent(getBaseContext(), FeedService.class);
            startService(intent);
            bindService(intent, mServiceConnection, BIND_AUTO_CREATE);

 先ほど実装したFeedReceiverを生成し、文字列定数BROADCAST_MSGをアクションとして設定したインテントフィルタとともにregistarReceiverメソッドでブロードキャストレシーバとして登録します。BROADCAST_MSGアクションはサービスがブロードキャストを発行する際にも使用していたことを思い出してください。これにより、このブロードキャストレシーバがFeedServiceの発行するブロードキャストを受け取ることが可能になります。

 FeedServiceを明示的に指定したインテントを生成し、startServiceメソッドでサービスの開始を行い、bindServiceでこのアクティビティとサービスをバインドしています。

 インターフェースIFeedReceiver唯一のメソッドであるfeedReceivedを追加します。

  • リスト5-15 :
         @Override
         public void feedReceived() {
              ListGenerateTask task = new ListGenerateTask();
              task.execute(this);
         }

 サービスがRSSフィードの取得を完了し、ブロードキャストを発行するとブロードキャストレシーバを通じてこのメソッドが呼び出されます。サービスが取得したRSSフィードを読み取ってリスト化するため、ListGenerateTaskを実行します。

 ListGenerateTaskは後ほど作成しますが、HttpAccessTask同様にバックグラウンド処理としてbackgroundProc、後処理としてpostProcメソッドを呼び出します。HttpAccessTaskから呼び出される両メソッドを置き換える形で、ListGenerateTask用に修正を加えていきます。

 修正後のbackgroundProcメソッドがリスト5-16になります。

  • リスト5-16 :
         @Override
         public void backgroundProc() {
              int count = 0;
              mList = new FeedList();
             if (mIFeedServiceBind != null) {
                  try {
                       mIFeedServiceBind.lockList();
                       count = mIFeedServiceBind.getFeedCount();
    
                       FeedItem item;
                       for (int i = 0; i < count; ++i) {
                            int type = mIFeedServiceBind.getItemType(i);
                            item = new FeedItem(type);
                            item.setFeedTitle(mIFeedServiceBind.getFeedTitle(i));
                            item.setArticleTitle(mIFeedServiceBind.getArticleTitle(i));
                            item.setPubDate(mIFeedServiceBind.getPubDate(i));
                            item.setDescription(mIFeedServiceBind.getDescription(i));
                            item.setLink(mIFeedServiceBind.getLink(i));
                            mList.getList().add(item);
                       }
                       mIFeedServiceBind.unlockList();
                  }
                  catch (RemoteException e) {
                       e.printStackTrace();
                  }
             }
         }

 空のFeedListを生成したら、サービスバインダーのlockListによりサービスが保持するRSSフィードリストをロックします。これにより、リストの取得中にリストの差し替えがおきることを防いでいます。
 ロックに成功したらフィードの数を取得し、フィード数分のフィード情報をサービスから取得してFeedItemを生成、先程作成したFeedListに追加します。全てのフィードを追加し終えたら、unlockListでサービスのRSSフィードリストをアンロックします。

  • リスト5-17 :
         @Override
         public void postProc() {
              if (mList.count() > 0) {
                   ArrayList<FeedItem> list = mList.getList();
                   if (list != null) {
                        FeedAdapter adapter = new FeedAdapter(this, list);
                        setListAdapter(adapter);
                   }
              }
         }

 postProc(リスト5-17)ではバックグラウンドで生成したリストをチェックし、フィード数が0でなければFeedAdapterを生成し、アクティビティのリストに設定します。これによりサービスがバックグラウンドで取得したRSSフィードがリストに表示されます。

 最後にListGenerateTaskを作成します。ベースクラスをAsyncTask、テンプレートをITaskEntity, Integer, VoidとしてListGenerateTaskを生成し、リスト5-18のように修正します。

  • リスト5-18 :
    package com.beatcraft.rssreader;
    
    import android.os.AsyncTask;
    
    public class ListGenerateTask extends AsyncTask<ITaskEntity, Integer, Void> {
         private ITaskEntity mITaskEntity;
    
         @Override
         protected Void doInBackground(ITaskEntity... params) {
              mITaskEntity = params[0];
              mITaskEntity.backgroundProc();
              return null;
         }
    
         @Override
         protected void onPostExecute(Void v) {
              mITaskEntity.postProc();
         }
    }

 実際の処理はアクティビティに実装されたbackgroundProc/postProcが行うため、HttpAccessTaskとの違いはプログレスダイアログの有無のみとなっています。

 以上で全ての実装は完了です。実行して動作を確認してみて下さい。

5-4. サービス版RSSReader全ソースコード/AIDLファイル/XMLファイル

  • リスト5-19 RSSReaderApplication.java :
    package com.beatcraft.rssreader;
    
    import android.app.Application;
    import android.content.SharedPreferences;
    import android.content.SharedPreferences.Editor;
    
    public class RSSReaderApplication extends Application {
        private static final String CONF_NAME = "rssreader.conf";
    
        public static final int DEFAULT_NUM_OF_GET = 3;
        public static final int NUM_OF_FEED = 10;
    
        private int mNumOfGet = DEFAULT_NUM_OF_GET;
        private String mFeedURL[];
    
    
        @Override
        public void onCreate() {
            mFeedURL = new String[NUM_OF_FEED];
            loadConfig();
        }
    
        public int numberOfGet() {
            return mNumOfGet;
        }
        public void setNumberOfGet(int numOfGet) {
            mNumOfGet = numOfGet;
        }
    
        public String feedURL(int index) {
            if ((index < 0) || (10 <= index)) {
                return null;
            }
            return mFeedURL[index];
        }
        public void setFeedURL(int index, String url) {
            if ((index < 0) || (NUM_OF_FEED <= index)) {
                return;
            }
            mFeedURL[index] = url;
        }
        public void clearFeedURL() {
            for (int i = 0; i < NUM_OF_FEED; ++i) {
                mFeedURL[i] = "";
            }
        }
    
        public void loadConfig() {
            SharedPreferences pref = getSharedPreferences(CONF_NAME, MODE_PRIVATE);
            mNumOfGet = pref.getInt("NumOfGet", DEFAULT_NUM_OF_GET);
            for (int i = 0; i < NUM_OF_FEED; ++i) {
                String key = String.format("FeedURL%02d", (i + 1));
                mFeedURL[i] = pref.getString(key, "");
            }
        }
    
        public void saveConfig() {
            SharedPreferences pref = getSharedPreferences(CONF_NAME, MODE_PRIVATE);
            Editor pe = pref.edit();
    
            pe.putInt("NumOfGet", mNumOfGet);
            for (int i = 0; i < NUM_OF_FEED; ++i) {
                String key = String.format("FeedURL%02d", (i + 1));
                pe.putString(key, mFeedURL[i]);
            }
            pe.commit();
        }
    }

  • リスト5-20 RSSReaderActivity.java :
    package com.beatcraft.rssreader;
    
    import java.util.ArrayList;
    
    import android.app.ListActivity;
    import android.content.ComponentName;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.view.View;
    import android.view.Window;
    import android.widget.AdapterView;
    import android.widget.Button;
    
    public class RSSReaderActivity extends ListActivity implements View.OnClickListener, IFeedReceiver, ITaskEntity {
        private static final String BROADCAST_MSG = "com.beatcraft.rssreader.BROADCAST_MSG";
    
        private FeedList mList = null;
        private IntentFilter mIFilter;
        private IFeedService mIFeedServiceBind = null;
        private ServiceConnection mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // インターフェースを取得
                mIFeedServiceBind = IFeedService.Stub.asInterface(service);
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
                //
            }
        };
    
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            setContentView(R.layout.main);
    
            Button button;
            button = (Button) findViewById(R.id.Config);
            button.setOnClickListener(this);
    
            getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
                    FeedItem item = (FeedItem) getListView().getItemAtPosition(pos);
                    Intent intent = new Intent(getApplicationContext(), com.beatcraft.rssreader.DescActivity.class);
                    intent.putExtra("FeedTitle", item.feedTitle());
                    intent.putExtra("ArticleTitle", item.articleTitle());
                    intent.putExtra("PubDate", item.pubDate());
                    intent.putExtra("Description", item.description());
                    intent.putExtra("Link", item.link());
                    startActivity(intent);
                }
            });
    
            FeedReceiver recv = new FeedReceiver(this);
            mIFilter = new IntentFilter(BROADCAST_MSG);
            registerReceiver(recv, mIFilter);        
    
            Intent intent = new Intent(getBaseContext(), FeedService.class);
            startService(intent);
            bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
        }
    
    
        @Override
        public void onClick(View view) {
            int id = view.getId();
    
            switch (id) {
            case R.id.Config:
                showConfig();
                break;
            }
        }
    
        @Override
        public void feedReceived() {
            ListGenerateTask task = new ListGenerateTask();
            task.execute(this);
        }
    
        @Override
        public void backgroundProc() {
            int count = 0;
            mList = new FeedList();
            if (mIFeedServiceBind != null) {
                try {
                    mIFeedServiceBind.lockList();
                    count = mIFeedServiceBind.getFeedCount();
    
                    FeedItem item;
                    for (int i = 0; i < count; ++i) {
                        int type = mIFeedServiceBind.getItemType(i);
                        item = new FeedItem(type);
                        item.setFeedTitle(mIFeedServiceBind.getFeedTitle(i));
                        item.setArticleTitle(mIFeedServiceBind.getArticleTitle(i));
                        item.setPubDate(mIFeedServiceBind.getPubDate(i));
                        item.setDescription(mIFeedServiceBind.getDescription(i));
                        item.setLink(mIFeedServiceBind.getLink(i));
                        mList.getList().add(item);
                    }
                    mIFeedServiceBind.unlockList();
                }
                catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        public void postProc() {
            if (mList.count() > 0) {
                ArrayList<FeedItem> list = mList.getList();
                if (list != null) {
                    FeedAdapter adapter = new FeedAdapter(this, list);
                    setListAdapter(adapter);
                }
            }
        }
    
        private void showConfig() {
            Intent intent = new Intent(getApplicationContext(), com.beatcraft.rssreader.ConfigActivity.class);
            startActivity(intent);
        }
    }

  • リスト5-21 DescActivity.java :
    package com.beatcraft.rssreader;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.View;
    import android.view.Window;
    import android.webkit.WebView;
    import android.widget.Button;
    import android.widget.TextView;
    
    public class DescActivity extends Activity implements View.OnClickListener {
        private static final String WEBVIEW_BEGIN =
            "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" /></head><body bgcolor=\"#f6f6f6\">";
        private static final String WEBVIEW_LINK = "<p><a href=\"%s\">%s</a></p>";
        private static final String WEBVIEW_END = "</body></html>";
    
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            setContentView(R.layout.description);
    
            Button button = (Button) findViewById(R.id.Back);
            button.setOnClickListener(this);
    
            Intent intent = getIntent();
            if (intent != null) {
                String tmp = "";
                String desc = "";
                TextView tv;
    
                tv = (TextView) findViewById(R.id.FeedTitle);
                tmp = intent.getStringExtra("FeedTitle");
                tv.setText(tmp);
                tv = (TextView) findViewById(R.id.ArticleTitle);
                tmp = intent.getStringExtra("ArticleTitle");
                tv.setText(tmp);
                tv = (TextView) findViewById(R.id.PubDate);
                tmp = intent.getStringExtra("PubDate");
                tv.setText(tmp);
    
                WebView wv = (WebView) findViewById(R.id.Description);
                desc = WEBVIEW_BEGIN;
                tmp = intent.getStringExtra("Link");
                desc += String.format(WEBVIEW_LINK, tmp, tmp);
                tmp = intent.getStringExtra("Description");
                desc += tmp + WEBVIEW_END;
                wv.loadDataWithBaseURL("about:blank", desc, "text/html", "utf-8", null);
            }
        }
    
        @Override
        public void onClick(View view) {
            if (view.getId() == R.id.Back) {
                finish();
            }
        }
    }

  • リスト5-22 ConfigActivity.java :
    package com.beatcraft.rssreader;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.View;
    import android.view.Window;
    import android.view.WindowManager.LayoutParams;
    import android.widget.ArrayAdapter;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.Spinner;
    
    public class ConfigActivity extends Activity implements View.OnClickListener {
        public static final int REQUEST_RECOMMEND = 1234;
        private static final int NUM_OF_GET_LIST[] = {1, 3, 5};
    
        private Spinner mNumOfGet;
        private EditText mFeedURL[];
    
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            this.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
            setContentView(R.layout.config);
    
            RSSReaderApplication app = (RSSReaderApplication) getApplication();
    
            ArrayAdapter<CharSequence> adapter;
            adapter = ArrayAdapter.createFromResource(this, R.array.number_of_get, android.R.layout.simple_spinner_item);
            adapter.setDropDownViewResource(
                android.R.layout.simple_spinner_dropdown_item);
            mNumOfGet = (Spinner) findViewById(R.id.NumOfGet);
            mNumOfGet.setAdapter(adapter);
            for (int i = 0; i < 3; ++i) {
                if (NUM_OF_GET_LIST[i] == app.numberOfGet()) {
                    mNumOfGet.setSelection(i);
                    break;
                }
            }
    
            mFeedURL = new EditText[RSSReaderApplication.NUM_OF_FEED];
            mFeedURL[0] = (EditText) findViewById(R.id.FeedURL01);
            mFeedURL[1] = (EditText) findViewById(R.id.FeedURL02);
            mFeedURL[2] = (EditText) findViewById(R.id.FeedURL03);
            mFeedURL[3] = (EditText) findViewById(R.id.FeedURL04);
            mFeedURL[4] = (EditText) findViewById(R.id.FeedURL05);
            mFeedURL[5] = (EditText) findViewById(R.id.FeedURL06);
            mFeedURL[6] = (EditText) findViewById(R.id.FeedURL07);
            mFeedURL[7] = (EditText) findViewById(R.id.FeedURL08);
            mFeedURL[8] = (EditText) findViewById(R.id.FeedURL09);
            mFeedURL[9] = (EditText) findViewById(R.id.FeedURL10);
            for (int i = 0; i < RSSReaderApplication.NUM_OF_FEED; ++i) {
                mFeedURL[i].setText(app.feedURL(i));
            }
    
            Button button;
            button = (Button) findViewById(R.id.Recommend);
            button.setOnClickListener(this);
            button = (Button) findViewById(R.id.Cancel);
            button.setOnClickListener(this);
            button = (Button) findViewById(R.id.Regist);
            button.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
            int id = view.getId();
    
            switch (id) {
            case R.id.Recommend:
                showRecommend();
                break;
    
            case R.id.Cancel:
                finish();
                break;
    
            case R.id.Regist:
                regist();
                break;
            }
        }
    
        protected void onActivityResult(int reqCode, int result, Intent data) {
            super.onActivityResult(reqCode, result, data);
    
            if (result == RESULT_OK) {
                if (reqCode == REQUEST_RECOMMEND) {
                    int count = 0;
                    count = data.getIntExtra("RecommendCount", 0);
                    for (int i = 0; i < count; ++i) {
                        String key = "";
                        String url = "";
                        key = String.format("Recommend%02d", (i + 1));
                        url = data.getStringExtra(key);
                        if (url.equals("") == false) {
                            for (int j = 0; j < RSSReaderApplication.NUM_OF_FEED; ++j) {
                                String tmp = mFeedURL[j].getText().toString().trim();
                                if (tmp.equals("") == true) {
                                    mFeedURL[j].setText(url);
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
    
        private void showRecommend() {
            Intent intent = new Intent(getApplicationContext(), com.beatcraft.rssreader.RecommendActivity.class);
            startActivityForResult(intent, REQUEST_RECOMMEND);
        }
    
        private void regist() {
            RSSReaderApplication app = (RSSReaderApplication) getApplication();
            app.setNumberOfGet(NUM_OF_GET_LIST[mNumOfGet.getSelectedItemPosition()]);
            app.clearFeedURL();
            for (int i = 0, j = 0; i < RSSReaderApplication.NUM_OF_FEED; ++i) {
                String url = mFeedURL[i].getText().toString().trim();
                if (url.equals("") == false) {
                    app.setFeedURL(j++, url);
                }
            }
            app.saveConfig();
            finish();
        }
    }

  • リスト5-23 RecommendActivity.java :
    package com.beatcraft.rssreader;
    
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    import org.apache.http.HttpResponse;
    import org.apache.http.HttpStatus;
    import org.apache.http.NameValuePair;
    import org.apache.http.auth.AuthScope;
    import org.apache.http.auth.Credentials;
    import org.apache.http.auth.UsernamePasswordCredentials;
    import org.apache.http.client.entity.UrlEncodedFormEntity;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.message.BasicNameValuePair;
    import org.apache.http.protocol.HTTP;
    import org.xmlpull.v1.XmlPullParser;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.util.Xml;
    import android.view.View;
    import android.view.Window;
    import android.widget.ArrayAdapter;
    import android.widget.Button;
    import android.widget.CheckBox;
    import android.widget.Spinner;
    
    public class RecommendActivity extends Activity implements View.OnClickListener, ITaskEntity {
        private static final String GENRE[] = {"news", "music", "movie"};
        private static final String POST_DOMAIN = "labs.beatcraft.com";
        private static final String POST_PATH = "/ja/androidtext/recommend.php";
        private static final String POST_USER = "beatandroid";
        private static final String POST_PASS = "sample";
        private static final int NUM_OF_RECOMMEND = 3;
    
        private Spinner mGenre;
        private CheckBox mFeedCheck[];
        private String mRecommendTitle[];
        private String mRecommendURL[];
        private Button mAddFeed;
        private int mStat = -1;
    
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            setContentView(R.layout.recommend);
    
            ArrayAdapter<CharSequence> adapter;
            adapter = ArrayAdapter.createFromResource(this, R.array.genre, android.R.layout.simple_spinner_item);
            adapter.setDropDownViewResource(
                android.R.layout.simple_spinner_dropdown_item);
            mGenre = (Spinner) findViewById(R.id.Genre);
            mGenre.setAdapter(adapter);
    
            Button button;
            button = (Button) findViewById(R.id.Search);
            button.setOnClickListener(this);
            button = (Button) findViewById(R.id.Back);
            button.setOnClickListener(this);
            mAddFeed = (Button) findViewById(R.id.AddFeed);
            mAddFeed.setOnClickListener(this);
    
            mFeedCheck = new CheckBox[NUM_OF_RECOMMEND];
            mFeedCheck[0] = (CheckBox) findViewById(R.id.Feed01);
            mFeedCheck[1] = (CheckBox) findViewById(R.id.Feed02);
            mFeedCheck[2] = (CheckBox) findViewById(R.id.Feed03);
            mRecommendTitle = new String[NUM_OF_RECOMMEND];
            mRecommendURL = new String[NUM_OF_RECOMMEND];
        }
    
        @Override
        public void onClick(View view) {
            int id = view.getId();
    
            switch (id) {
            case R.id.Search:
                HttpAccessTask task = new HttpAccessTask(this);
                task.execute(this);        
                break;
    
            case R.id.Back:
                finish();
                break;
    
            case R.id.AddFeed:
                addFeed();
                break;
            }
        }
    
        @Override
        public void backgroundProc() {
            for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
                mRecommendTitle[i] = "";
                mRecommendURL[i] = "";
            }
            mStat = get(GENRE[mGenre.getSelectedItemPosition()]);
        }
    
        @Override
        public void postProc() {
            if (mStat == 0) {
                for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
                    mFeedCheck[i].setText(mRecommendTitle[i]);
                    mFeedCheck[i].setVisibility(View.VISIBLE);
                    mFeedCheck[i].setChecked(false);
                }
                mAddFeed.setVisibility(View.VISIBLE);
            }
            else {
                for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
                    mFeedCheck[i].setText("");
                    mFeedCheck[i].setVisibility(View.INVISIBLE);
                    mFeedCheck[i].setChecked(false);
                }
                mAddFeed.setVisibility(View.INVISIBLE);
            }
        }
    
        private int get(String genre) {
            String url = "http://" + POST_DOMAIN + POST_PATH;
    
            DefaultHttpClient client = new DefaultHttpClient();
            if (client != null) {
                client.getParams().setParameter("http.socket.timeout", new Integer(15000));
                HttpPost method = null;
                try {
                    method = new HttpPost(url);
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
                if (method == null) {
                    return -1;
                }
                HttpResponse response = null;
                try {
                    List<NameValuePair> pair = new ArrayList<NameValuePair>();
                    pair.add(new BasicNameValuePair("genre", genre));
                    method.setEntity(new UrlEncodedFormEntity(pair, HTTP.UTF_8));
    
                    Credentials cred = new UsernamePasswordCredentials(POST_USER, POST_PASS);
                    client.getCredentialsProvider().setCredentials(new AuthScope(POST_DOMAIN, 80), cred);
    
                    response = client.execute(method);
                    int ret = response.getStatusLine().getStatusCode();
                    if (ret == HttpStatus.SC_OK) {
                        InputStream is = response.getEntity().getContent();
                        return parse(is);
                    }
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
                finally {
                    client.getConnectionManager().shutdown();
                }
            }
            return -1;
        }
    
        private int parse(InputStream is) {
            int count = 0;
            boolean inFeed = false;
    
            XmlPullParser p = Xml.newPullParser();
            try {
                p.setInput(is, null);
                int event = p.getEventType();
                while (event != XmlPullParser.END_DOCUMENT) {
                    String elem = null;
                    String tmp = null;
    
                    switch (event) {
                    case XmlPullParser.START_TAG:
                        elem = p.getName();
                        if (elem.equals("feed") == true) {
                            if (inFeed == true) {
                                count++;
                                if (count >= NUM_OF_RECOMMEND) {
                                    return 0;
                                }
                            }
                            inFeed = true;
                        }
                        else if (elem.equals("title") == true) {
                            tmp = p.nextText();
                            if (tmp != null) {
                                mRecommendTitle[count] = tmp;
                            }
                        }
                        else if (elem.equals("url") == true) {
                            tmp = p.nextText();
                            if (tmp != null) {
                                mRecommendURL[count] = tmp;
                            }
                        }
                        break;
    
                    case XmlPullParser.END_TAG:
                        elem = p.getName();
                        if (elem.equals("feed") == true) {
                            count++;
                            if (count >= NUM_OF_RECOMMEND) {
                                return 0;
                            }
                            inFeed = false;
                        }
                        break;
                    }
    
                    event = p.next();
                }
            }
            catch (Exception e) {
                e.printStackTrace();
                return -1;
            }
            return 0;
        }
    
        private void addFeed() {
            int count = 0;
            Intent result = new Intent();
    
            for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
                if (mFeedCheck[i].isChecked() == true) {
                    String key = "";
                    key = String.format("Recommend%02d", (count + 1));
                    result.putExtra(key, mRecommendURL[i]);
                    count++;
                }
            }
            result.putExtra("RecommendCount", count);
            setResult(RESULT_OK, result);
            finish();
        }
    
    }

  • リスト5-24 FeedItem.java :
    package com.beatcraft.rssreader;
    
    public class FeedItem {
        public static final int ITEMTYPE_FEEDCHANNEL = 0;
        public static final int ITEMTYPE_FEEDITEM = 1;
    
        private int mItemType;
        private String mFeedTitle = "";
        private String mArticleTitle = "";
        private String mPubDate = "";
        private String mDescription = "";
        private String mLink = "";
    
        public FeedItem(int itemType) {
            mItemType = itemType;
        }
    
        public int itemType() {
            return mItemType;
        }
    
        public String feedTitle() {
            return mFeedTitle;
        }
        public void setFeedTitle(String title) {
            mFeedTitle = title;
        }
    
        public String articleTitle() {
            return mArticleTitle;
        }
        public void setArticleTitle(String title) {
            mArticleTitle = title;
        }
    
        public String pubDate() {
            return mPubDate;
        }
        public void setPubDate(String pubDate) {
            mPubDate = pubDate;
        }
    
        public String description() {
            return mDescription;
        }
        public void setDescription(String description) {
            mDescription = description;
        }
    
        public String link() {
            return mLink;
        }
        public void setLink(String link) {
            mLink = link;
        }
    }

  • リスト5-25 FeedList.java :
    package com.beatcraft.rssreader;
    
    import java.io.InputStream;
    import java.util.ArrayList;
    
    import org.apache.http.HttpResponse;
    import org.apache.http.HttpStatus;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.xmlpull.v1.XmlPullParser;
    
    import android.util.Xml;
    
    public class FeedList {
        private ArrayList<FeedItem> mList = null;
        
        public FeedList() {
            mList = new ArrayList<FeedItem>();
        }
    
        public ArrayList<FeedItem> getList() {
            return mList;
        }
        public int count() {
            if (mList != null) {
                return mList.size();
            }
            return 0;
        }
    
        public int get(RSSReaderApplication app) {
            int success = 0;
            for (int i = 0; i < RSSReaderApplication.NUM_OF_FEED; ++i) {
                String url = app.feedURL(i);
                if (url.equals("") == true) {
                    continue;
                }
    
                DefaultHttpClient client = new DefaultHttpClient();
                if (client != null) {
                    client.getParams().setParameter("http.socket.timeout", new Integer(15000));
                    HttpGet method = null;
                    try {
                        method = new HttpGet(url);
                    }
                    catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (method == null) {
                        continue;
                    }
                    HttpResponse response = null;
                    try {
                        response = client.execute(method);
                        int ret = response.getStatusLine().getStatusCode();
                        if (ret == HttpStatus.SC_OK) {
                            InputStream is = response.getEntity().getContent();
                            if (parse(is, app.numberOfGet()) > 0) {
                                success++;
                            }
                            is.close();
                        }
                    }
                    catch (Exception e) {
                        e.printStackTrace();
                    }
                    finally {
                        client.getConnectionManager().shutdown();
                    }
                }
            }
            return success;
        }
    
        private int parse(InputStream is, int max) {
            int count = 0;
            boolean inChannel = false;
            boolean inItem = false;
            FeedItem item = null;
            String feedTitle = "";
    
            XmlPullParser p = Xml.newPullParser();
            try {
                p.setInput(is, null);
                int event = p.getEventType();
                while (event != XmlPullParser.END_DOCUMENT) {
                    String elem = null;
                    String tmp = null;
    
                    switch (event) {
                    case XmlPullParser.START_TAG:
                        elem = p.getName();
                        if (elem.equals("channel") == true) {
                            inChannel = true;
                            item = new FeedItem(FeedItem.ITEMTYPE_FEEDCHANNEL);
                        }
                        else if (elem.equals("item") == true) {
                            if (inChannel == true) {
                                if (item != null) {
                                    mList.add(item);
                                    item = null;
                                    count++;
                                }
                                inChannel = false;
                            }
                            inItem = true;
                            item = new FeedItem(FeedItem.ITEMTYPE_FEEDITEM);
                            item.setFeedTitle(feedTitle);
                        }
                        else if (elem.equals("title") == true) {
                            tmp = p.nextText();
                            if ((tmp != null) && (item != null)) {
                                if (inChannel == true) {
                                    feedTitle = tmp;
                                    item.setFeedTitle(tmp);
                                }
                                else if (inItem == true) {
                                    item.setArticleTitle(tmp);
                                }
                            }
                        }
                        else if ((elem.equals("pubDate") == true) 
                                || (elem.equals("date") == true)) {
                            if (inItem == true) {
                                tmp = p.nextText();
                                if ((tmp != null) && (item != null)) {
                                    item.setPubDate(tmp);
                                }
                            }
                        }
                        else if (elem.equals("description") == true) {
                            if (inItem == true) {
                                tmp = p.nextText();
                                if ((tmp != null) && (item != null)) {
                                    item.setDescription(tmp);
                                }
                            }
                        }
                        else if (elem.equals("link") == true) {
                            if (inItem == true) {
                                tmp = p.nextText();
                                if ((tmp != null) && (item != null)) {
                                    item.setLink(tmp);
                                }
                            }
                        }
                        break;
                        
                    case XmlPullParser.END_TAG:
                        elem = p.getName();
                        if (elem.equals("channel") == true) {
                            if (inChannel == true) {
                                if (item != null) {
                                    mList.add(item);
                                    item = null;
                                    count++;
                                }
                                inChannel = false;
                            }
                        }
                        else if (elem.equals("item") == true) {
                            if (inItem == true) {
                                if (item != null) {
                                    mList.add(item);
                                    item = null;
                                    count++;
                                    max--;
                                    if (max == 0) {
                                        return count;
                                    }
                                }
                                inItem = false;
                            }
                        }
                    }
    
                    event = p.next();
                }
            }
            catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
            return count;
        }
    }

  • リスト5-26 FeedAdapter.java :
    package com.beatcraft.rssreader;
    
    import java.util.List;
    
    import android.content.Context;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ArrayAdapter;
    import android.widget.TextView;
    
    public class FeedAdapter extends ArrayAdapter<FeedItem> {
        private LayoutInflater mInflate;
    
        public FeedAdapter(Context context, List<FeedItem> obj) {
            super(context, 0, obj);
            mInflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }
    
        @Override
        public boolean isEnabled(int pos) {
            FeedItem item = getItem(pos);
            if (item.itemType() == FeedItem.ITEMTYPE_FEEDCHANNEL) {
                return false;
            }
            return true;
        }
    
        public View getView(final int pos, View convView, ViewGroup parent) {
            View view = convView;
            FeedItem item = getItem(pos);
    
            switch (item.itemType()) {
            case FeedItem.ITEMTYPE_FEEDCHANNEL:
                view = buildChannel(item);
                break;
    
            case FeedItem.ITEMTYPE_FEEDITEM:
                view = buildItem(item);
                break;
            }
            return view;
        }
    
        private View buildChannel(FeedItem item) {
            View view = null;
            view = mInflate.inflate(R.layout.item_channel, null);
    
            TextView tv;
            tv = (TextView) view.findViewById(R.id.FeedTitle);
            tv.setText(item.feedTitle());
    
            return view;
        }
    
        private View buildItem(FeedItem item) {
            View view = null;
            view = mInflate.inflate(R.layout.item_item, null);
    
            TextView tv;
            tv = (TextView) view.findViewById(R.id.ArticleTitle);
            tv.setText(item.articleTitle());
            tv = (TextView) view.findViewById(R.id.PubDate);
            tv.setText(item.pubDate());
    
            return view;
        }
    }

  • リスト5-27 ITaskEntity.java :
    package com.beatcraft.rssreader;
    
    public interface ITaskEntity {
        void backgroundProc();
        void postProc();
    }

  • リスト5-28 HttpAccessTask.java :
    package com.beatcraft.rssreader;
    
    import android.app.Activity;
    import android.app.ProgressDialog;
    import android.os.AsyncTask;
    
    public class HttpAccessTask extends AsyncTask<ITaskEntity, Integer, Void> {
        private Activity mActivity;
        private ProgressDialog mDialog;
        private ITaskEntity mITaskEntity;
    
        public HttpAccessTask(Activity activity) {
            mActivity = activity;
        }
    
        @Override
        protected void onPreExecute() {
            mDialog = new ProgressDialog(mActivity);
            mDialog.setMessage(mActivity.getString(R.string.loading));
            mDialog.show();
        }
    
        @Override
        protected Void doInBackground(ITaskEntity... params) {
            mITaskEntity = params[0];
            mITaskEntity.backgroundProc();
            return null;
        }
    
        @Override
        protected void onPostExecute(Void v) {
            mITaskEntity.postProc();
            mDialog.dismiss();
            mDialog = null;
        }
    }

  • リスト5-29 ListGenerateTask.java :
    package com.beatcraft.rssreader;
    
    import android.os.AsyncTask;
    
    public class ListGenerateTask extends AsyncTask<ITaskEntity, Integer, Void> {
        private ITaskEntity mITaskEntity;
    
        @Override
        protected Void doInBackground(ITaskEntity... params) {
            mITaskEntity = params[0];
            mITaskEntity.backgroundProc();
            return null;
        }
    
        @Override
        protected void onPostExecute(Void v) {
            mITaskEntity.postProc();
        }
    }

  • リスト5-30 FeedService.java :
    package com.beatcraft.rssreader;
    
    import java.util.ArrayList;
    
    import android.app.AlarmManager;
    import android.app.PendingIntent;
    import android.app.Service;
    import android.content.Context;
    import android.content.Intent;
    import android.os.IBinder;
    import android.os.RemoteException;
    
    public class FeedService extends Service implements Runnable {
        private static final String SERVICE_ACTION = "com.beatcraft.rssreader.SERVICE_ACTION";
        private static final String BROADCAST_MSG = "com.beatcraft.rssreader.BROADCAST_MSG";
    
        private boolean mWorking = false;
        private FeedList mListFG = null;
        private FeedList mListBG = null;
    
        @Override
        public void onStart(Intent intent, int startID) {
            super.onStart(intent, startID);
            Thread thread = new Thread(this);
            thread.run();
        }
    
        @Override
        public IBinder onBind(Intent arg0) {
            return mIFeedServiceBinder;
        }
    
        @Override
        public void run() {
            RSSReaderApplication app = (RSSReaderApplication) getApplication();
            mListBG = new FeedList();
            if (mListBG.get(app) > 0) {
                lock();
                mListFG = mListBG;
                unlock();
                Intent bcast = new Intent(BROADCAST_MSG);
                getBaseContext().sendBroadcast(bcast);
            }
    
            Intent ac = new Intent();
            ac.setAction(SERVICE_ACTION);
            PendingIntent pi = PendingIntent.getService(this, 0, ac, 0);
            AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
            long at = System.currentTimeMillis() + 60000;
            am.set(AlarmManager.RTC, at, pi);
        }
    
        // service binder
        private final IFeedService.Stub mIFeedServiceBinder = new IFeedService.Stub() {
            @Override
            public void lockList() {
                lock();
            }
            
            @Override
            public void unlockList() {
                unlock();
            }
    
            @Override
            public int getFeedCount() throws RemoteException {
                if (mListFG != null) {
                    return mListFG.count();
                }
                return 0;
            }
    
            @Override
            public int getItemType(int index) {
                if (mListFG != null) {
                    ArrayList<FeedItem> list = mListFG.getList();
                    return list.get(index).itemType();
                }
                return 0;
            }
    
            @Override
            public String getFeedTitle(int index) {
                if (mListFG != null) {
                    ArrayList<FeedItem> list = mListFG.getList();
                    return list.get(index).feedTitle();
                }
                return "";
            }
    
            @Override
            public String getArticleTitle(int index) {
                if (mListFG != null) {
                    ArrayList<FeedItem> list = mListFG.getList();
                    return list.get(index).articleTitle();
                }
                return "";
            }
    
            @Override
            public String getPubDate(int index) {
                if (mListFG != null) {
                    ArrayList<FeedItem> list = mListFG.getList();
                    return list.get(index).pubDate();
                }
                return "";
            }
    
            @Override
            public String getDescription(int index) {
                if (mListFG != null) {
                    ArrayList<FeedItem> list = mListFG.getList();
                    return list.get(index).description();
                }
                return "";
            }
    
            @Override
            public String getLink(int index) {
                if (mListFG != null) {
                    ArrayList<FeedItem> list = mListFG.getList();
                    return list.get(index).link();
                }
                return "";
            }
        };
    
        private synchronized void lock() {
            while (mWorking) {
                try {
                    wait();
                }
                catch (Exception e) {
                }
            }
            mWorking = true;
        }
    
        private synchronized void unlock() {
            mWorking = false;
        }
    }

  • リスト5-31 FeedReceiver.java :
    package com.beatcraft.rssreader;
    
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    
    public class FeedReceiver extends BroadcastReceiver {
        IFeedReceiver mReceiver;
    
        public FeedReceiver(IFeedReceiver receiver) {
            mReceiver = receiver;
        }
    
        @Override
        public void onReceive(Context context, Intent intent) {
            mReceiver.feedReceived();
        }
    
    }

  • リスト5-32 IFeedReceiver.java :
    package com.beatcraft.rssreader;
    
    public interface IFeedReceiver {
        void feedReceived();
    }

  • リスト5-33 IFeedService.aidl :
    package com.beatcraft.rssreader;
    
    interface IFeedService {
        void lockList();
        void unlockList();
        int getFeedCount();
        int getItemType(int index);
        String getFeedTitle(int index);
        String getArticleTitle(int index);
        String getPubDate(int index);
        String getDescription(int index);
        String getLink(int index);
    }

  • リスト5-34 main.xml :
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
    
        <LinearLayout
            android:id="@+id/linearLayout1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dip" >
    
            <Button
                android:id="@+id/Config"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dip"
                android:layout_marginRight="5dip"
                android:layout_weight="1"
                android:text="@string/config" />
        </LinearLayout>
    
        <ListView
            android:id="@+id/android:list"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:fastScrollEnabled="true" >
        </ListView>
    
    </LinearLayout>

  • リスト5-35 item_channel.xml :
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#cccccc"
        android:orientation="vertical" >
    
        <TextView
            android:id="@+id/FeedTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dip"
            android:text="FeedTitle"
            android:textColor="#000000"
            android:textSize="16dip"
            android:textStyle="bold" />
    
    </LinearLayout>

  • リスト5-36 item_item.xml :
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
    
        <TextView
            android:id="@+id/ArticleTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dip"
            android:text="ArticleTitle" />
    
        <TextView
            android:id="@+id/PubDate"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right"
            android:layout_margin="5dip"
            android:text="pubDate" />
    
    </LinearLayout>

  • リスト5-37 description.xml :
    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/ScrollView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:orientation="vertical" >
    
            <Button
                android:id="@+id/Back"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:text="@string/back" />
    
            <TextView
                android:id="@+id/FeedTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:text="FeedTitle"
                android:textSize="16dip"
                android:textStyle="bold" />
    
            <TextView
                android:id="@+id/ArticleTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:text="ArticleTitle" />
    
            <TextView
                android:id="@+id/PubDate"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="right"
                android:layout_marginRight="10dip"
                android:text="pubDate" />
    
            <android.webkit.WebView
                android:id="@+id/Description"
                android:layout_width="fill_parent"
                android:layout_height="0dip"
                android:layout_margin="5dip"
                android:layout_weight="1" >
            </android.webkit.WebView>
        </LinearLayout>
    
    </ScrollView>

  • リスト5-38 config.xml :
    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/ScrollView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:orientation="vertical" >
    
            <LinearLayout
                android:id="@+id/linearLayout1"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_margin="10dip" >
    
                <TextView
                    android:id="@+id/LabelNumOfGet"
                    android:layout_width="wrap_content"
                    android:layout_height="fill_parent"
                    android:gravity="center_vertical"
                    android:text="@string/label_number_of_get" />
    
                <Spinner
                    android:id="@+id/NumOfGet"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
    
                <Button
                    android:id="@+id/Recommend"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="10dip"
                    android:layout_weight="1"
                    android:text="@string/recommend" />
            </LinearLayout>
    
            <EditText
                android:id="@+id/FeedURL01"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:inputType="textUri" >
    
                <requestFocus />
            </EditText>
    
            <EditText
                android:id="@+id/FeedURL02"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:inputType="textUri" />
    
            <EditText
                android:id="@+id/FeedURL03"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:inputType="textUri" />
    
            <EditText
                android:id="@+id/FeedURL04"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:inputType="textUri" />
    
            <EditText
                android:id="@+id/FeedURL05"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:inputType="textUri" />
    
            <EditText
                android:id="@+id/FeedURL06"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:inputType="textUri" />
    
            <EditText
                android:id="@+id/FeedURL07"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:inputType="textUri" />
    
            <EditText
                android:id="@+id/FeedURL08"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:inputType="textUri" />
    
            <EditText
                android:id="@+id/FeedURL09"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:inputType="textUri" />
    
            <EditText
                android:id="@+id/FeedURL10"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_marginRight="10dip"
                android:inputType="textUri" />
    
            <LinearLayout
                android:id="@+id/linearLayout2"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_margin="10dip" >
    
                <Button
                    android:id="@+id/Cancel"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="5dip"
                    android:layout_marginRight="5dip"
                    android:layout_weight="1"
                    android:text="@string/cancel" />
    
                <Button
                    android:id="@+id/Regist"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="5dip"
                    android:layout_marginRight="5dip"
                    android:layout_weight="1"
                    android:text="@string/regist" />
            </LinearLayout>
        </LinearLayout>
    
    </ScrollView>

  • リスト5-39 recommend.xml :
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
    
        <LinearLayout
            android:id="@+id/linearLayout1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dip" >
    
            <TextView
                android:id="@+id/LabelGenre"
                android:layout_width="wrap_content"
                android:layout_height="fill_parent"
                android:gravity="center_vertical"
                android:text="@string/label_genre" />
    
            <Spinner
                android:id="@+id/Genre"
                android:layout_width="120dip"
                android:layout_height="wrap_content" />
    
            <Button
                android:id="@+id/Search"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dip"
                android:layout_weight="1"
                android:text="@string/search" />
        </LinearLayout>
    
        <Button
            android:id="@+id/Back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dip"
            android:text="@string/back" />
    
        <CheckBox
            android:id="@+id/Feed01"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dip"
            android:text="CheckBox"
            android:visibility="invisible" />
    
        <CheckBox
            android:id="@+id/Feed02"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dip"
            android:text="CheckBox"
            android:visibility="invisible" />
    
        <CheckBox
            android:id="@+id/Feed03"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dip"
            android:text="CheckBox"
            android:visibility="invisible" />
    
        <Button
            android:id="@+id/AddFeed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="5dip"
            android:text="@string/addfeed"
            android:visibility="invisible" />
    
    </LinearLayout>

  • リスト5-40 AndroidManifest.xml :
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.beatcraft.rssreader"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk android:minSdkVersion="7" />
    
        <application
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:name="com.beatcraft.rssreader.RSSReaderApplication" >
            <activity
                android:label="@string/app_name"
                android:name=".RSSReaderActivity" >
                <intent-filter >
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity
                android:label="@string/app_name"
                android:name=".ConfigActivity" />
            <activity
                android:label="@string/app_name"
                android:name=".DescActivity" />
            <activity
                android:label="@string/app_name"
                android:name=".RecommendActivity" />
    
            <service android:name=".FeedService" >
                <intent-filter >
                    <action android:name="com.beatcraft.rssreader.SERVICE_ACTION" />
                </intent-filter>
            </service>
        </application>
    
        <uses-permission android:name="android.permission.INTERNET" >
        </uses-permission>
    
    </manifest>


内藤

BC::labsへの質問は、bc9-dev @ googlegroups.com までお願い致します。
トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   最終更新のRSS
Last-modified: 2012-12-13 (木) 14:52:06 (2624d)