Google Android 完全解説 (アスキームック) 注意点


9/18 変更

[参考になる URL]

Android を勉強するにあたり、日本語で読める文献としては現在のところ以下の本が一番詳しいようです。 この本は android m3 を対象に書かれていますが、 その後 android m5 → android 0.9 beta とバージョンが上がるに伴い
API が変更されたため、本のサンプルをそのまま記述してもエラーが出ることが多いようです。

学生の指導にこの本を用いていますが、その際に気づいた注意点を以下に記しておきます。
本の全ての内容をカバーしているわけではありません。また、本ページの利用は自己責任でお願いします。
[新着情報]
2009 年 7 月に上記書籍の続編「Google Androidプログラミング入門」が発売されるようです。
こちらですと、最新版の Android に対応していることでしょう。



第5〜6章

特に注意書きがない場合は android m5 と android 0.9 beta 対応とし、 どちらかのバージョンのみの場合はその都度指定します。

p.32 図 3
パッケージ名 com.mamezou.android.bmicalc

パッケージはある程度自由に決めて良い (mamezou は執筆者の所属会社名) が、 アプリケーションごとに変えないと、後でエラーが出てしまう。
一度決めたパッケージ名を変えたい場合はパッケージエクスプローラの パッケージ名 (com.mamezou.android.bmicalc) 上で右クリックし、
リファクタリング→名前の変更。 さらに AndroidManifest.xml の中身にもパッケージ名が書かれているので変更する必要がある。

p.33 左
strings.xml を開き、

パッケージエクスプローラーで strings.xml をダブルクリックし、 中央の領域の「ソース」タブをクリックすることで strings.xml を開くことができる。
layout 以下の main.xml を開く場合も同様。

p.34 main.xml ファイル内の記述

<EditText id="@+id/text_height"
...
<EditText id="@+id/text_weight"
...
<Button 
	id="@+id/button_calculate"

// は、以下のように android:id としないと警告が出る

<EditText android:id="@+id/text_height"
...
<EditText android:id="@+id/text_weight"
...
<Button 
	android:id="@+id/button_calculate"



p.34 main.xml ファイル内の記述 (android 0.9 beta 以降限定)

android:numeric="true"
→
android:numeric="integer"



p.37 リスト3 (追加)

import android.app.Activity;   // この2行はデフォルトで書かれている。
import android.os.Bundle;      // ソースの左の「+」の記号をクリックして展開

import android.widget.Button;
import android.widget.EditText;
import android.app.AlertDialog;
import android.view.View;

