顯示具有 Android 標籤的文章。 顯示所有文章
顯示具有 Android 標籤的文章。 顯示所有文章

2009年8月7日 星期五

作品:Android遊戲設計-XGame

前幾個月花了點時間隨便弄出來的小遊戲
簡單來說,就是類似RPG遊戲製作大師那種東西
因為只是想研究android,就沒有特別去設計遊戲內容了

遊戲Menu



一開始是Menu的Activity,他可以選擇進入其他兩個Activity
總共三個選項,使用ListActivity配合相對排版實作出來
其中為了讓選單可以被選擇時出現圖片的特效
特別自訂了一個SelectAdapter,他去implement了BaseAdapter
因此我可以讓選單擁有提示圖片的效果,並且使用了全螢幕顯示特效。













Option



Option的部分,他提供難易度選擇跟音量大小選擇的部分(音樂未實作),在這部份使用了TabActivity去做,並替每個Tab加上了各種圖示。
一開始我以為事件處裡的部分很簡單,想不到花了我不少工夫才搞好...

Game


遊戲本體部分,他使用了SurfaceView去做Render的動作
因為他的Performace比較好,在繪圖上自由度也比較高。
其中使用了磁磚式遊戲的方式去實作
他是用Tiler(Block)去拼湊地圖的方式去實作,但是Android並沒有提供相關函式,所以我就自己模擬實做了類似相關類別。














而遊戲結束方法就是衝到終點的藍色區塊,以此就可以結束遊戲
圖中會有怪物發射飛彈來阻撓玩家前進,一擊斃命
中了藍色子彈是直接GG


















恩.......不是什麼出色的作品,畢竟只是實驗性質下的產物
請客官不要太嚴厲

2009年6月14日 星期日

Android 遊戲製作心得

這幾天嘗試用Android去寫一個遊戲,由於他不像J2ME有豐富的遊戲套件
所以不少東西都要自己慢慢打造,當然這可以享受自己造車的快感
而且也能學到更多東西。

一開始的時候參考了LunarLander這個Goolge官方範例,使用Drawable家族的BitmapDrawable來作繪圖的動作,但是這是我一開始作的最大的錯誤的決定。

使用BitmapDrawable來繪圖真的只能用萬劫不復來形容我的心情,不但要多用一層warpper去包Bitmap而導致Constructor使用的困難,要設定圖片座標一定得用setBound函式,更重要的是只用BitmapDrawable還不能更改Bitmap,一定得用BitmapDrawable陣列去做,後來去trace一下BitmapDrawable的原始碼,他把setBitmap這個動作設成private,幾乎是綁定了一個BitmapDrawable一個Bitmap。

而這還不打緊,更痛苦的是使用BitmapDrawable更讓我遊戲效率大幅下降,Lag頻頻(算FPS可能不到十張吧)。看了一下原始碼BitmapDrawable的draw還真作不少事,而且三不五時就會跳出

Android ......... not responding

這樣的警告訊息(中間的字忘了),CPU內存不夠,遊戲無法接受我的input,必須選擇wait讓CPU有時間去執行我的input。

後來改用Canvas的drawBitmap函式直接去繪畫Bitmap後,一切都迎刃而解
不但效率大幅提高,Android not responding的警告也幾乎不會出現了
而且在類別設計上更容易更有彈性,雖然重構花了不少時間,但卻相當值得。

最後,附上一張遊戲選單的畫面

2009年6月10日 星期三

Android Canvas的save()跟restore()函式

在看有關SurfaceView等相關code的時候
有時會看到save()跟restore()兩個函式包起來的程式碼片段

..
canvas.save();
canvas.rotate((float) mHeading, (float) mX, mCanvasHeight
- (float) mY);

if (mMode == STATE_LOSE) {
mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
+ mLanderHeight);
mCrashedImage.draw(canvas);
} else if (mEngineFiring) {
mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
+ mLanderHeight);
mFiringImage.draw(canvas);
} else {
mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
+ mLanderHeight);
mLanderImage.draw(canvas);
}
canvas.restore();
..

如上面所示,因為某些繪圖上的效果必須透過canvas提供的函式達到
所以必須修改canvas的狀態,如上面紅字的部分canvas.rotate()
但是問題是其他繪圖物件也必須用到canvas,因此必須有個機制能夠回復canvas被改變的狀態
這可以夠過canvas提供的save跟restore兩個函式辦到
先用save保存canvas的狀態,之後再去做須要改變canvas的動作,最後用完了再用restore把canvas的狀態復原

舉個例子

canves.save();
canves.rotate(20.86f);
people.draw(canves);
canves.restore();
p.setColor(Color.BLUE);
canves.drawRect(new Rect(100,100,200,200), p);

我希望people可以轉個角度,所以改變了canvas的rotate,但是我之後必須畫一個正方形
但我不希望他跟著旋轉,所以就用save跟restore鎖定要改變角度的程式碼
這樣可以讓其他繪圖工作不受影響

結果如下


如果我不用save、restore的話,藍色正方形的部分會跟著人類圖像一起旋轉

2009年6月7日 星期日

IllegalThreadStateException in LunarLander

在實驗LunarLander 的時後,如果在run LunarLander的途中,按下Home跳出
而不是按下return的話,此時在進入LunarLander,就會跑出 IllegalThreadStateException這樣的例外


稍微研究了一下這個原因


首先看一下這張圖,綠色框起來是Return,藍色框起來是Home
比較這兩鍵按下去返回主選單在回去遊戲的不同

Return
生命週期變化

onCreate->onStart->onResume->按下Retuen->onPause->onStop->onDestroy
->回到LunarLander->onCreate->onStart->onResume



Home
生命週期變化

onCreate->onStart->onResume->按下Home->onPause->onStop
->回到LunarLander->onRestart->onStart->onResume


上面的比較可以發現,按下Return的時候,整個Activity會依照正常程序Destroy在Create
但是按下Home回到主選單的時候Activity不會Destroy掉,他的資料仍然會存在記憶體,之後只是Restart他

而LunarLander遊戲設計是假設SurfaceView的生命週期跟Activity一致,在Activity的Create跟Destroy這段期間,只會發生一次的surfaceCreate跟surfaceDestroy

來觀察一下按下Home的時候,SurfaceView的生命週期

onCreate->onStart->onResume
onWindowFocusChanged->surfaceCreated->surfaceChanged->按下Home->onPause->surfaceDestroyed->onWindowFocusChanged->onStop
->回到LunarLander->onRestart->onStart->onResume->surfaceCreated
->
surfaceChanged->onWindowFocusChanged


就像上面所示,因為Activity沒有照正常程序被Destroy掉
而在Activity的create跟destroy之間,SurfaceView的create跟Destroy執行了超過一次以上,這是沒有在當出遊戲設計者的預料之中,所以會發生IllegalThreadStateException這個例外而不能執行的Bug

