2011年11月20日日曜日

いつからかEclipse上でAndroidプロジェクトのMavenビルドが出来なくなったので…

久々にMavenを使用してAndroidアプリを作成しようと思ったらなぜかビルドが出来ない。。
ADTやらSDKを更新したタイミングで使えなくなったのだろうと思い、いろいろ思考錯誤してみた。
そしてようやくビルドが出来る環境を作成する手順が確立出来たので忘れずメモ。

ちなみに私の環境は以下の通り。
Mac OS X 10.6.8
ADT 15 & SDK15
Maven 3.0.3
Eclipse Indigo

m2eのインストール

[Help] -> [Install New Software...]
プルダウンからIndigoのアップデートサイトを選択しCollaborationを展開する。
「Maven Integration for Eclipse」と「slf4j over logback loggind」にチェックし、-> [Next]
あとは指示通り進んでインストール完了後Eclipseを再起動。

m2e-androidのインストール

[Help] -> [Eclipse Marketplace...]
「m2e-android」と検索すると「Android Configuration for M2E」がヒットするので
Installボタンをクリックし指示通り進んでインストール完了後Eclipseを再起動。

Archetypeの追加

[New] -> [Project...] -> [Maven Project]
Workspace locationに任意の場所を指定 -> [Next] -> [Add Archetype...]
下記のような画面になるので各項目を入力 -> [OK]
Archetype Group Id: de.akquinet.android.archetypes
Archetype Artifact Id : android-quickstart
Archetype Version: 1.0.6
Repository URL: 空白
上記で追加したandroid-quickstartを選択しAndroidプロジェクトを作成する。

m2e connectorのインストール

Androidプロジェクトを作成すると、下記画面のようにpom.xmlにエラーマークがつくので
POMエディターで開く。
pom.xmlを開くと上記に赤くメッセージが表示されるのでその箇所をクリック。
下記のような黄色いウィンドウが表示されるので「Discover new m2e connectors」をクリック。
m2e Marketplaceなるものが開くので「Application」と「Maven」にチェックをし、
「Lifecycle Mappings」と「embedded maven runtimes」をインストールしEclipseを再起動。

pom.xmlの修正

<artifactId>maven-android-plugin</artifactId>
<version>2.8.4</version>
↓
<artifactId>android-maven-plugin</artifactId>
<version>3.0.0-alpha-14</version>
以上でEclipse上でAndroidプロジェクトのMavenビルドが出来るようになるはず。

ちなみにpom.xmlの全体はこんな感じ。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>jp.u1aryz.products.hoge</groupId>
  <artifactId>HogeSample</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>apk</packaging>
  <name>HogeSample</name>

  <dependencies>
    <dependency>
      <groupId>com.google.android</groupId>
      <artifactId>android</artifactId>
      <version>2.1.2</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <finalName>${project.artifactId}</finalName>
    <sourceDirectory>src/main/java</sourceDirectory>
    <plugins>
      <plugin>
        <groupId>com.jayway.maven.plugins.android.generation2</groupId>
        <artifactId>android-maven-plugin</artifactId>
        <version>3.0.0-alpha-14</version>
        <configuration>
          <sdk>
            <!-- SDKのパスはsettings.xmlで定義していれば不要 -->
            <path>"your ANDROID_HOME"</path>
            <platform>7</platform>
          </sdk>
          <manifest>
            <debuggable>true</debuggable>
          </manifest>
        </configuration>
        <extensions>true</extensions>
      </plugin>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

2011年11月18日金曜日

RoboGuiceでAndroidアプリのDI開発

今更ながらRoboGuiceを使ってみたので忘れずメモ。
RoboGuiceについて簡単に触れておくとGoogle GuiceベースのAndroid用のDIコンテナである。
roboguice - Project Hosting on Google Code