public class BMICalculatorActivity extends Activity {

あるいはワイルドカード (「*」) を用いて以下の3行も機能する。

p.37 リスト3 (追加)

import android.app.Activity;   // この2行はデフォルトで書かれている。
import android.os.Bundle;      // ソースの左の「+」の記号をクリックして展開

import android.widget.*;
import android.app.*;
import android.view.*;

public class BMICalculatorActivity extends Activity {



p.37 リスト3

AlertDialog.show(BMICalculator2Activity.this, null, String.valueOf(bmi),
		getResources().getText(R.string.button_close_dialog),false);

// android m5 の場合は以下のように変更

AlertDialog.show(BMICalculator2Activity.this, null, 0, String.valueOf(bmi),
		getResources().getText(R.string.button_close_dialog),false);

// android 0.9 beta 以降の場合は以下のように変更
// setTitle しないと、何故かサイズがおかしくなる

new AlertDialog.Builder(BMICalculatorActivity.this)
     .setMessage(String.valueOf(bmi))
     .setPositiveButton(getResources().getText(R.string.button_close_dialog), null)
     .setTitle("Message")
     .show();



p.46 右

layout/result.xml というファイルを新たに作成し、

パッケージエクスプローラーの layout で右クリックして新規→その他→XML→XML
「次へ」で名前 result.xml を入力。さらに「次へ」で 「XML テンプレートから XML ファイルを作成」を選択して作成する。

p.37 と同様、リスト2 には以下の3行と追加の1行を冒頭に追加。

p.47 リスト2 (追加)

import android.widget.*;
import android.app.*;
import android.view.*;
import android.content.Intent;

あるいは以下でも良い。

p.47 リスト2 (追加2)

import android.widget.*;
import android.app.*;
import android.view.*;
import android.content.*;



p.47 リスト2 (android 0.9 beta 以降限定)

startSubActivity(intent,0);
→
startActivityForResult(intent,0);



p.47 右

次に、結果を表示するアクティビティを新規に作成します。

→
このためには、パッケージエクスプローラのパッケージ名 (com.mamezou.android.bmicalc) で右クリックし、新規→クラス。
・名前: ResultActivity
・スーパークラス: android.app.Activity
として終了。その後、import 文を以下の3行で置き換える。

import android.app.Activity;
import android.os.Bundle;
import android.widget.*;



p.48 左

AndroidManifest.xml を開き、

パッケージエクスプローラーで AndroidManifest.xml をダブルクリックし、
中央の領域の「AndroidManifest.xml」タブをクリックすることで開くことができる。


p.48 リスト3

extras.getInteger("HEIGHT");
extras.getInteger("WEIGHT");

// これは以下に変更。

extras.getInt("HEIGHT");
extras.getInt("WEIGHT");



p.51 リスト8 (削除)

private int bmi = 0;   // この行は不要なので削除



p.51 リスト8 (変更)

super.onCreate(icicle);

// ... 省略されている部分にある以下の2行において、以下の様に int を削除
height = extras.getInt("HEIGHT");
weight = extras.getInt("WEIGHT");

Button saveResult = (Button) ...



p.51 リスト8 (android 0.9 beta 以降限定)

setResult(editor.commit() ? RESULT_OK : RESULT_CANCELED);
→
// android 0.9 beta では editor.commit() が常に false を返すように見えるので
// (実際には commit できている) 以下でしのいだ

setResult(editor.commit() ? RESULT_OK : RESULT_OK);



MaleIntentReceiver を作成する際は

(android m5 の場合)
・名前: MaleIntentReceiver
・スーパークラス: android.content.IntentReceiver

(android 0.9 beta 以降の場合)
・名前: MaleIntentReceiver
・スーパークラス: android.content.BroadcastReceiver

とする。



p.56/57 リスト12および13 (追加)

// 冒頭に以下の  import 文追加

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import android.content.IntentReceiver;

// なお、android 0.9 beta 以降では最後の1行のみ以下に差し替え

import android.content.BroadcastReceiver;



(android 0.9 beta 以降限定)

p.56 リスト12

public class MaleIntentReceiver extends IntentReceiver {
→
public class MaleIntentReceiver extends BroadcastReceiver {

public void onReceiveIntent(Context context, Intent intent){
→
public void onReceive(Context context, Intent intent){

p.57 リスト13

public class FemaleIntentReceiver extends IntentReceiver {
→
public class FemaleIntentReceiver extends BroadcastReceiver {

public void onReceiveIntent(Context context, Intent intent){
→
public void onReceive(Context context, Intent intent){



p.56/57 リスト12および13 (変更)

// 末尾にある以下の3行を削除して…

Notification Manager nm = ...
 .getSystemService(...);
nm.notifyWithText(...);

// 以下の1行に差し替え

Toast.makeText(context, message.toString(), Toast.LENGTH_SHORT).show();



p.58 リスト14 (変更)
// 追加する部分を以下に変更
// 
// なお、最後の scheme="content", path="/female" の intent-filter は
// android 0.9 beta では機能しない。原因は今のところ不明。

        <receiver android:name=".MaleIntentReceiver">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.SAMPLE_CODE" />
                <data android:path="/male" />
            </intent-filter>
        </receiver>
                <receiver android:name=".FemaleIntentReceiver">
            <intent-filter>
                <data android:priority="1" />
                <action android:name="android.intent.action.VIEW" />
            </intent-filter>
            <intent-filter>
                <data android:priority="0" />
                <data android:scheme="content" />
                <data android:host="hoge" />
                <data android:path="/female" />
            </intent-filter>
        </receiver>



p.60 リスト15 (追加)

// 冒頭に以下の import 文を追加

import android.net.Uri;



p.60 リスト15 (追加)

// 以下の1行を削除し、

ContentURI uri = ContentURI. ...

// 以下に変更

Uri uri = Uri.parse("content://hoge/female");



p.60 リスト15 (追加)  (android 0.9 beta 以降限定)

broadcastIntent(intent);   // 3箇所あり
→
sendBroadcast(intent);


Intent intent = new Intent(Intent.VIEW_ACTION);  // 2箇所あり
→
Intent intent = new Intent(Intent.ACTION_VIEW);


intent.addCategory(Intent.SAMPLE_CODE_CATEGORY);
→
intent.addCategory(Intent.CATEGORY_SAMPLE_CODE);



以上、android_m5r15 および android_0.9beta にてリスト15までは動作を確認しました。


第7章

ここから先は android_0.9beta のみでテストを行っています。

この章は、android の API が変更されていることに加え、 書籍中に全ソースが掲載されていないこともあり、ややしんどいです。
(バージョン m3 用の全ソースはオフィシャルサイトで配布されてはいますが)

面倒なので android_0.9beta で動作したソースを丸ごと貼り付けます。
まずは、「ボタンを押したら、指定された秒数待つサービス」のサンプルから。

[strings.xml]
(ゼロから書いたので、Google Android完全解説 追加情報 でダウンロードできるサンプルと変数名などが異なる可能性があります)
<?xml version="1.0" encoding="utf-8"?>
<resources>
	<string name="message">Input wait second</string>
	<string name="app_name">ServiceExample</string>
	<string name="button_startservice">Start Service</string>
	<string name="time_expired_message">Time expired!</string>
</resources>


[main.xml]
(ゼロから書いたので、Google Android完全解説 追加情報 でダウンロードできるサンプルと変数名などが異なる可能性があります)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	>
<TextView  
	android:layout_width="fill_parent" 
	android:layout_height="wrap_content" 
	android:text="@string/message"
	/>
<EditText android:id="@+id/edit_sleeptime"
	android:layout_width="fill_parent" 
	android:layout_height="wrap_content" 
	android:numeric="integer"
	android:text="2"
	/>
<Button
	android:id="@+id/button_startservice"
	android:layout_width="fill_parent" 
	android:layout_height="wrap_content"
	android:text="@string/button_startservice" 
	/>
</LinearLayout>


[ServiceExample.java]
新規→クラス で以下を作成
package com.tk.android.serviceexample; // 自分のパッケージ名で

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.content.Intent;

public class ServiceExample extends Activity {
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		Button startButton = (Button)findViewById(R.id.button_startservice);

		startButton.setOnClickListener(
			new View.OnClickListener(){
				public void onClick(View view){
					int sleepTime;
					try{
						EditText sleepTimeEdit=(EditText)findViewById(R.id.edit_sleeptime);
						sleepTime = Integer.parseInt(sleepTimeEdit.getText().toString())*1000;
					}catch(NumberFormatException e){
						sleepTime = 1000;
					}
					Intent intent = new Intent(ServiceExample.this, BackGroundService.class);
        				
					intent.putExtra("sleepTime", sleepTime);
					startService(intent);
				}
			}
		);
	}
}


[BackGroundService.java]
新規→クラス で以下を作成
package com.tk.android.serviceexample; // 自分のパッケージ名で

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
import android.os.Handler;

public class BackGroundService extends Service {
	
	@Override
	public IBinder onBind(Intent intent) {
		// TODO 自動生成されたメソッド・スタブ
		return null;
	}
	
	public void onStart(Intent intent, int startId){
		final Integer sleepTime = intent.getExtras().getInt("sleepTime");
		String message = "sleepTime is " + sleepTime + " [ms]";
		showMessage(message);
		
		Thread t = new Thread(){
			@Override
			public void run(){
				try{
					Thread.sleep(sleepTime.intValue());
				}catch(InterruptedException e){
				}
				handler.post(showMessageFromThread);
				
				BackGroundService.this.stopSelf(); // onDestroy() を呼び出して終了
			}
		};
		t.start();
	}

	public void onDestroy(){
		showMessage(getResources().getText(R.string.time_expired_message));
	}

	void showMessage(CharSequence cs){
		// スレッド t の run() メソッドから呼ぶとエラーが出る
		Toast.makeText(this, cs, Toast.LENGTH_SHORT).show();
	}
	
	// スレッド t の run() メソッドからは呼びたい場合は以下を用いる
	private final Handler handler = new Handler();
	
	// スレッド t の run() メソッドからは呼ばれる関数
	private final Runnable showMessageFromThread = new Runnable(){
		public void run(){
			showMessage("A message from run() method.");
		}
	};
}


以上、p.64 まで

次に Service に接続する例。この例は定番の変更ばかりなので、
Google Android完全解説 追加情報 でダウンロードできるサンプルから少しの変更で済みます。

[IMessageService.aidl]
パッケージ名で右クリックして「新規」→「その他」
そして「一般」→「ファイル」にてファイル名を IMessageService.aidl と入力する
以下の内容で保存すると、IMessageService.java が自動生成されるが、 このファイルは読み取り専用であり、触る必要がない。

package com.tk.android.bindservice;  // 自分のパッケージ名で

interface IMessageService
{
	void setMessage(String message);
	String getMessage();
	int getPid();

}


[MessageService.java]
新規→クラス で以下を作成
package com.tk.android.bindservice; // 自分のパッケージ名で

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.DeadObjectException;
import android.os.Process;
import android.widget.Toast;

public class MessageService extends Service {

	private String message;
	private final IBinder binder = new IMessageService.Stub(){
		public String getMessage() throws DeadObjectException{
			return message;
		}
		
		public void setMessage(String message) throws DeadObjectException{
			MessageService.this.message = message;
		}
		
		public int getPid(){
			return Process.myPid();			
		}	
	};
	
	@Override
	public IBinder onBind(Intent arg0) {
		// TODO 自動生成されたメソッド・スタブ
		return binder;
	}
	@Override
	public void onDestroy() {
		  showMessage(getResources().getText(R.string.service_destroyed_message));
	}
	@Override
	public void onCreate() {
		  showMessage(getResources().getText(R.string.service_create_message));
	}
	public void onStart(Intent intent, int startId) {
		showMessage(getResources().getText(R.string.service_start_message));
	}
	void showMessage(CharSequence cs){
		Toast.makeText(this, cs, Toast.LENGTH_SHORT).show();
	}
}


[BindServiceExample.java]
新規→クラス で以下を作成
package com.tk.android.bindservice;  // 自分のパッケージ名で

import android.app.Activity;
import android.os.Bundle;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.DeadObjectException;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Process;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class BindServiceExample extends Activity {

	private Button bindButton;
	private Button unbindButton;
	private Button killProcessButton;
	private Button setMessageButton;
	private Button getMessageButton;
	private EditText messageEditText;
	private TextView connectionStatusText;
	private boolean connected = false;
	private IMessageService messageService = null;
	
	private ServiceConnection messageServiceConnection = new ServiceConnection() {
		public void onServiceConnected(ComponentName name, IBinder service) {
			messageService = IMessageService.Stub.asInterface(service);
			showMessage(getResources().getText(R.string.on_service_connected_message));
		}
		public void onServiceDisconnected(ComponentName name) {
			messageService = null;
			showMessage(getResources().getText(R.string.on_service_disconnected_message));
		}
	};
	  
    /** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
        
		connectionStatusText = (TextView) findViewById(R.id.connectionStatusText);
		bindButton = (Button) findViewById(R.id.bindButton);
		unbindButton = (Button) findViewById(R.id.unbindButton);
		killProcessButton = (Button) findViewById(R.id.killProcess);
		setMessageButton = (Button) findViewById(R.id.setMessageButton);
		getMessageButton = (Button) findViewById(R.id.getMessageButton);
		messageEditText = (EditText) findViewById(R.id.messageEditText);

		bindButton.setOnClickListener(bindButtonListener);
		unbindButton.setOnClickListener(unbindButtonListener);
		setMessageButton.setOnClickListener(setMessageButtonListener);
		getMessageButton.setOnClickListener(getMessageButtonListener);
		killProcessButton.setOnClickListener(killProcessButtonListener);

		bindButton.requestFocus();
    }

	private OnClickListener bindButtonListener = new OnClickListener() {
		public void onClick(View view) {

			if (!connected) {
				bindService(new Intent(BindServiceExample.this, MessageService.class),
					messageServiceConnection, Context.BIND_AUTO_CREATE);
				connected = true;
				connectionStatusText.setText(getText(R.string.connected_message));
				messageEditText.requestFocus();
			}
		}
	};
	private OnClickListener unbindButtonListener = new OnClickListener() {
		public void onClick(View view) {
			if (connected) {
				unbindService(messageServiceConnection);
				connected = false;
				connectionStatusText.setText(getText(R.string.disconnected_message));
				bindButton.requestFocus();
			}
		}
	};
	private OnClickListener setMessageButtonListener = new OnClickListener() {
		public void onClick(View view) {
			if (connected) {
				try {
					messageService.setMessage(messageEditText.getText().toString());
				} catch (DeadObjectException e) {
					showMessage(getResources().getText(R.string.process_already_killed_message));
				}catch (RemoteException e) {}
			}
		}
	};
	private OnClickListener getMessageButtonListener = new OnClickListener() {
		public void onClick(View view) {
			if (connected) {
				try {
					String message = messageService.getMessage();
					showMessage(message);
				} catch (DeadObjectException e) {
					showMessage(getResources().getText(R.string.process_already_killed_message));
				}catch (RemoteException e) {}
			}
		}
	};

	private OnClickListener killProcessButtonListener = new OnClickListener() {
		public void onClick(View view) {
			if (connected) {
				try {
					int pid = messageService.getPid();
					Process.killProcess(pid);
				} catch (DeadObjectException e) {
					showMessage(getResources().getText(R.string.process_already_killed_message));
				}catch (RemoteException e) {}
			}
		}
	};   
	void showMessage(CharSequence cs){
		Toast.makeText(this, cs, Toast.LENGTH_SHORT).show();
	}
}


[strings.xml]
(Google Android完全解説 追加情報 でダウンロードできるサンプルと同じです)

[main.xml]
(基本的にはGoogle Android完全解説 追加情報 でダウンロードできるサンプルと同じです。
ただし、textAlign に関する行は全て削除し、id= を android:id= に書き換えました )

[AndroidManifest.xmls]
service の行だけ、以下で置き換えます。
<service android:name=".MessageService" android:process=":remote" />


以上、68ページまで



金丸隆志の「ダウンロードページ」に戻る