IllegalThreadStateException是怎麼發生的呢?
先來看他的定義

Thrown when an operation is attempted which is not possible given the state that the executing thread is in.

也就是試圖從相同的程序中start相同的Thread
舉個例子

Thread t=new Thread(new Runnable(){

@Override
public void run() {
// TODO Auto-generated method stub

}});
t.start();
t.start();//start兩次,拋出IllegalThreadStateException

而要避開這例外,必須使用新的Thread

Thread t=new Thread(new Runnable(){

@Override
public void run() {
// TODO Auto-generated method stub

}});
t.start();
t=new Thread(..........);
t.start();



而在Lunar,IllegalThreadStateException是從LunarView的surfaceCreated中拋出來的

/*
* Callback invoked when the Surface has been created and is ready to be
* used.
*/
public void surfaceCreated(SurfaceHolder holder) {
....
thread.start();//由此函式拋出IllegalThreadStateException
}

如上面所示,因為surfaceCreated在Activity執行了兩次以上的話,相同的thread就會start兩次以上,thread的產生是在Activity的onCreate呼叫建構式LunarView時誕生,所以只會被產生一次

public LunarView(Context context, AttributeSet attrs) {
....
thread = new LunarThread(holder, _context, new Handler() {
@Override
public void handleMessage(Message m) {
mStatusText.setVisibility(m.getData().getInt("viz"));
mStatusText.setText(m.getData().getString("text"));
}
});

....
}

在此我提出一個暴力法的解決方案解決這Bug,就是在surfaceCreated的時候就產生一個新的LunarThread,這樣可以避免同樣的thread被start兩次以上,不過我沒做State的Retore,所以他就跟按下Return一樣是從新開始一個遊戲

我的作法就是讓LunarThread的生命週期跟Surface一致
  • surfaceCreated:產生LunarThread
  • surfaceDestroyed:消滅LunarThread


修改方法如下,粗體是我新增的程式碼
surfaceCreated

public void surfaceCreated(SurfaceHolder holder) {
/*判斷thread是否為null,是的話產生新的Thread*/
if (thread == null) {
/*
把建構式創造thread的程式碼copy過來,記得建構式保持原樣就好,不須要更改
*/

thread = new LunarThread(holder, _context, new Handler() {
@Override
public void handleMessage(Message m) {
mStatusText.setVisibility(m.getData().getInt("viz"));
mStatusText.setText(m.getData().getString("text"));
}
});
setFocusable(true);

/*這邊是跟建構式不同的地方,因為按下Home的時候,會改變thread的狀態
在這邊要手動把狀態設置為ready來讓keyEvent能正常動作
*/

thread.setState(LunarThread.STATE_READY);
}


thread.setRunning(true);
thread.start();
}


surfaceDestroyed

public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
/*在此把thread設為null消滅現有的thread
這邊是為了配合surfaceCreated的if(thread==null)
*/

thread=null;
}


onWindowFocusChanged

public void onWindowFocusChanged(boolean hasWindowFocus) {

if (!hasWindowFocus)
{
/*surfaceDestroy發生後會在發生 onWindowFocusChanged事件
因為thread已經被設為null了所以不能在執行函式動作
這也是為什麼surfaceCreated要手動設置Ready
*/

if(thread!=null)
thread.pause();
}

}


以上,改完就大功告成了
--------------------
更好的解決方案
在建構式ListView的時候創造跟啟動執行緒(相較於在surfaceCreate啟動執行緒)
創造一個lock的物件
而在surfaceCreate的時候使用notify跟restore
在surfaceDestroy的時候使用wait跟相關儲存動作
創造一個函式destroyGame()真正跳出整個遊戲,只有在Activity的onDestroy去呼叫他(相較於在surfaceDestroy結束執行緒)

不過我沒有去真正實作上面的方案,只是構想,不過因該是八九不離十

2009年6月5日 星期五

Android 應用程式全螢幕作法

當初看到這個功能的時候說老實話我也嚇了一跳
沒搞錯吧,手機也要玩全螢幕,如果說一般PC遊戲程式用全螢幕我還相信
因為全螢幕的狀態遊戲程式的效能會比視窗模式還要高上許多
不過Android的確有提供這個功能,就是為了替手機的遊戲程式等提供更乾淨的介面


來看一下這張圖

一個Android應用程式會有兩個不屬於我們佈局檔的東西(圖看不清楚請點開放大)
  1. 第一個是上面紅框圈起來,也就是手機狀態的Bar
  2. 第二個是則是綠框圈起來的部分,是我們應用程式的標題


而要如何讓他們消失呢
可以用一行程式

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);

首先用Activity的getWindow()函式得到Window物件
之後用他的setFlag()函式去設定視窗屬性在此用WindowManager.LayoutParams.FLAG_FULLSCREEN
代表我要設定為全螢幕

來看一下效果


唉呀!標題框還在沒消除。那個標題框是Activity的顯示屬姓,所以必須仰賴Activity的requestWindowFeature函式
去對Activity增加顯示效果,在此我使用

requestWindowFeature(Window.FEATURE_NO_TITLE);

這個是Google的遊戲範例,像是Snake、LunarLander都會看到的一行程式,使用Window.FEATURE_NO_TITLE這個參數,作用就是把應用程式的標題給移除。

onCreate的部分程式


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);

setContentView(R.layout.list);
........
}



最後來看一下效果吧

他就變成一個完整的全螢幕應用程式了

2009年6月2日 星期二

startActivity跟startActivityForResult

Android想切換新的Activity的時候
最常用的兩個函式就是startActivity跟startActivityForResult
比方說我想讓程式去開一個網頁就可以用

Uri uri=Uri.parse("http://www.google.com.tw");
Intent i=new Intent(Intent.ACTION_VIEW,uri);
startActivity(i);

在Android這個動作必須先創造Intent(意圖),也就是我有個"意圖"想要喚醒某個動作
但是我不能直接去叫用Activity,必須將想叫起的Activity變成Intent然後把意圖丟給startActivity,讓他去告訴Android我有個意圖,請他幫我執行,並且可以透過finish()關掉一個Activity

而startActivity跟startActivityForResult又有什麼不同?
startActivity是個單向開啟的動作,可以透過Bundle傳資料給下一個Activity
但是沒辦法從下一個Activity那邊接收訊息

舉個例子,有隻BMI的Activity想要把Data傳給Report這個Activity顯示


BMI.java

....
public void onClick(View v) {
Intent intent=new Intent();
intent.setClass(BMI.this, Report.class);
Bundle bundle=new Bundle();
bundle.putString("meta", "BMI:");
bundle.putDouble("BMI", 25.0);
intent.putExtras(bundle);
startActivity(intent);

}
...


Report.java

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.report);
TextView _msg=(TextView)findViewById(R.id.result);
Bundle b=this.getIntent().getExtras();
_msg.setText(b.getString("meta")+b.getDouble("BMI"));