細かいViewの制御は抜きにして以下のようなアプリを例としてRoboGuiceを使用して作成してみる。
  • トップ画面より入力された名前を次の画面で文字列を付け足して表示する。




  • まず前エントリーを参考にMavenプロジェクトからAndroidプロジェクトを作成。
    pom.xmlを追記
    <dependency>
      <groupId>org.roboguice</groupId>
      <artifactId>roboguice</artifactId>
      <version>1.1.2</version>
    </dependency>
    

    TopActivity
    public class TopActivity extends RoboActivity {
    
        @InjectResource(R.string.message)
        String message;
    
        @InjectView(R.id.txt_msg)
        TextView mTextView;
    
        @InjectView(R.id.edt_name)
        EditText mEditText;
    
        @InjectView(R.id.btn_decision)
        Button mButton;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
    //        mTextView.setText(message); // ここでViewにアクセスするとエラー
            setContentView(R.layout.top); // ここでViewがInjectされる
            mTextView.setText(message);
            mButton.setOnClickListener(new OnClickListener() {
    
                public void onClick(View v) {
                    Intent intent = new Intent(TopActivity.this, HelloActivity.class);
                    intent.putExtra("name", mEditText.getText().toString());
                    startActivity(intent);
                }
            });
        }
    
    }
    
    
    RoboActivityを継承してアクティビティを作成する。※1.1からGuiceActivityからRoboActivityに変更された
    ListActivityの場合はRoboListActivity、TabActivityの場合はRoboTabActivityなどそれぞれ対応したもの
    が存在する。
    ソースを見るとわかる通り、@InjectView(resouceId)でViewをInjectしてくれる。
    リソースを参照したい場合は@InjectResource(resourceId)を使用すればいい。
    他にもこんなことが可能である。
    Drawable icon = getResources().getDrawable(R.drawable.icon);
    ↓
    @InjectResource(R.drawable.icon)
    Drawable icon;
    
    LocationManager loc = (LocationManager) getSystemService(Activity.LOCATION_SERVICE);
    ↓
    @Inject
    LocationManager loc;
    

    HelloActivityを実装する前にインターフェースと実装クラスを使用して
    少しDIっぽい処理を入れてみる。
    RoboSampleService
    public interface RoboSampleService {
    
        public String hello(String name);
    }
    
    

    RoboSampleServiceImpl
    public class RoboSampleServiceImpl implements RoboSampleService {
    
        public String hello(String name) {
            return "Hello " + name;
        }
    
    }
    
    

    続いて上記サービスをバインド
    MyApplication
    public class MyApplication extends RoboApplication {
    
        @Override
        protected void addApplicationModules(List<Module> modules) {
            modules.add(new MyModule());
        }
    
        static class MyModule extends AbstractAndroidModule {
    
            @Override
            protected void configure() {
                // RoboSampleServiceのInject要求に対してRoboSampleServiceImplを返すようバインドする
                bind(RoboSampleService.class).to(RoboSampleServiceImpl.class);
            }
    
        }
    }
    
    ※こちらもGuiceApplicationからRoboApplicationに変更されているので注意

    アプリケーションのエントリポイントとして上記クラスを呼び出す必要があるので
    AndroidManifest.xmlに以下を追加
    <application...
        android:name=".MyApplication"
        ...>
    

    最後にHelloActivityの実装
    HelloActivity
    public class HelloActivity extends RoboActivity {
    
        @InjectExtra("name")
        String name;
    
        @InjectView(R.id.hello_msg)
        TextView textView;
    
        @Inject
        RoboSampleService service;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.hello);
            textView.setText(service.hello(name));
        }
    
    }
    

    @InjectExtra("name")でTopActivityでputExtraした値が入る。
    また、RoboSampleServiceに@Injectアノテーションをつけることで上記でバインドした
    RoboSampleServiceImplをInjectしてくれるのでインスタンスを生成せず使用することが出来る。

    続いてDIの長所を活かしてHelloActivityのテストを書いてみる。
    テストプロジェクトを作成する前にテスト対象のプロジェクトのクラスパスをエクスポート。

    通常通りAndroidテストプロジェクトを作成する。
    今回作成したサンプルプログラムのhelloメソッドは静的な文字列を付加するだけの
    シンプルな作りであるが、実際は動的であったり、バグが混入していたり、
    決まっていなかったりするので依存するクラスの振る舞いを固定化するモックを作成する。

    MockServiceImpl
    public class MockServiceImpl implements RoboSampleService {
    
        @Override
        public String hello(String name) {
            return "test";
        }
    
    }
    

    続いてテストコードの実装
    HelloActivityTest
    public class HelloActivityTest extends RoboActivityUnitTestCase<HelloActivity> {
    
        private Context mContext;
    
        public HelloActivityTest() {
            super(HelloActivity.class);
        }
    
        class MockApplication extends RoboApplication {
    
            public MockApplication(Context context) {
                super();
                attachBaseContext(context);
            }
    
            @Override
            protected void addApplicationModules(List<Module> modules) {
                modules.add(new MockModule());
            }
    
        }
    
        class MockModule extends AbstractAndroidModule {
    
            @Override
            protected void configure() {
                // RoboSampleServiceのInject要求に対してMockServiceImplを返すようバインドする
                bind(RoboSampleService.class).to(MockServiceImpl.class);
            }
    
        }
    
        @Override
        protected void setUp() throws Exception {
            super.setUp();
            mContext = getInstrumentation().getContext();
            setApplication(new MockApplication(mContext));
        }
    
        @MediumTest
        public void testShouldBeHelloMsg() {
            Intent intent = new Intent(mContext, HelloActivity.class);
            intent.putExtra("name", "name");
            HelloActivity a = startActivity(intent, null, null);
            assertNotNull(a);
    
            // Mockで返す文字列を"test"にしているため
            assertEquals(((TextView)a.findViewById(R.id.hello_msg)).getText(), "test");
        }
    }
    

    RoboActivityUnitTestCase<対象アクティビティ>を継承してテストコードを作成する。
    RoboSampleServiceのInject要求に対してモックを返すようにバインドさせ、
    setApplicationでセットすることで依存したオブジェクトの振る舞いを固定化させることが出来る。

    Run As -> Android JUnit Testで...
    これで一通り完了。案外使いやすいかも?

    2011年9月25日日曜日

    Androidでオブジェクト/XMLマッピング

    TwitterクライアントなどXMLやJSONをWeb上から取得してアプリに表示することは少なくない。
    しかし、XMLやJSONをパースするような単調な処理は出来るだけ無くしたいので、
    Spring AndroidとSimple(xmlシリアルフレームワーク)を使用してXMLデータを取得し、
    アプリに表示させてみた。
    今回はイベント開催支援ツールのATNDのAPIを使用してAndroid系イベントを取得して
    イベントタイトルとイベント日時をアプリにリスト表示してみる。
    実際に使用するAPI→http://api.atnd.org/events/?keyword=android&format=xml
    サンプルプロジェクトのダウンロードリンクは記事の最後。

    まず前エントリーを参考にMavenプロジェクトからAndroidプロジェクトを作成。

    pom.xmlを追記
    <dependency>
      <groupId>org.simpleframework</groupId>
      <artifactId>simple-xml</artifactId>
      <version>2.6.1</version>
      <exclusions>
        <exclusion>
          <artifactId>stax</artifactId>
          <groupId>stax</groupId>
          </exclusion>
          <exclusion>
            <artifactId>stax-api</artifactId>
            <groupId>stax</groupId>
          </exclusion>
          <exclusion>
            <artifactId>xpp3</artifactId>
            <groupId>xpp3</groupId>
          </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.android</groupId>
      <artifactId>spring-android-rest-template</artifactId>
      <version>1.0.0.M4</version>
    </dependency>
    
    <repositories>
      <repository>
        <id>org.springframework.maven.milestone</id>
        <name>Spring Maven Milestone Repository</name>
        <url>http://maven.springframework.org/milestone</url>
        <snapshots><enabled>false</enabled></snapshots>
      </repository>
    </repositories>
    

    次にXMLデータをマッピングさせるオブジェクトを作成する。
    要素には@Element、属性には@Attributeをつける。
    @Element、@Attributeのnameは要素名(属性名)と変数名が同じであれば省略可能。
    XMLのすべての要素をオブジェクトにマッピングしない場合は@Rootアノテーションにstrict = false
    をつける必要がある。
    Event.java
    package jp.u1aryz.products.atndsearch;
    
    import org.simpleframework.xml.Element;
    import org.simpleframework.xml.Root;
    
    @Root(name = "event", strict = false)
    public class Event {
    
        @Element
        private String title;
    
        @Element(name = "started_at")
        private String eventDate;
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setEventDate(String eventDate) {
            this.eventDate = eventDate;
        }
    
        public String getEventDate() {
            return eventDate;
        }
    }
    

    リストの場合は@ElementListアノテーションを使う。
    EventList.java
    package jp.u1aryz.products.atndsearch;
    
    import java.util.List;
    
    import org.simpleframework.xml.ElementList;
    import org.simpleframework.xml.Root;
    
    @Root(name = "events", strict = false)
    public class EventList {
    
        @ElementList
        private List<Event> events;
    
        public void setEvents(List<Event> events) {
            this.events = events;
        }
    
        public List<Event> getEvents() {
            return events;
        }
    }
    

    そしてXMLデータからオブジェクトにマッピングさせる処理を作成する。
    HTTP通信は通常、UIスレッドとは別のスレッドで行うため、今回のポイントとなる処理も
    doInBackground内に入れる。
    GetAndroidEventActivity.java
    package jp.u1aryz.products.atndsearch;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.http.HttpEntity;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.client.RestTemplate;
    
    import android.app.ListActivity;
    import android.app.ProgressDialog;
    import android.os.AsyncTask;
    import android.os.Bundle;
    import android.util.Log;
    
    public class GetAndroidEventActivity extends ListActivity {
    
        private static String TAG = GetAndroidEventActivity.class.getSimpleName();
        private ProgressDialog progressDialog;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            // ATND APIの呼び出しを非同期で行う
            new GetEventTask().execute();
        }
    
        private void refreshEvents(List<Event> events) {
            if (events != null) {
                EventListAdapter adapter = new EventListAdapter(this, events);
                setListAdapter(adapter);
            }
        }
    
        public void showProgressDialog() {
            if (progressDialog == null) {
                progressDialog = new ProgressDialog(this);
                progressDialog.setIndeterminate(true);
            }
    
            progressDialog.setMessage("Loading. Please wait...");
            progressDialog.show();
        }
    
        public void dismissProgressDialog() {
            if (progressDialog != null) {
                progressDialog.dismiss();
            }
        }
    
        private class GetEventTask extends AsyncTask<Void, Void, List<Event>> {
    
            @Override
            protected void onPreExecute() {
                // プログレスバーを表示
                showProgressDialog();
            }
    
            @Override
            protected List<Event> doInBackground(Void... params) {
                try {
                    // Android関連のイベントを検索するURL
                    String url =
                        "http://api.atnd.org/events/?keyword=android&format=xml";
                    // Acceptヘッダに"application/xml"をセット
                    HttpHeaders requestHeaders = new HttpHeaders();
                    List<MediaType> acceptableMediaTypes =
                        new ArrayList<MediaType>();
                    acceptableMediaTypes.add(MediaType.APPLICATION_XML);
                    requestHeaders.setAccept(acceptableMediaTypes);
    
                    HttpEntity<?> requestEntity =
                        new HttpEntity<Object>(requestHeaders);
                    RestTemplate restTemplate = new RestTemplate();
    
                    // HTTPのGETリクエストを実行(マッピングさせるクラスを指定する)
                    ResponseEntity<EventList> responseEntity =
                        restTemplate.exchange(url,
                                            HttpMethod.GET,
                                            requestEntity,
                                            EventList.class);
    
                    // イベントリストを取得
                    EventList eventList = responseEntity.getBody();
    
                    return eventList.getEvents();
                } catch (Exception e) {
                    Log.e(TAG, e.getMessage(), e);
                }
    
                return null;
            }
    
            @Override
            protected void onPostExecute(List<Event> result) {
                // プログレスバーを非表示
                dismissProgressDialog();
    
                // 結果を返す
                refreshEvents(result);
            }
        }
    }
    

    最後にViewに取得したデータをセットするAdapterの作成。
    特別な処理はしていないので、通常のAndroidの知識で理解出来ると思う。
    EventListAdapter.java
    package jp.u1aryz.products.atndsearch;
    
    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 EventListAdapter extends ArrayAdapter<Event> {
    
        private LayoutInflater mInflater;
    
        public EventListAdapter(Context context, List<Event> objects) {
            super(context, 0, objects);
            mInflater = LayoutInflater.from(context);
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
    
            if (convertView == null) {
                convertView =
                    mInflater.inflate(android.R.layout.simple_list_item_2, null);
                holder = new ViewHolder();
                holder.title =
                    (TextView) convertView.findViewById(android.R.id.text1);
                holder.eventDate =
                    (TextView) convertView.findViewById(android.R.id.text2);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
    
            Event event = getItem(position);
            holder.title.setText(event.getTitle());
            holder.eventDate.setText(event.getEventDate());
    
            return convertView;
        }
    
    }
    class ViewHolder {
        TextView title;
        TextView eventDate;
    }
    

    <uses-permission android:name="android.permission.INTERNET" />を忘れなければ
    下記のようにイベントを取得出来る。
    日時のフォーマットが見づらいけど。。。
    これで単調なパース処理とおさらば〜
    時間があればJSONバージョンもやるかも!?

    ダウンロードはこちらから

    2011年9月23日金曜日

    MavenでAndroidアプリケーションの構成管理をはじめる手順

    最近ではサードパーティー製のAndroidフレームワークやライブラリが増加してきており、
    構成管理にMavenを使用すると便利である。
    Maven初心者ながらAndroidプロジェクトの構成管理を始めてみたのでその時の手順をメモ。
    2011年11月20日に更新しました。

    <事前環境>
    Apache Maven 3.0.3
    Eclipse 3.7 + ADT v12
    ※環境のインストール手順については割愛
    1. M2_REPOの設定
      EclipseからMavenを利用するために下記コマンドを実行
      mvn -Declipse.workspace=<path-to-eclipse-workspace> eclipse:add-maven-repo
    2. Eclipseプラグインのインストール
      Update siteから下記の2つのプラグインを追加
      ( [Help] -> [Install New Software...] -> [Add...] )
      • Maven Integration for Eclipse
        http://m2eclipse.sonatype.org/sites/m2e
      • Maven Integration for Android Development Tools
        https://svn.codespot.com/a/eclipselabs.org/m2eclipse-android-integration/updates/m2eclipse-android-integration/
    3. Archetypeの追加
      現在ではGitHub等に多数Archetypeが公開されているので、今回はこちらを利用する。
      [New] -> [Project...] -> [Maven Project]
      Workspace locationに任意の場所を指定 -> [Next] -> [Add Archetype...]
      下記のような画面になるので各項目を入力 -> [OK]


      Archetype Group Id: de.akquinet.android.archetypes
      Archetype Artifact Id : android-quickstart
      Archetype Version: 1.0.5
      Repository URL: 空白
    4. Androidプロジェクトの作成
      上記で追加したandroid-quickstartを選択 -> [Next]
      下記のような画面になったら各項目に任意の値を入力 -> [Finish]

    以上でAndroidアプリもMavenで構成管理が出来るようになるはず。

    Androidへのデプロイはプロジェクト配下で下記コマンドを実行する。
    mvn clean install android:deploy

    2011年9月20日火曜日

    Ubuntu 11.04にRedmineをインストールする

    最近ではfluxflexでボタンを数回クリックすればRedmineの環境は用意出来てしまう程便利になった。
    試しにUbuntuにインストールしてみたら結構めんどうだったのでインストールログをメモ。

    環境はUbuntu 11.04(Ubuntu標準アプリ以外は空)
    最終目標はRedmine 1.0.5をインストールし、
    http://localhost/redmineでRedmineにアクセス出来るようにする

    ※Redmineの各バージョンで必要となるRailsのバージョンはこちらを参照

    まずはRubyをインストール
    $ sudo apt-get install ruby
    $ ruby -v
    ruby 1.8.7 (2010-08-16 patchlevel 302) [x86_64-linux]
    

    RubyGems(Ruby用のパッケージ管理システム)をインストール
    $ cd /home/u1aryz/work
    $ sudo wget http://rubyforge.org/frs/download.php/70696/rubygems-1.3.7.tgz
    $ sudo tar zxvf rubygems-1.3.7.tgz
    $ cd rubygems-1.3.7
    $ sudo ruby setup.rb
    $ gem1.8 -v
    1.3.7
    
    $ sudo apt-get install rubygems1.8
    $ gem -v
    1.3.7
    

    Ruby on Railsをインストール
    $ sudo gem install rails -v=2.3.5
    $ rails -v
    Rails 2.3.5
    

    Rackをインストール
    $ sudo gem install rack -v=1.0.1
    

    SQLite及び開発用ライブラリをインストール
    $ sudo apt-get install sqlite libsqlite3-dev
    

    i18nをインストール(多言語対応するため?)
    $ sudo gem install -v=0.4.2 i18n
    

    sqlite3-ruby(SQLiteのRuby用アダプタ)をインストール
    $ sudo gem install sqlite3-ruby
    

    Redmineをインストール
    $ cd /home/u1aryz/work
    $ sudo wget http://rubyforge.org/frs/download.php/73692/redmine-1.0.5.tar.gz
    $ sudo tar zxvf redmine-1.0.5.tar.gz
    $ sudo mv redmine-1.0.5 /usr/local/.
    

    Redmineの設定
    $ cd /usr/local/redmine-1.0.5/config
    $ cp -p database.yml.example database.yml
    $ vi database.yml
    

    database.ymlの中を一部修正(+が追加、-が削除)
    production:
    +   adapter: sqlite3
    +   dbfile: db/redmine.db
    +   timeout: 5000
    -   adapter: mysql
    -   database: redmine
    -   host: localhost
    -   username: root
    -   password:
    -   encoding: utf8
    

    セッションストア秘密鍵を生成する
    $ rake config/initializers/session_store.rb
    

    テーブルを作成する
    $ rake db:migrate RAILS_ENV=production
    

    確認のためWEBrickで起動し、ブラウザからアクセスする。
    $ cd /usr/local/redmine-1.0.5/
    $ ruby script/server webrick -e production
    
    http://localhost:3000でアクセス出来れば次へ

    Apache及び開発用ライブラリをインストール
    $ sudo apt-get install apache2 apache2-prefork-dev
    

    OpenSSLの開発ライブラリをインストール
    $ sudo apt-get install libcurl4-openssl-dev libcurl4-openssl-dev
    

    Phusion Passengerをインストール
    $ sudo gem install passenger
    $ sudo passenger-install-apache2-module
    

    Apacheの設定
    $ cd /etc/apache2/sites-available
    $ sudo vi default
    

    defaultの中の中を一部修正(+が追加、-が削除)
    + LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-3.0.9/ext/apache2/mod_passenger.so
    + PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-3.0.9
    + PassengerRuby /usr/bin/ruby1.8
    
      <VirtualHost *:80>
          ServerAdmin webmaster@localhost
    +     RailsBaseURI /redmine
          DocumentRoot /var/www
          <Directory />
              Options FollowSymLinks
              AllowOverride None
          </Directory>
          <Directory /var/www/>
              Options Indexes FollowSymLinks MultiViews
              AllowOverride None
              Order allow,deny
              allow from all
          </Directory>
    

    リンクを貼る
    $ cd /var/www/
    $ sudo ln -s /usr/local/redmine-1.0.5/public redmine
    

    Apacheの再起動
    $ sudo /etc/init.d/apache2 restart
    

    http://localhost/redmineでブラウザからアクセス出来れば終了~

    2011年7月14日木曜日

    AndroidのXMLで2次元配列を定義してソースから呼び出す

    Androidでは文字列などのリソースをXMLで定義すると何かと便利なので、
    2次元配列をXMLで定義してソースからアクセスする方法を忘れずにメモ。

    XML
    <array name="array_parent">
        <item >@array/array_sub1</item>
        <item >@array/array_sub2</item>
    </array>
    
    <string-array name="array_sub1" >
        <item >data1</item>
        <item >data2</item>
        <item >data3</item>
    </string-array>
    
    <string-array name="array_sub2" >
        <item >data4</item>
        <item >data5</item>
        <item >data6</item>
    </string-array>
    

    Java
    TypedArray typedArray = getResources().obtainTypedArray(R.array.array_parent);
    // 配列の要素数(ここではarray_parentの子要素の数)
    int length = typedArray.length();
    
    // 子要素の配列のリソースIDを取得(ここではarray_sub1のリソースID)
    int resourceId = typedArray.getResourceId(0, 0);
    
    // 配列の値を取得(ここではarray_sub1の各値)
    String[] array = getResources().getStringArray(resourceId);
    


    これに多少の応用を利かせればDrawable配列などの多次元配列も可能となる。

    2011年5月29日日曜日

    Androidでスクリーンロック解除しなくても操作可能な画面をつくる&呼び出す


    音楽アプリなどで音楽再生中にスクリーンロックがかかった場合、
    再度音楽を操作するのにいちいちスクリーンロックを解除するのはかなりの手間である。

    Androidマーケットで常に上位のPowerAMPはこのように操作可能になっている。
    このようなアプリではユーザビリティを考慮する上でも必要な機能となる。

    そこで今回はスクリーンロック解除しなくても操作可能な画面&呼び出しを
    ミニマム構成で作成してみた。
    サンプルプロジェクトのダウンロードリンクは記事の最後。

    構成は以下の通り。
    1. 画面の電源ONの通知を受け取るレシーバーの登録/登録解除を行うサービス
      ScreenStateService
    2. スクリーンロック解除画面より手前に表示させる画面
      ScreenLockEnabledActivity
    3. "2"の画面を表示させるか否かを設定する画面
      SetActivity
    まず、"画面の電源ONの通知を受け取るレシーバーの登録/登録解除を行うサービス"を作成する。
    画面の電源が入るとACTION_SCREEN_ONが通知される。
    ACTION_SCREEN_ONを受け取るには明示的にregisterReceiverする必要がある。
    ScreenStateService.java
    package jp.u1aryz.products.screenlockenable;
    
    import android.app.Service;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.os.IBinder;
    
    public class ScreenStateService extends Service {
    
        private BroadcastReceiver mScreenOnListener = new BroadcastReceiver() {
    
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
    
                // 画面の電源が入ったらActivityを起動
                if (action.equals(Intent.ACTION_SCREEN_ON)) {
                    Intent i = new Intent(context, ScreenLockEnabledActivity.class);
                    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    context.startActivity(i);
                }
            }
        };
    
        @Override
        public void onStart(Intent intent, int startId) {
            super.onStart(intent, startId);
            // ACTION_SCREEN_ONを受け取るBroadcastReceiverを登録
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_SCREEN_ON);
            registerReceiver(mScreenOnListener, filter);
        }
    
        @Override
        public void onDestroy() {
            // BroadcastReceiverを登録解除
            unregisterReceiver(mScreenOnListener);
    
            super.onDestroy();
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }
    
    サービスが起動されたらレシーバーの登録、サービスが停止されたらレシーバーの登録解除を行う。
    かなり簡略化してシンプルな作りにしている。

    そして今回のポイントとなる"Lock解除画面より手前に表示させる画面"を作成する。
    ScreenLockEnabledActivity.java
    package jp.u1aryz.products.screenlockenable;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.Window;
    import android.view.WindowManager;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    
    public class ScreenLockEnabledActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.lock);
    
            // Lock解除画面より手前に表示させる
            final Window win = getWindow();
            win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    
            Button btnRelease = (Button) findViewById(R.id.btn_release);
            btnRelease.setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    // Activityを終了することでLock解除画面に移る
                    finish();
                }
            });
        }
    }
    
    こちらもかなりミニマムであるが、このActivityに各々操作できるウィジェット(View)等を
    配置するといいと思う。

    続いて"上記の画面を表示させるか否かを設定する画面"を作成する。
    通常はPreferenceActivityなどで実装すると良い。
    SetActivity.java
    package jp.u1aryz.products.screenlockenable;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.content.SharedPreferences;
    import android.os.Bundle;
    import android.widget.CheckBox;
    import android.widget.CompoundButton;
    import android.widget.CompoundButton.OnCheckedChangeListener;
    
    public class SetActivity extends Activity {
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            // チェックボックスの値を保存するために使用
            final SharedPreferences pref = getSharedPreferences("pref", MODE_PRIVATE);
            final Intent intent = new Intent(SetActivity.this, ScreenStateService.class);
    
            CheckBox chbEnable = (CheckBox) findViewById(R.id.chb_enable);
            chbEnable.setChecked(pref.getBoolean("is_lockEnable", false));
            chbEnable.setOnCheckedChangeListener(new OnCheckedChangeListener() {
    
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    // チェックされたらサービスを起動
                    if (isChecked) {
                        pref.edit().putBoolean("is_lockEnable", isChecked).commit();
                        startService(intent);
                    // チェックが外されたらサービスを停止
                    } else {
                        pref.edit().putBoolean("is_lockEnable", isChecked).commit();
                        stopService(intent);
                    }
                }
            });
    
        }
    }
    
    端末の再起動のパターンやその他もろもろ対応しきれてないけどあしからず〜

    ダウンロードはこちらから