透過意圖(Intent)把一些資訊一起帶給下一個Activity,並且新的Activity可以透過Intent收到資料

但是上面只能單向傳遞資料,可是有時候我希望可以從新的Activity得到一些資訊
這時候就能使用startActivityForResult,他代表我開啟一個Activity並等待他傳些東西回來,而使用startActivityForResult的時候,必須複寫Activity的onActivityResult函式才能真的有作用

舉個例子,假設我有個A想要開啟B並等待他傳回些什麼

A code

private static final int EDIT=1;
....
Intent intent=new Intent();
intent.setClass(this,Edit.class);
startActivityForResult(intent, EDIT);

startActivityForResult除了要傳輸的意圖之外,還要帶一個參數requestCode,這是為了讓接收資料的onActivityResult能夠辨別是哪個Activity回傳的資料,因為我可能一個Activity能夠開啟很多不同的Activity


B code

....
Intent i=new Intent();
Bundle b=new Bundle();
b.putString("B", "I am B");
i.putExtras(b);
setResult(RESULT_OK,i);
finish();
....

或者我不需要傳資料,只是通知A是B返回的

....
setResult(RESULT_OK);
finish();
....

setResult這個函式可以帶兩個參數,一個是resultCode,告知onActivityResult這次洞做是否成功,RESULT_OK是個常數,第二個參數是可選的,一個intent,主要是把資料回傳給上一個Activity,也可以不用回傳資料

最後回來看onActivityResult

A code

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);

switch(requestCode){
case EDIT:

Toast.makeText(this, data.getExtras().getString("B"), 0).show();
}
}

onActivityResult帶三個參數
  1. requestCode:是對應上面的startActivityForResult的第二個參數,判斷是由哪個Activity回傳資料,上面我是用EDIT
    startActivityForResult(intent, EDIT);
    所以這邊用swtich去判斷他是不是EDIT,是的話就用Toast.makeText去製造一個訊息Bar把他顯示出來
  2. resultCode:對應B code的setResult()的第一個參數,其值是上面設的RESULT_OK,在此我們還用不到
  3. data:對應B code的setResult()的第二個參數(如果有設的話),有就是B回傳的資料,可以用data.getExtras()得到Bundle把資料取出來

ListView跟ListActivity小考究

因為Goolge有隻範例程式NotePad,所以目前的書籍很多都會用這個當例子
裡面會用到ListActivity這東西,今天我想探討其相關議題,包括如後自訂List的格式
他會使用到BaseAdapter這東西

說到ListActivity呢,就要先談及Activiry,Activity是Android四大支柱之一
他是代表一個顯示在手機上的"畫面",每個程式都至少有一個Activity。
而ListActivity是從Actitivy延伸出去的,用來顯示列表。
但是已經有個ListView可以顯示列表了,為何還有個ListActivity,他跟ListView又有什麼不同
ListActivity簡單來說是一個包含ListView的Activity,他將ListView的功能從新包裝,扮演一個wrapper的角色,讓ListView更容易控制

ListActivity的部分原始碼

public class ListActivity extends Activity {

protected ListView mList;

private Handler mHandler = new Handler();
private boolean mFinishedStart = false;
.....

}


下面一個簡單的ListView範例


public class Note extends Activity {
String[] data=new String[]{"A","B","C"};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.main);
ListView list=(ListView)findViewById(R.id.ListView01);
ListAdapter ad=new ArrayAdapter (this,android.R.layout.simple_list_item_1,data);
list.setAdapter(ad);
list.setOnItemClickListener(new AdapterView.OnItemClickListener(){

@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,long arg3) {

TextView txt=null;
txt=(TextView)arg0.getChildAt((int)arg3);
}});
}

在上面程式中可以用findViewById找出我在main.xml設定的ListView,好處是名子沒有被綁死,如果用ListActivity的話,佈置(layout)檔的ListView名子會被鎖定,上面設定Row用了佈局android.R.layout.simple_list_item_1,他跟R.layout不一樣,他是android.R.layout開頭,是Android提供的預設排版,懶惰的時候可以直接套Android提供的資源,除了layout之外他還有提供各種圖示,如果想寫一些通訊錄之類的,可以使用他提供的android.R.drawable.sym_call_incoming的圖示。
ListView想處裡按下事件的話是使用 AdapterView.OnItemClickListener,他提供的函式並不容易處裡閱讀,所以ListActivity提供了更好的界面

ListActivity一點簡單的程式

public class Note extends ListActivity {
/** Called when the activity is first created. */
String[] data=new String[]{"A","B","C"};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
getListView().setEmptyView(findViewById(R.id.empty));

Button b=(Button)findViewById(R.id.Button01);
b.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View v) {
fillData();
}});

}


private void fillData(){
ListAdapter ad=new ArrayAdapter(this,R.layout.notes_row,data);
setListAdapter(ad);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// TODO Auto-generated method stub
super.onListItemClick(l, v, position, id);
TextView txt=null;
txt=(TextView)l.getChildAt(position);
}

}

ListActivity的佈局檔,在此我取list,在ListActivity的佈局檔裡面最少要有一個ListView,而且名子必須是android:list,這是被綁死的,而且必須指定row的佈局,在此我用R.layout.notes_row,裡面只有一個TextView


<Listview android:id="@+id/android:list">

佈局檔

<?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"
>

<ListView android:id="@+id/android:list"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
></ListView>
<TextView
android:id="@+id/empty"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="No Data"
/>

<Button android:text="Get" android:id="@+id/Button01" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
</LinearLayout>




我們可以看一下ListActivity原始碼設局的部分

public void onContentChanged() {
super.onContentChanged();
View emptyView = findViewById(com.android.internal.R.id.empty);
mList = (ListView)findViewById(com.android.internal.R.id.list);
if (mList == null) {
throw new RuntimeException(
"Your content must have a ListView whose id attribute is " +
"'android.R.id.list'");
}
.....
}

如果ListActivity用的ListView名子不為android:list就會丟出RuntimeException的例外

ListActivity對於按下事件監聽是用onListItemClick(ListView l, View v, int position, long id) ,這1比ListView提供的更好使用且更清晰,裡面幾個主要的參數ListView l,得到ListView主體。position是代表被按下item的編號,ListView預設是只有TextView,所以ListView看起來像是個TextView陣列,如果想自定義ListView的View必須自己寫一個類別繼承BaseAdapter
而最後一個參數id可以跟SQLite配合,他會回傳SQLite裡面的_id的值

SQLite的部分,請去參閱手冊,在Android裡面SQLite資料表配合CursorAdapter一定得有個名叫"_id"的主鍵,Android幾個有關Cursor的Adapter
  1. CursorAdapter
  2. ResourceCursorAdapter
  3. SimpleCursorAdapter

其中SimpleCursorAdapter繼承ResourceCursorAdapter,ResourceCursorAdapter又去繼承CursorAdapter。

而如果想要自定ListView的型態的話,必須寫個類別繼承
比方說帶著圖片的ListView


首先,先看一下一般書上都有的範例,或者到官方的網站也可以看到
http://developer.android.com/guide/samples/NotePad/index.html

private void fillData(){
Cursor c=mdb.fetchAll();
startManagingCursor(c);
c.moveToFirst();
String[] from=new String[]{mdb.KEY_NOTE};
int[] to =new int[]{R.id.text};
SimpleCursorAdapter ad=new SimpleCursorAdapter(this,R.layout.notes_row,c,from,to);
setListAdapter(ad);
}

fillData是把資料庫的東西填到ListView,一般都會用SimpleCursorAdapter去轉接,但是這不能滿足我們,所以我照自己的方式修改了一下

private void fillData(){
Cursor c=mdb.fetchAll();
startManagingCursor(c);
IconAdapter icad=new IconAdapter(this,c);
setListAdapter(icad);
}

寫一個帶著圖片的轉接器,來看一下IconAdapter 的程式碼

static class IconAdapter extends BaseAdapter{

private Context _ctx;
private ArrayList notes;
private LayoutInflater mlin;
private Bitmap icon;
private Cursor _c;
public IconAdapter(Context ctx,Cursor c){...}

,@Override
public int getCount() {...}

@Override
public Object getItem(int arg0) {...}

@Override
public long getItemId(int arg0) {...}

@Override
public View getView(int pos, View view, ViewGroup arg2) {...}

static class Holder{
TextView text;
ImageView icon;
}
}

BaseAdapter有四個函式要複寫getCount、getItem、getItemId、getView,而且不能亂寫
因為我是要從資料庫取資料,所以我用了Cursor當參數,用ArrayList存他,如果有買『旗標:Android程式設計與應用』的話,他裡面的例子是用靜態陣列當資料,但是那不能滿足我們需求

一步一步解析

建構式

public IconAdapter(Context ctx,Cursor c){
_ctx=ctx;
_c=c;
mlin=LayoutInflater.from(ctx);
notes=new ArrayList();
int index=0;

for(int i=0;i<c.getColumnCount();i++)
{
if(c.getColumnName(i).equals(NoteDbAdapter.KEY_NOTE))
{
index=i;
}
}

c.moveToFirst();
for(int i=0;i<c.getCount();i++)
{
notes.add(c.getString(index));
c.moveToNext();
}

icon=BitmapFactory.decodeResource(
_ctx.getResources(),android.R.drawable.sym_call_incoming);
}

裡面只是一般的設值,Cursor的操作請參考上一篇http://hatsukiakio.blogspot.com/2009/06/cursorindexoutofboundsexception.html
而函式LayoutInflater.from(this),這是要把佈局檔從xml file轉成Layout物件,這之後會用到
而要將資源轉成Bitmap則必須使用BitmapFactory.decodeResource(res,resID);
第一個參數可以用Context.getResources()取得,第二個是圖片ID,在此我們使用系統的來電圖示

getCount()

public int getCount() {

return notes.size();
}

要複寫函式之一,主要是這個ListView共有幾筆資料,我直接回傳ArrayList的size

getItem(int arg0)

public Object getItem(int arg0) {
return getView(arg0, null, null);

要複寫函式之二,回傳第arg0筆資料的物件,在此我交給getView去做

getItemId(int arg0)

@Override
public long getItemId(int arg0) {

if(_c!=null)
{
//重要
_c.moveToPosition(arg0);
return _c.getLong(0);
}
else{
return 0;
}
}

要複寫函式之三,這個函式很重要,如果要希望ListView能夠執行SQLite的執行刪除等動作,這個函式一定要寫對,因為onListItemClick(ListView l, View v, int position, long id)第四個參數id就是從這個函式來的,參考我們上面說的,想要對資料庫作動作須仰賴這個屬姓
之前因為沒寫好這個函式,導致程式雖然顯示正常,但是資料庫動作都錯掉了
一開始我們用一個變數Cursor _c儲存了傳進來的資料庫指標,先判斷他是不是null,不是的話用moveToPosition()移到指定位置把該row的"_id"欄位抓出來回傳,在此我的"_id"欄是在第0個位置(getItem跟getItemId傳進來的參數都是被點選row的position)

getView(int pos, View view, ViewGroup arg2)

@Override
public View getView(int pos, View view, ViewGroup arg2) {

Holder holder=null;
if(view==null)
{
view=mlin.inflate(R.layout.icon, null);
holder=new Holder();
holder.icon=(ImageView)view.findViewById(R.id.icon);
holder.text=(TextView)view.findViewById(R.id.icontext);
view.setTag(holder);

}
else{
holder=(Holder)view.getTag();
}

holder.text.setText(notes.get(pos));
holder.icon.setImageBitmap(icon);
return view;
}

要複寫函式之四,整個BaseAdapter最核心的函式,沒有他連顯示都辦不到
他會先傳近三個參數(第pos個row,第pos個row的View物件,Viewgroup)
我們真正會用到的只有前兩個
在看程式碼之前先看看R.layout.icon的佈局檔,裡面有一個ImageView、一個TextView

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ImageView
android:id="@+id/icon"
android:layout_width="35sp"
android:layout_height="35sp"
></ImageView>
<TextView
android:id="@+id/icontext"
android:gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
></TextView>
</LinearLayout>

而為了儲存這ImageView跟TextView,我宣告了一個資料結構

static class Holder{
TextView text;
ImageView icon;
}

一開始先宣告變數holder,然後看看現在這個位置的View是否產生過
如果沒有(view==null),先讓View產生一個Layout佈局的物件,透過inflate函式可以把xml file的佈局檔轉成View物件

view=mlin.inflate(R.layout.icon, null);

之後就可以用findViewById取得佈局檔裡面的ImageView跟TextView,之後用setTag把Holder這資料結構儲存,以方便之後會再用到

holder=new Holder();
holder.icon=(ImageView)view.findViewById(R.id.icon);
holder.text=(TextView)view.findViewById(R.id.icontext);
view.setTag(holder);

如果View已經產生過了,就可以用getTag把之前儲存的Holder取出來

holder=(Holder)view.getTag();

之後把內容設定好後就可以回傳了

holder.text.setText(notes.get(pos));
holder.icon.setImageBitmap(icon);
return view;

-----------------------
這邊在提一下取用的方法

SimpleCursorAdapter ad=new SimpleCursorAdapter(this,R.layout.notes_row,c,from,to);
setListAdapter(ad);
...
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
TextView txt=(TextView)l.getChildAt(position);
...
}
}

在使用預設的SimpleCursorAdapter 時候,ListView的佈局只是一個TextView,可以直接用getChildAt(position)把內容抓出來

但是用了我們自己的IconAdapter的時候,要依據我們的佈局去抓,像是我用一個LinearLayout去包一個TextView,一個ImageView,所以用getChildAt(position)抓出來的是一個LinearLayout物件,要再去裡面抓想要的資料


IconAdapter icad=new IconAdapter(this,c);
setListAdapter(icad);
.........
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
LinearLayout lay=(LinearLayout)l.getChildAt(position);
TextView txt=(TextView)lay.findViewById(R.id.icontext);
.........
}



感謝2F的意見,下面是修改後的程式碼
上面錯誤的部分我就留者對照比較了

IconAdapter icad=new IconAdapter(this,c);
setListAdapter(icad);
.........
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
LinearLayout lay=(LinearLayout)l.getChildAt(position-l.getFirstVisiblePosition());
TextView txt=(TextView)lay.findViewById(R.id.icontext);
.........
}

2009年6月1日 星期一

CursorIndexOutOfBoundsException

今天嘗試自己寫Adapter的時候,在操作Cursor出現了這樣的一個錯誤

android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 1
at android.database.AbstractCursor.checkPosition(AbstractCursor.java:559)
at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:172)
at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:41)
at net.poemcode.android.PurseEdit.onCreate(PurseEdit.java:41)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1122)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2104)


CursorIndexOutOfBoundsException,為什麼呢?
看了一下我寫的程式

Cursor c;
.....
int index=0;

for(int i=0;i<c.getColumnCount();i++)
{
if(c.getColumnName(i).equals(NoteDbAdapter.KEY_NOTE))
{
index=i;
}
}

for(int i=0;i<c.getCount();i++)
{
notes.add(c.getString(index));
c.moveToNext();
}

好像也沒什麼錯,查了一下這個例外是由Cursor.getString()丟出來的
查了一下index值也沒錯,getColumnName()等函式也運作正常
後來看了一下源碼,原來是我用SQLite得到的Query結果會存在Cursor這個指標裡
但是這個指標一開始是指向-1的位置
所以源碼裡有一行moveToFirst(),把cursor指到第一個位置,只要把這函式加進去就可以解決了
修改版如下

Cursor c;
.....
int index=0;

for(int i=0;i<c.getColumnCount();i++)
{
if(c.getColumnName(i).equals(NoteDbAdapter.KEY_NOTE))
{
index=i;
}
}
c.moveToFirst();
for(int i=0;i<c.getCount();i++)
{
notes.add(c.getString(index));
c.moveToNext();
}

順便介紹一下Cursor幾個常用的函式

Cursor.getColumnCount()//得到資料表欄位總數
Cursor.getColumnName(int index);//得到資料表第index欄的欄名
Cursor.getColumnIndex(String name)//得到欄名為name的index
Cursor.getCount()//得到傳回資料表的資料筆數
Cursor.getType(int index)
//其中Type可以是String、short等,得到第index欄的資料,,會受到Cursor指標影響

moveToNext()//將Cursor指標移到下一個Row
moveToFirst()//將Cursor指標移到第一個Row
moveToPosition(int index)//將Cursor指標移到第index個Row

ActivityNotFoundException

今天程式寫一寫熊熊跑出ActivityNotFoundException這樣一個例外,明明程式檢察都沒錯
原來這個例外是由startActivity(Intent)函式拋出來的
這個例外會發生是因為startActivity找不到Intent所代表的Activity
這邊可以提一下,當執行startActivity,他會丟給Android去註冊表找Intent要求的Activity
如果註冊表找不到,就會丟出ActivityNotFoundException的例外
而為什麼會找不到呢?
原來是忘了去AndrioidManifest.xml註冊
所有程式會用到的Activity都必須到AndrioidManifest.xml裡面去註冊,否則程式就算寫了也不能用

Android 生命週期探索



上面是Goole Android的一張活動流程圖
摘自http://developer.android.com/guide/topics/fundamentals.html
主要分成幾個主要的階段
  1. Create
  2. Start
  3. Restart
  4. Stop
  5. Pause
  6. Resume

其中跟記憶體等資源分配有關的活動有 CreateDestory,Activity記憶體等資源的規畫跟釋放都在這兩個階段完成

而影響是否能看見的事件有Start、ReStart、Stop三個狀態,當Activity跑到onstrat的時候,這個Activity就會處於螢幕上可以看見的狀態,而當處於Stop狀態的時候Activity就會從螢幕上消失,這邊要注意,來到stop只是讓Activity看不見,但是他仍存在,隨時可以用叫回來,而當Activity如果stop了,但是又被叫回來的話,就會先觸發Restart

當Activity是否能由使用者去引發Event,這要經過ResumePause狀態,當經過Resume的時候,才能正常做些什麼,反之亦然

總結以上,當"創造"一個Activity的時候,必然會經過三個階段
假設我有一個Activity叫A

A:onCreate->onStart->onResume

而當Activity呼叫另一個Activity的時候,會先呼叫Pause,在去創造新的Activity,然後在stop自己,假設A呼叫了Acivity B

A:onPause onStop
^
| |
ˇ
B: onCreate->onStart->onResume

其實A在pause自己之前會先執行一個onSaveInstanceState的階段,可以在這個階段做點事情




銷毀一個Activity必然會經過的三個階段則是

onPause->onStop->onDestroy


假設B已經退出了,現在回到A,B會先Pause,然後Restart A,之後在依照前面的步驟去start跟resume A,因為A之前沒有destroy,所以不會create而是restart,最後再回到B,把B給stop跟Destroy

A: onRestart->onStart->onResume
^
| |
ˇ
B: onPause onStop->onDestroy

而最後A也退出了,就會執行

A:onPause->onStop->onDestroy


所以不管如何,不論是或消滅都會經過三個階段,很多坊間的書登會提醒,要寫onResume跟onPause,因為當意外導致Activity被暫停的時候(比方說來電或沒電),必須把資料保留下來讓之後要回復的時候不會出錯
Resume跟Pause是程式創造、回復、消滅、暫停必然會執行的階段,所以防呆程式通常建議寫在這兩個地方

2009年5月30日 星期六

window環境下載Android source code

今天寫程式的時候突然想看一下Android的原始碼長怎樣
因為在寫ListActivity類別的時候對他的架構很好奇,但是SDK裡面的android.jar並沒有.java檔,所以找了一下windows下載原始碼的方法

首先要有下載工具,請到下列網址下載git,c


http://code.google.com/p/msysgit/downloads/list
直接下載(可能因為版本更新而失效)
Git-1.6.3-preview20090507-2.exe


之後到Android source網站
http://android.git.kernel.org/
裡面有Linux直接下載的方法,window要下載必須用上面的工具
上面的工具下載下來安裝後,執行Git Bash



之後會出現類似cmd的畫面,要下載source code,就輸入

git clone git://android.git.kernel.org/ + project path.


project path參照http://android.git.kernel.org/
請照自己的需要下載,整個Android原始碼2G多
舉個例子,我只想看android開發的相關類別像是TextView等等
我就只須要下載platform/frameworks/base.git,Android的framework都放在這
輸入

git clone git://android.git.kernel.org/platform/frameworks/base.git

就會開始下載了,之後就可以遨遊Android的世界

2009年5月29日 星期五

Log配合logcat

在Android除錯最常用的方式就是開Logcat來觀看,他可以取代JRE的Console來幫忙偵錯
不然每次程式錯誤都要寫個AlertDialog也是個麻煩
LogCat叫出方法,Window -> Show View -> Other,找Android -> LogCat

而在Android有個Log類別可以跟Logcat配合,他可以幫忙除錯,有點像以前我們寫C/C++用土法煉鋼printf來看資訊一樣

Log基本用法

Log.類型(標籤名子,訊息)

Log的類型大概分以下幾類
  1. Log.v(TAG,Message) :Verbose 記錄詳細訊息
  2. Log.d(TAG,Message) :Debug 除錯
  3. Log.i(TAG,Message) :INFO 資訊
  4. Log.w(TAG,Message):Warning 警告
  5. Log.e(TAG,Message) : Error 錯誤

------------
其中TAG是可以幫助我們篩選訊息,因為一般logcat會擷取一大堆訊息,包括GC做了什麼
自定Tag可以幫我們下Filter

LogCat雜七雜八訊息

而我加入了自己的訊息,假設我想記錄開啟程式成功與否

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.main);
Log.i("Create Android", "Test");
findVuew();
setClick();


}

而我想過濾那些雜七雜八的訊息,可以從右上的綠色加號增加filter

之後會跳出設定視窗

裡面幾個必要參數
  1. Filter Name:過濾名稱 可以隨自己喜好取
  2. by Log Tag:想留下的Tag名稱,在此設我剛剛取的Create Android
  3. by Log level :這選項是可選的,可以過濾Log的類型,在此我選none


而接下來,就只會剩下我想看到的訊息

Android一些資源站

在開發程式的時候總是希望自己的程式美美的
尤其是在講就豐富使用者介面的現在,更是須要美化程式界面的方法
從書上看到的一些資源站,主要是各種免費且漂亮的界面圖片、icon等等

Tango
http://tango.freedesktop.org/Tango_Icon_Library#Download

Nuvola
http://www.icon-king.com/projects/nuvola/

openclipart
http://openclipart.org/media/view/media/developer

使用findViewById傳回Null及Android重構

今天在寫Android的時候,使用findViewById尋找layout上的物件
原本好好的,但是想不到重構後變成不會跑了,後來用logcat看了一下
原來是findViewById回傳了null的物件,結果丟出NullPointerException
重構前程式

private EditText _width;
private EditText _height;
...
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.main);
_width=(EditText)findViewById(R.id.width_text);
_height=(EditText)findViewById(R.id.height_text);
}

重構後程式

private EditText _width;
private EditText _height;
...
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
findView();
setContentView(R.layout.main);

}
private void findView(){
_width=(EditText)findViewById(R.id.width_text);
_height=(EditText)findViewById(R.id.height_text);
}

------------
原來是我把findViewById的動作移到了 setContentView(R.layout.main);之前
在Android如果在使用setContentView把顯示元件創造出來之前
findViewById是找不到任何東西的,所以必須把findViewById的動作放到setContentView之後


這邊順便介紹一下,在Android裡有提供一個好用的重構工具:Extract Android String
在Android通常會把常數字串放到string.xml裡面存取,而當我們寫程式的時候通常很懶的再把東西寫到另外一個檔案裡面,這個工具可以簡單替我們辦到
而這項重構工具在Refactor->Android->Extract Android String




順便筆記一下,如果想存取string.xml,也就是R.string之下的東西,必須配合getText函式

_result.setText(getText(R.string.data);

2009年5月26日 星期二

Android 1.5 使用JUnit測試框架

對於一些習慣TDD開發方式的人來說
沒用JUnit好像少了什麼似的,就順便研究了一下在Android下如何使用JUnit
----------------
在Android裡面基本上有兩個方案

傳統JUnit


有需要的話可以到http://www.junit.org/ 下載
因為Android用的不是JDK在跑程式,而是用他自己的虛擬機器
所以在eclipse必須修改一下JUnit的執行環境才能跑,而且也只能跑一些java的邏輯測試或業務封裝,對於Andorid的測試不能用一般的JUnit去跑,這個可以用第二個方案解決

先介紹第一種一般JUnit的使用法,把junit-4.6.jar(版本可能有異)下載下來後
先在eclipse的 project->properties 跳出設定視窗後選擇Java Build Path->Libraries
選擇Add External JARs,把剛才下載的junit-4.6.jar加入eclipse
這個方法可用於想加入任何第三方API時使用


之後File->New->JUnit Test Case新增一個TestCase
假設程式碼如下

public class TestActor extends TestCase{

public TestActor(String name){
super(name);
}
public void testAction(){
assertEquals(true, true);//請修改為自己的測試程式
}

}

----
之後開啟Run Configutrations(不知道請參照使用eclipse開發Andrio 1.5)

開啟視窗後請先依序幾個步驟
  1. 先在左邊的JUnit那欄點兩下,會出現右邊這種設定卡,先選Test這個Tag
  2. 再來取個喜歡的名子
  3. 這個步驟比較重要,選定要執行的測試類別
  4. 選定Junit版本
  5. 點選Apply
再來跳到Classpath這個Tag,一開始會看到Bootstrap Entries是Android 1.5(版本可能有異)

請點選Android 1.5 後按下 Advanced 將他改成JRE



他會先跳出一個Advanced Options的選單,選則Add Classpath Variables後按下OK




他會又跳出一個選單,選擇你的JRE環境後按下OK,在此我的是JRE_LIB


最後在Bootstrap Entries上就會出現JRE的環境,這邊要注意一下User Entries有沒有Junit
沒有的話用Add External JARs將他加進來,之後按下Apply




之後再要測試的Testcast calss上按右鍵 Run As->JUnit Test就可以跑了


如果出現詢問視窗的話,先在Override workspace setting上打勾
而後選擇Eclipse JUnit Launcher按下OK就行了



注意一下Run的選單


這是1.5後才出現的,以後或許會再改也不一定,畢竟Android現在比起J2ME等仍然是不夠完善

Android JUnit Test


這是第二個方案,專門給Android專案作測試用的,Android的APIDemo裡面就有很詳盡的用法
註:1.5版後的ApiDemo範例改放到
android-sdk-windows-1.5_r1\platforms\android-1.5\samples目錄底下


-----
Android JUnit Test用法一樣是到Run Configutrations下產生一個新的JUnit的Config出來
不同的是程式寫法
首先一定要有繼承Activity的類別

public class Game extends Activity {
.....
}

然後寫測試程式,他必須繼承ActivityInstrumentationTestCase,而且泛型樣版必須放進一個繼承 Activity的類別

public class Test extends ActivityInstrumentationTestCase<Game> {
public Test(){
super("com.android.ut", Game.class);
}

public void testRun()
{
assertEquals(true, true);
}
}


這裡有一點要注意
super("com.android.ut", Game.class);
super第一個參數一定要是你專案的package名稱,不然跑出來結果會錯誤,在此我是隨便舉的例


之後我產生一個JUnit的Config,用預設的就好
再來最重要的是修改AndroidManifest.xml設定檔

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.cusano.star"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Game"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="org.cusano.star"
android:label="Tests "/>

<uses-sdk android:minSdkVersion="3" />
</manifest>


-----------
用顏色標住起來的地方就是我們要加進去的東西
<uses-library android:name="android.test.runner" />

是跟Android說要引進這個lib,不然Android不知道
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="org.cusano.star"
android:label="Tests "/>



有三個屬性,第一個屬性是固定的,targetPackage屬性要填專案當初設定的package名稱
label屬性則是在Android會看到的標籤


再來就可以選擇第一個Android JUnit Test去Run了

在此之前,可以叫出Logcat去觀察他的結果,Logcat是Android很好用的工具,可以幫忙觀察很多錯誤,叫出方法,Window -> Show View -> Other,找Android -> LogCat


在跑Android JUnit Test他會去叫出模擬器(avd記得先設定好),之後可以去Dev Tools下的Instrumentation,找到我們的測試程式,名子是AndroidManifest.xml當初設定label屬性,也就是Tests







按下去後就可以在Eclipse或是Logcat上面看結果

2009年5月25日 星期一

用eclipse發佈android的apk

當我們把程式寫好之後,總是希望別人使用他
而在Android必須發佈程apk檔才能讓人安裝
eclipse提供了很方便的道具來幫我們做到這件事
在apk裡面有個主要設定檔AndroidManifest.xml,包括了設定軟體版本或是權限等等都是用他包辦

在elcipse裡面,從專案總管裡面可以直接看到AndroidManifest.xml,點兩下就可以看到設定畫面
而如果要將專案打包成軟體就使用裡面的Export an unsigned APK and sign it manually即可

或是在專案上按右鍵 Android Tools裡面也可以發佈

有分為簽署跟未簽署兩種
未簽署可以直接發佈,簽署的話須要用JDK的keytool工具產生金鑰keystore,之後再用jarsigner簽章後才能發佈

keytool語法

keytool -genkey -v -keystore android.store -alias android.keystore -keyalg RSA -validity 20000

其中-genkey是產生key,-v顯示詳細資訊 -keystore android.store是名稱,-alias android.keystore是別名、-keyalg RSA 是加密方式為RSA、-validity 20000是有效期限,在Android Market上要求是至少50年(18250天)
之後系統會要求輸入一些有的沒的

之後可以在打指令的目錄下找到剛剛的金鑰檔

除了上述方法產生key之外,Android Tools也可以幫忙產生key,端看個人喜好

2009年5月24日 星期日

Android 1.5 tool的一些用法

這兩天研究了一下Android的指令跟eclipse上的一些用法
趁現在腦袋還清醒整理一下

在1.5版開啟模擬器必須先創造avd(Android virtual device)
創造法請參考上一篇使用eclipse開發Andrio 1.5
到android-sdk-windows-1.5_r1\tools目錄底下實施

android create avd -n avdname -t 2

接下來的指令假設都已經在android-sdk-windows-1.5_r1\tools底下了

如果要直接開啟模擬器的話一樣是用emulator.exe,但是跟1.1版不同的是
要加上參數avd
開啟模擬器語法

emulator.exe -avd test

test是我前面創造的avd

如果要指定skin的話,預設有四種skin可用

emulator.exe -avd test -skin SKINNAME

四種skin

  1. emulator.exe -avd test -skin HVGA-L:480x320水平顯示

  2. emulator.exe -avd test -skin HVGA-P:320x480垂直顯示(預設值)

  3. emulator.exe -avd test -skin QVGA-L:320x240水平顯示

  4. emulator.exe -avd test -skin QVGA-P:240x320垂直顯示



而如果想在模擬器使用sdcard的話,必須仰賴tool底下的mksdcard.exe指令
語法

mksdcard.exe sizeM Name.img

舉個例子,實施

mksdcard.exe 64M sd.img

就會在同個目錄下產生名為sd.img的映像檔


接下來我想掛載映像檔的話,就實施

emulator.exe -avd test -sdcard sd.img


而如果想要上傳檔案到sd.img裡面裡面,必須仰賴tool裡面的adb.exe指令
假設我想上傳一張圖片1.jpg,並且我把它放到跟adb.exe同個目錄下
註:可以上傳一整個目錄,不只檔案

實施命令

adb.exe push 1.jpg /sdcard

sdcard是映像檔裡面的目錄,如果嘗試push到其他目錄的話會出現錯誤訊息

adb.exe push 1.jpg /
...
failed to copy 1.jpg to /: Read-only file system


來看結果吧



adb還有其他指令

adb shell\\進入Linux模式,可用來檢視sdcard映像檔的檔案
adb pull /sdcard/XXX\\下載檔案
adb install xxx.apk\\ 安裝人家寫好的android應用程式
adb install -r xxx.apk\\ 重新安裝
adb uninstall package\\刪除應用程式 必須是packagename ex :com.me.mail
adb uninstall -k package\\刪除應用程式 但是保留資料


而tool裡面還有一個DDMS.exe工具,可以觀看模擬器的狀態,
輸入

ddms\\這個指令只有在模擬器開啟時有效



再來簡單介紹一下emulator.exe其他好用的參數

emulator.exe -avd AVDNAME -no-boot-anim\\省掉開機動話,寫程式時非常好用
emulator.exe -avd AVDNAME -dpi-device 200\\設定解析度 在此為200
emulator.exe -avd AVDNAME -wipe-data\\ 將模擬器回復到出廠時的設定


而android模擬器還有一個很大的優點,就是可以用telnet遠端登入模擬一些情況
登入語法

telnet localhost port

模擬器的port預設是5554,沒什麼必要的話也不用去改他
進去之後可以先用help指令去看有哪些東西可用
假設我想傳簡訊給模擬器我可以下達如下的指令

sms send 9527 'hello'

第三個參數是source號碼,而第四個參數是簡訊內容,這個非常重要,因為可以模擬很多實際情況,比方說來電
結果如下



----------------
最後來看一下eclipse的一些功能,eclipse也有創造avd跟關閉開機動畫的選項
一樣進入Run configuration,方法請看上一篇使用eclipse開發Andrio 1.5

進到Target後,下面有三個比較重要的功能

  1. AVD Manager:創造avd,而且可以同時指定sdcard
    可以取代指令

    android create avd -n avdname -t 2


  2. Wipe User Data:還原選項,可以取代指令

    emulator.exe -avd AVDNAME -wipe-data


  3. Disable Boot Animation:關掉開頭動畫,可以取代指令

    emulator.exe -avd AVDNAME -no-boot-anim




而這邊來看一下AVD Manager的選單,他會列出現在有的avd跟創造avd的功能

下面有個Create AVD的panel,他有四個選項可選

  1. Name:avd的名子在此用Test3

  2. Target:avd sdk的版本,在此選1.5

  3. skin:選擇顯示模式

  4. SDCard:avd要用的sdcard,在此用64M
    他可以有兩種輸入

    1. size:創造一個新的SDCard,這裡要注意一定要加上單位 M

    2. path:一個以存在的SDCard的映像檔所在路徑,譬如說我前面創造的
      E:\Android\android-sdk-windows-1.5_r1\tools\sd.img




之後按下Create AVD按鈕即可創造一個avd

這邊要注意一下,如果用size創造sdcard的話,他會存在avd所在位置之下
並且被命名為sdcard.img
avd所在位置預設是C:\Documents and Settings\你目前的使用者\.android\avd\
進去之後會看到剛剛創造的Test3.avd,點進去就可以看到sdcard

2009年5月21日 星期四

使用eclipse開發Andrio 1.5

最近想要研究一下Google放出來的Android平台,他是個開放源碼的作業系統
個人很看好這塊市場,趁現在還燙著趕緊來研究一下

-------------------------
這邊限定1.5版,因為方法與1.1版有異,以後搞不好會再更改

在開始之前先簡單透視一下Android SDK的架構


這張圖是『旗標:Android程式設計與應用』上的一章示意圖
Android是用Linux 2.6X版的kernel為基礎發展出來的平台,也有人說他就是Google版的Linux
但他用的是ASL(apache software license)授權方式而非Linux常用的GPL授權
往上在架構了lib層跟他自己的run-time環境,最後在搭上一套Framework就成了Android平台

而Android SDK開發要有幾個要件

  1. Android Linux系統核心

  2. Android模擬器

  3. Eclipse

  4. Dalvik虛擬機器

  5. Android除錯工具



Android目前總共有幾個系統上的SDK Windows,Mac OS,跟Linux

在此先介紹Windows版的安裝法

首先呢請先去他的網站下載SDK
http://developer.android.com/sdk/1.5_r1/index.html

請選擇相對應的版本把他下載下來

直接在I agree to the terms of the Android SDK License Agreement.上打勾
然後按Download就可以下載了
之後把他解壓到適當的目錄下,在此假設我解壓到E:\Android之下
隨後會產生android-sdk-windows-1.5_r1這個子目錄


再來我們需要Eclipse,但是我們還會需要Eclipse的JDT跟WTP兩個開發工具
不過沒關係我們去下載Eclipse的的Classic這個版本他就會替我們包好了
http://www.eclipse.org/downloads/

目前的版本為3.4.2 詳細版本號可能會變,請下載最新那版,然後同樣解壓到E:\Android那個目錄下


如果之前沒有安裝過JAVA JDK的話請先去下載安裝
http://java.sun.com/javase/downloads/index.jsp
版本挑1.5以上的

最後請安裝用Eclipse請安裝Android ADT開發工具
首先請先去剛才解壓的eclipse目錄下點選eclipse.exe開啟他
一開始會要我們選workspace,請照自己喜歡的創造
再來要安裝Android ADT了,先點選Help->Software Updates...

跳出視窗後先後點選Available Software->Add Site
之後輸入http://dl-ssl.google.com/android/eclipse/
按下OK(因為我之前已經輸入過了 所以圖片出現了重覆的訊息)



然後他會出現Android的選項,全部勾一勾按install

之後只要等待安裝完成

接下來呢必須設定SDK路徑
從Eclipse上方的選單中 Windows->Preferences會跳出一個視窗
點選Android,在SDK Location的地方設定剛剛Android SDK的位置
之後按下Apply在按下OK及設定完成


然後呢 要開始新增第一個Android的部分了
這邊開始就跟『旗標:Android程式設計與應用』有些不同了,因為這本書上提到的是1.1的安裝法
如果照書上直接去啟動Android的模擬器(在E:\Android\android-sdk-windows-1.5_r1\tool下的emulator.exe)會出現錯誤訊息

emulator: ERROR: You did not provide the name of an Android Virtual Device
with the -avd <name> option. Read -help-avd for more information.

If you *really* want to *NOT* run an AVD, consider using -data <file>
to specify a data partition image file (I hope you know what you're doing).


在這邊首先要用命令提示字元(開始->執行 輸入cmd)去創建一個avd
進入命令提示字元之後移動到SDK tool的目錄下(E:\Android\android-sdk-windows-1.5_r1\tool)輸入

android create avd -n test -t 2

他會出現訊息

Android 1.5 is a basic Android platform.
Do you wish to create a custom hardware profile [no]

直接用他默認的選項,按下Enter,就會出現創建成功的訊息

Created AVD test based on Android 1.5


在這邊因為我已經創建過了,所以我用test2取代

再來就可以創造一個Android Project了
選擇File->new->Project ,跳出一個視窗後選擇Android->Android Project



再來會跳出輸入相關參數的視窗,裡面有幾個一定要選的



  1. Project name:隨便打,主要是在eclipse裡面的專案名稱

  2. Build Target:SDK版號 選1.5版

  3. Application name:隨便打

  4. Package name:隨便打,但是要是網址的形式,主要是創建package用

  5. Create Activity:隨便打,創建出來class的名子


下面是創建出來後的對照

先用一段測試的程式碼

public class Test extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv=new TextView(this);
tv.setText("Hello World!");
setContentView(tv);
}
}


然後要執行的話,從上面的Bar裡面選擇Run->Run Configutrations或是點選start那個綠色按鈕旁邊的小三角形選Run Configutrations

跳出選單後先點選右上的小圖示(New Launch configuration)創建一個新的config


接著輸入Config的名子,按下Brower選取剛剛我們創建出來的專案按下Apply


之後點選Target,之後選擇剛剛創建出來的avd,按下Apply之後就可以按下Run來執行了


模擬器出來之後,一開始是Android的畫面,用滑鼠按一下

接下來會進入開機的畫面

之後要執行程式,先點選被框起來那個小圖

再來跳出的選單中可以看到已經安裝我們剛剛創造的Project,點下去就可以執行了



------------------
這裡打個小廣告,Eclipse真的是很好用的開發工具,從Java、C\C++甚至PHP等等都可以用他來開發,pulg-in,一直在增加,想要開發Web Service還是J2ME、UML架構都可以包辦
這次Google選擇他當主力開發環境實在令人高興,Eclipse可以幫程式設計師提升寫程式的速度