高焕堂-上課講義

上課講義之 1 高煥堂講解 Intent-based Programming

Android的4種嫡系組件(即Activity、Service、IntentReceiver和ContentProvider)之間如何互相溝通呢?這4種嫡系組件都是由Android啟動的,並不是組件之間透過直接呼叫而啟動的。就像我們打手機去車行叫計程車,而不是直接到街道上叫車。我們送給行一個簡訊一通電話,表明我們的「意圖」(Intent),當車行經理接到此意圖,就依據你的意圖的內含條件而去挑選最合適的計程車,然後派遣它去接你。

「意圖」(Intent)本身是定義為一個類別(Class),一個Intent物件表達一個目的(Goal)或期望(Expectation),敘述其所期望的服務或動作、與動作有關的資料等。Android則根據此Intent物件之敘述,負責配對,找出相配的組件,然後將 Intent物件傳遞給所找到的組件,Android的媒婆任務就完成了。

因此,Intent物件扮演著媒體仲介的角色,提供「Client組件 à Android à Server組件」之間互相溝通的相關資訊,實現了Client組件與Server組件之間『不知而亦能用』之效果,這又稱為疏結合(Loosely-coupled)效果。其創造了Server組件抽換的自由度,這又稱為PnP(Plug and Play)。

茲以下圖為例,Activity主要是提供UI畫面來與User進行互動,兩個Activity之間的直接互動較少。其它如ContentProvider則常是為Activity等提供服務的。所以Activity發出Intent物件委託Android挑選到適當的ContentProvider物件(並且將Intent物件傳遞給ContentProvider物件)之後,通常會透過ContentProvider介面而呼叫ContentProvider的各項服務或功能。

在此圖所示的範例裡,當我們在一個訂單列表畫面(如Activity-1),點選某個訂單之後,希望能夠呈現出此訂單的採購細項畫面(如Activity-2)。此時,Activity-1需要發出一個 Intent物件,這個Intent物件說明了意圖:包括“查找”(Get)動作、訂單ID等資料,然後呼叫Activity父類別的startActivity (Intent intent)函數,將此Intent物件傳送給Android。而Android會根據此Intent物件中的敘述,與AndroidManifest.xml所敘述的各嫡系類別之條件相比較,找出與此Intent敘述相配的組件(如Activity-2),然後Android將該Intent物件遞交給它,於是Activity-2會根據此Intent物件之敘述而執行相應的動作。

上課講義摘錄之2 高煥堂講解 ContentProvider & 範例

1. 何謂Android的嫡系組件

Android有4項一等公民(或稱為嫡系親屬),包括:Activity、ContentProvider、IntentReceiver與Service。它們都必須宣告於AndroidManifest.xml檔案裡,如下:

package="com.misoo.SQ03">

android:name="android.permission.INTERNET">

<provider android:name="DataProvider"

android:authorities="com.misoo.provider.SQ03">

provider>

<activity android:name=".ac01" android:label="@string/app_name">

activity>

<activity android:name=".DispActivity" android:label="DispActivity">

activity>

這讓Android知道我們城市裡定義了多少個嫡系組件類別;Android可以在啟動時就將它們執行起來,成為共享的(Shared)服務組件。這些嫡系服務組件間的溝通,通常是透過「意圖」(Intent)物件來請Android轉達給對方,Android則會依據意圖而找出最佳的配對。配對成功,就展開相互的溝通與服務了。

2. 什麼是ContentProvider嫡系組件

---- SQLite為例

在Android裡,SQLite資料庫是最典型的ContentProvider,負責儲存各式各樣的內容。除了資料庫之外,還有許多其他種類的ContentProvider。在這裡並不是要介紹這些ContentProvider,而是要透過SQLite認識ContentProvider介面,然後將舶來Linter組件,配上這種ContentProvider介面,讓它搖身一變成為Android的嫡系組件。

2.1 一般(即非嫡系)SQLite的範例

沒有透過ContentProvider介面來使用SQLite,就是對SQLite的「非嫡系」用法。此時,應用程式透過JDBC介面和SQL語句來與SQLite溝通,以存取資料庫裡的內容。先認識這種傳統用法。此範例將從SQLite讀取資料。首先建立一個程式專案,其含有兩個Java程式檔:ac01.java和DataProvider.java。其中,ac01.java 是典型的Activity類別,負責UI畫面的顯示工作,而DataProvider則負責與SQLite溝通。其詳細程式碼為:

/* ----- ac01.java 程式碼 ------*/

package com.misoo.pklx;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.Map;

import android.app.ListActivity;

import android.database.Cursor;

import android.os.Bundle;

import android.view.View;

import android.widget.ListView;

import android.widget.SimpleAdapter;

public class ac01 extends ListActivity {

private static final String[] PROJECTION = new String[] { "stud_no", "stud_name" };

@Override protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

DataProvider dp = new DataProvider(this);

Cursor cur = dp.query(PROJECTION, null, null, null);

ArrayList> coll

= new ArrayList>();

Map item;

cur.moveToFirst();

while(!cur.isAfterLast()) {

item = new HashMap();

item.put("c1", cur.getString(0) + ", " + cur.getString(1));

coll.add(item);

cur.moveToNext();

}

dp.close();

this.setListAdapter(new SimpleAdapter(this, coll,

android.R.layout.simple_list_item_1, new String[] { "c1" },

new int[] {android.R.id.text1}));

}

@Override

protected void onListItemClick(ListView l, View v, int position, long id) {

finish();

}}

指令:

DataProvider dp = new DataProvider(this);

這和一般類別之用法是一樣的。ac01物件指名要誕生一個DataProvider的物件。然後呼叫它,如下指令:

Cursor cur = dp.query(PROJECTION, null, null, null);

這要求SQLite從資料庫查詢出某些資料。詳細的DataProvider.java程式碼如下:

/* ----- DataProvider.java 程式碼 ------*/

package com.misoo.pklx;

import android.content.Context;

import android.database.Cursor;

import android.database.SQLException;

import android.database.sqlite.SQLiteDatabase;

import android.util.Log;

public class DataProvider {

private static final String DATABASE_NAME = "StudDB";

private static final String TABLE_NAME = "Student";

private final int DB_MODE = Context.MODE_PRIVATE;

private SQLiteDatabase db=null;

public DataProvider(Context ctx) {

try { db = ctx.openOrCreateDatabase(DATABASE_NAME, DB_MODE, null); }

catch (Exception e) { Log.e("ERROR", e.toString()); return; }

try { db.execSQL("drop table "+ TABLE_NAME); }

catch (Exception e) { Log.e("ERROR", e.toString()); }

db.execSQL("CREATE TABLE " + TABLE_NAME + " (" + "stud_no" + " TEXT,"

+ "stud_name" + " TEXT" + ");");

String sql_1 = "insert into "+ TABLE_NAME +

" (stud_no, stud_name) values('S101', 'Lily');";

String sql_2 = "insert into " + TABLE_NAME +

" (stud_no, stud_name) values('S102', 'Linda');";

String sql_3 = "insert into " + TABLE_NAME +

" (stud_no, stud_name) values('S103', 'Bruce');";

try { db.execSQL(sql_1); db.execSQL(sql_2); db.execSQL(sql_3); }

catch (SQLException e) { Log.e("ERROR", e.toString()); return; }

}

public Cursor query(String[] projection, String selection, String[] selectionArgs,

String sortOrder) {

Cursor cur = db.query(TABLE_NAME, projection, null, null, null, null, null);

return cur;

}

public void close(){ db.close(); }

}

這種用法屬於非嫡系的用法:在ac01.java程式碼裡,其指令:

DataProvider dp = new DataProvider(this);

明確指定由DataProvider物件來提供服務。反之,嫡系用法則是透過意圖(Intent)來請Android代為配對,進而找出適當的ContentProvider物件來為aco1物件提供服務。

2.2 嫡系SQLite的範例

剛才的範例裡,我們直接使用DataProvider類別的介面來與SQLite溝通。本節的範例,將替DataProvider配上ContentProvider介面,讓ac01物件能透過ContentProvider新介面來溝通。此範例也是從SQLite資料庫讀取3筆資料;請仔細看看其程式碼的微妙差異:

/* ----- ac01.java 程式碼 ------*/

package com.misoo.pkrr;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.Map;

import android.app.ListActivity;

import android.content.Intent;

import android.database.Cursor;

import android.net.Uri;

import android.os.Bundle;

import android.view.View;

import android.widget.ListView;

import android.widget.SimpleAdapter;

public class ac01 extends ListActivity {

public static int g_variable;

public static final String AUTHORITY = "com.misoo.provider.rx09-02";

public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY

+ "/Student");

private static final String[] PROJECTION

= new String[]{ "stud_no", "stud_name"};

@Override protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

Intent intent = getIntent();

if (intent.getData() == null) intent.setData(CONTENT_URI);

Cursor cur = getContentResolver().query(getIntent().getData(),

PROJECTION, null, null, null);

ArrayList> coll = new ArrayList>();

Map item;

cur.moveToFirst();

while (!cur.isAfterLast()) {

item = new HashMap();

item.put("c1", cur.getString(0) + ", " + cur.getString(1));

coll.add(item);

cur.moveToNext();

}

this.setListAdapter(new SimpleAdapter(this, coll,

android.R.layout.simple_list_item_1, new String[] { "c1" },

new int[] { android.R.id.text1 }));

}

@Override

protected void onListItemClick(ListView l, View v, int position, long id) { finish();}

}

指令:

Cursor cur = getContentResolver().query(getIntent().getData(),

PROJECTION, null, null, null);

要求Android代為尋找適合的ContentProvider來提供服務,並不刻意指定由DataProvider物件來擔任。只要合乎ConentProvider介面,且符合意圖條件的物件皆可以來為ac01物件提供服務。於是,ac01程式碼就不再直接呼叫DataProvider類別的函數了,而是呼叫ContentProvider介面所提供的函數。再來仔細看看DataProvider類別與ContentProvider介面的搭配情形:

/* ----- DataProvider.java 程式碼 ------*/

package com.misoo.pkrr;

import android.content.ContentProvider;

import android.content.ContentValues;

import android.content.Context;

import android.database.Cursor;

import android.database.SQLException;

import android.database.sqlite.SQLiteDatabase;

import android.database.sqlite.SQLiteOpenHelper;

import android.net.Uri;

import android.util.Log;

public class DataProvider extends ContentProvider {

private static final String DATABASE_NAME = "StudNewDB";

private static final int DATABASE_VERSION = 2;

private static final String TABLE_NAME = "StudTable";

private static class DatabaseHelper extends SQLiteOpenHelper {

DatabaseHelper(Context context) {

super(context, DATABASE_NAME, null, DATABASE_VERSION); }

@Override public void onCreate(SQLiteDatabase db) {

db.execSQL("CREATE TABLE " + TABLE_NAME + " (" + "stud_no"

+ " TEXT," + "stud_name" + " TEXT" + ");");

String sql_1 = "insert into " + TABLE_NAME

+ " (stud_no, stud_name) values('S1001', 'Pam');";

String sql_2 = "insert into " + TABLE_NAME

+ " (stud_no, stud_name) values('S1002', 'Steve');";

String sql_3 = "insert into " + TABLE_NAME

+ " (stud_no, stud_name) values('S1003', 'John');";

try { db.execSQL(sql_1); db.execSQL(sql_2); db.execSQL(sql_3); }

catch (SQLException e) { Log.e("ERROR", e.toString()); }

}

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}

}

// ---------------------------------------------------------------------------------

private DatabaseHelper mOpenHelper;

@Override public boolean onCreate() {

mOpenHelper = new DatabaseHelper(getContext()); return true; }

@Override public Cursor query(Uri uri, String[] projection, String selection,

String[] selectionArgs, String sortOrder) {

SQLiteDatabase db = mOpenHelper.getReadableDatabase();

Cursor c = db.query(TABLE_NAME, projection, null, null, null, null, null);

return c;

}

@Override public String getType(Uri uri) { return null; }

@Override public Uri insert(Uri uri, ContentValues initialValues) { return uri; }

@Override public int delete(Uri uri, String where, String[] whereArgs) { return 0; }

@Override public int update(Uri uri, ContentValues values, String where,

String[] whereArgs)

{ return 0; }

}

類別定義:

public class DataProvider extends ContentProvider {

// …..…..

}

DataProvider類別繼承ContentProvider父類別,也繼承了它的介面定義。ContentProvider介面定義了多個函數,主要包括:

l query()函數---- 它查詢出合乎某條件的資料。

l insert()函數---- 它將存入一筆新資料。

l delete()函數---- 它刪除合乎某條件的資料。

l update()函數---- 更新某些筆資料的內容。

在這個DataProvider類別裡,撰寫了query()函數內的指令,來實現query()介面,這個query()函數實際呼叫SQLite資料庫的功能。也就是說,ac01等應用程式透過ContentProvider介面間接呼叫到DataProvider的query()函數,然後此query()函數才使用SQLite的服務。

由於此範例的DataProvider已經是ContentProvider嫡系身份了,必須由Android來啟動它,而不是有ac01等應用程式來直接啟動它,所以必須在AndroidManifest.xml文檔裡給Android一些指示,如下:

/* ----- AndroidManifest.xml 文檔 ------*/

package="com.misoo.pkrr"

android:versionCode="1"

android:versionName="1.0.0">

android:label="@string/app_name">

 

android:authorities="com.misoo.provider.rx09-02">

這特別說明DataProvider是一個ContentProvider,於是Android就會來啟動它。

上課講義摘錄之3 實際演練Android模擬器之操作

實際演練Android模擬器之操作

l Android的嫡系組件(first-class citizen)

Activity:敘述User使用此AP時會進行的一連串活動。

Intent Receiver:用以接收外來的事件通知(Notification)。

Service:非UI的幕後服務程式。

Content Provider:將資料儲存於檔案系統或資料庫(如SQLite或 Linter)裡。

l Android的角色

Android是在Windows或Linux上執行一個ARM-CPU模擬器,並在此模擬器上執行

Linux2.6.23. Android是一個應用框架(Application Framework),執行於上述的模擬

環境裡。

l Windows XP環境進入Android裡的Linux環境

使用XP環境的命令列模式,進入:/android-sdk-windows-1.0_r1/tools/打入命令:

adb shell 就會出現#號,就進入Linux地盤了。

l adb是什麼

adb是Android裡的一個管理程式,稱為Android Debug Bridge。儲存於

c:/android-sdk-windows-1.0_r1/tools/裡的一個.exe程式。必需在命令列模式

裡執行。它能安裝.apk檔案、將檔案拷貝到模擬器裡等等。

l 如何載入Android *.apk?

Step-1: 啟動Android的模擬器(以mouse點選c:/android-sdk-windows-1.0_r1/tools/ 裡

的android圖像)。

Step-2: 拷貝*.apk檔案到c:/android-sdk-windows-1.0_r1/tools/裡。

Step-3: 使用命令列模式,進入/tools/,然後執行 adb install *.apk。

此.apk就被存入Linux的/data/app/裡,並出現於模擬器畫面的.apk裡了。

(PS. Andorid應用程式編譯之後會產出一個.apk檔案,它是一個壓縮檔。)

l 如何移除*.apk?

使用命令列模式,進入c:/android-sdk-windows-1.0_r1/tools/,然後,執行

adb shell rm *.apk。或者,執行adb shell打開一個Linux shell,再進入/data/app/,

執行#rm *.apk。

l 清除模擬器裡的資料(Wipe your emulator data)

隨著程式的執行,常常會留下一些資料在模擬器裡,如果你想清除掉它們,

可進入c:/android-sdk-windows-1.0_r1/tools/裡,打入命令:emulator -wipe-data

來啟動模擬器。

l Kill-Server

如果發現 Eclipse與模擬器溝通不良(例如出現有* daemon not running. starting it

           now  * 的訊息時),可以關掉Eclipse,進入c:/android-sdk-windows-1.0_r1/tools/裡,

打入命令:adb kill-server,再啟動Eclipse。

l adb功能

adb(Android Debug Bridge)是Android提供的的Debug工具,它可以管理設備或手機

模擬器的狀態、更新模擬器中的應用程式碼、執行設備shell命令等。例如:adb

          install 、adb shell、#cd /data/app、#rm app.apk等。

---- 進入設備或模擬器的shell:adb shell就可以進入模擬器的shell環境中,這是

                Linux  Shell,可以執行各種Linux的命令,格式為:adb shell [command]

例如:

               adb shell  dmesg會列印出Linux的debug訊息。

---- 複製一個檔或目錄到模擬器上:adb push

---- 從模擬器上複製一個檔或目錄:adb pull 例如:adb pull /data/data/kk.xml

上課講義摘錄之4AndroidCross Compiler之關係

高煥堂談:AndroidCross Compiler之關係

---- Ubuntu/Linux/X86 環境裡使用2007q3-51交叉編譯C程式,然後放入Android裡執行。

l 何謂Cross compiler(交叉編譯器)?

Cross Compiler主要在資源較豐富的電腦上執行,而編譯出能在別的電腦上執行的目的碼(Object Code)。例如,當我們想寫個C程式,讓它能在Android手機裡跑。Android手機的ARM-CPU及記憶體容量都很小,我們無法在資源有限的Android/ARM裡進行編輯及編譯C程式。可行的方法是:在X86 PC環境裏編輯C程式,然後使用Cross Compiler去編譯出適合ARM-CPU裡執行的目的碼。這稱為Cross Compiler。

l 安裝ARM GNU/Linux 交叉編譯器

在Ubuntu裡安裝交叉編譯器的步驟是:

Step-1. 在Ubuntu畫面上,直接上網:

Step-2. 選取2007q3-51版,並下載:

Step-3. 這會自動安裝於 /home/tom/arm-2007q3/裡。

Step-4. 這樣,交叉編譯器就安裝完成了。

l 使用Cross Compiler編譯C函數,放入Android裡執行。

可先將.h和.c程式碼存於自訂的Proj_01檔案夾裡,如下:

l 開始進行交叉編譯C程式碼

接下來,對HalfAdder.c和 com_misoo_gx05_NativeJniAdder.c兩個程式檔,進行編譯,

將 產生.o的目的程式(Object Code)檔。

*** 編譯HalfAdder.c程式 ***

*** 編譯com_misoo_gx05_NativeJniAdder.c程式 ***

從畫面可看到他已經產出了兩個ARM-based的 .o 目的程式檔了。

l 連結出可在ARM上執行的 .so程式檔

對HalfAdder.o和 com_misoo_gx05_NativeJniAdder.o兩個目的程式檔,

進行連結而產生.so的共享程式檔案,使用下述命令:

l 將libNativeJniAdder.so共享程式檔拷貝並放置到Android模擬器裡

例如,在Windows環境。

Step-1. 先將.so檔案拷貝到c:/android-sdk-windows-1.0_r1/tools/裡。

Step-2. 啟動模擬器。

Step-3. 進入c:/android-sdk-windows-1.0_r1/tools/,並使用adb push命令

將.so檔案,存入模擬器的/system/lib/裡。

l 撰寫主程式去呼叫這libNativeJniAdder.so共享程式

在Android的Java程式可輕鬆地透過JNI去呼叫此.so程式庫。

也可以再利用Cross Compiler編譯一個C主函數(main())去呼叫它。

~ END ~

上課講義摘錄之5: 認識Android Application

認識Android 應用程式(Application)

---------------------------------------------------------------------------------------------------------------

PS.  別忘了....

       1.   高煥堂 11月台北 Android 教育訓練課程

                        12~1月上海 Android 教育訓練課程

       2.   下載 高煥堂 第1本Android書籍的免費e-book

-----------------------------------------------------------------------------------------

整個應用程式都定義於AndroidManifest.xml裡,其宣告了其進入點(Entry Point)、通訊層級(Communication Layer)、授權(Permission),以及各個Activity和意圖(Intent)等。其中,有4種基礎組件,我們稱之為Android的嫡系組件。

l Activity: Android 應用程式的UI(User Interface)基本組件。

l Intent receiver: 可隨時被啟動來處理Intent,並執行其任務。

l Service: 非UI功能的幕後處理組件。

l Content provider: 跨程式的共享資料之儲存者。

如何添增圖片(Image)資源

圖片資源就直接將圖片檔(例如ok.jpg)拷貝到/res/drawable檔案夾裡。此時,Eclipse的Android插件(Android Plug-In)會自動將一個新的ID值添加到R.java裡。所以R.java檔案裡會多加了一行指令如下:

在應用程式碼將就由此ID值來取得這個圖片檔,並顯示或處理它。

如何定義XML畫面佈局(Layout)

剛才已經新增了一個圖片資源檔。此時,在定義畫面佈局的XML檔案裡,就可以引用它了。畫面佈局的XML檔都擺在/res/layout檔案夾裡,其中Eclipse的Android插件已經誕生一個main.xml在那裡了。現在,你可利用Eclipse的File>New>File菜單選項來誕生新的畫面佈局XML檔案,例如:button_layout.xml。然後,以main.xml內容為底稿,將之拷貝到新的button_layout.xml裡。

將部分更改為如下:

在畫面佈局XML檔案裡,使用@drawable/就能輕鬆地引用/res檔案夾裡的資源了,例如上圖的android:src="@drawable/ok"。此外,layout_width和layout_height 則說明這個ImageButton顯示出來的大小(Size)。button_layout.xml也成為一項新的資源。所以在R.java裡也會自動產生新的一行,如下:

同樣地,在應用程式碼裡也能隨時引用這個資源了,例如將ac01.java裡的R.layout.main更改為R.layout.button_layout,如下:

此應用程式執行時,就引用到button_layout.xml資源而顯示於畫面上,如下:

~~ END ~~

上課講義摘錄之6:注意Android是應用框架,不是一般OS平台

在上一節課程裡,特別強調應用框架的神祕力量,必須由Android的設計人角度去看它。不一定要成為框架設計人,但從他的角度和觀點能深刻觀察框架的魅力的源頭。在本課程的第一本教科書<>裡(第1.5節),就提到常見的迷思:

1.5 框架與OS之關係:常見的迷思

1.5.1 迷思

許多人從空間角度去想像OS與應用框架之間的關係。的確,OS(如Linux或Windows)像木板床,應用框架像彈簧床墊,其擺在木版床上。而應用程式則像睡在床墊上的人。這個觀點是對的(如圖1-4所示)。 然而,許多人順勢推論他們之間的互動關係如下圖:

圖1-5 常見的迷思

乍看之下,似乎蠻合理的,其實是個迷思。請你換個角度,採取另一個觀點,如下圖,更容易體會框架的角色和涵意,此新觀點如下圖1-6所示。

回想一下,您寫傳統程式時,主控權掌握在程式手中,其決定如何呼叫庫存函數﹔就像棒球比賽的「投手」一樣。反之,使用框架時,您的程式則擔任「捕手」之角色。盼您在使用框架時,能有這種心理準備(Mindset) 。

圖1-6 較合理的觀點

上課講義摘錄之7: Android裡的類別繼承及物件組合

在Android裡定義了如下的類別繼承(Class Inheritance)體系:

還有如下的物件組合(Object Composition)關係:

ViewGroup的子孫類別(如下圖的LinearLayout),也自然繼承了上圖的組合關係:

同樣地,View的子孫類別也具有同樣的繼承,可推導出如下之組合關係:

這些是Android已經提供的基類(Base Class)。

在這裡,話插一下,我在北京程序員雜誌上寫的<<基類與愚公移山>>一文裡,我稱之為『畚箕』。Android應用程式的開發者就如同挑畚箕的人,在中華歷史上,有個家喻戶曉的偉大人物就是『愚公』,它是挑畚箕的人,想把泰山的土一擔一擔挑去填北海。現在,我就來扮演愚公的角色,挑一擔(寫個Android應用程式)給你看看,但是請你不要叫我愚公就是。此外,我這個超級愚公還可以一根扁擔挑3個畚箕呢!!

首先建立一個Android Project:

我這個愚公希望手機畫面出現如下:

在畫面上輸入一個字串,並按下時,就在畫面title區輸出了該字串:

現在開始寫程式了,拿著一根扁擔(Layout)和兩三個畚箕(一個EditText、和兩個Button)。

程式碼如下:

package com.misoo.pkaz;

import android.app.Activity;

import android.graphics.Color;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.EditText;

import android.widget.LinearLayout;

public class ac01 extends Activity implements OnClickListener {

private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;

private final int FP = LinearLayout.LayoutParams.FILL_PARENT;

private Button btn, btn2;

private EditText et;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

this.show_layout();

}

public void show_layout(){

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

et = new EditText(this);

LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(FP, WC);

layout.addView(et, param);

btn = new Button(this);

LinearLayout.LayoutParams param2 =

new LinearLayout.LayoutParams(WC, WC);

param2.topMargin = 5;

btn.setText("OK");

btn.setBackgroundResource(R.drawable.x_blue3);

btn.setOnClickListener(this);

layout.addView(btn, param2);

btn2 = new Button(this);

btn2.setText("Exit");

btn2.setTextColor(Color.RED);

btn2.setBackgroundResource(R.drawable.x_gray3);

btn2.setOnClickListener(this);

layout.addView(btn2, param2);

setContentView(layout);

}

public void onClick(View v) {

if(v == btn)

setTitle(et.getText());

else if(v == btn2)

finish();

}

}

透過layout扁擔的addView()函數就將畚箕一個一個挑起來了。

by Tom

講義摘錄之8:如何從DDMS發出簡()訊給應用程式?

1. 如何切換到DDMS?

途徑-1:

從Eclipse/Android應用程式編輯畫面:

按下右上角的:

就出現:

再選取就開啟了。

途徑-2:

按下 組合鍵,就開啟了。

DDMS開啟的畫面:

2. 如何返回Eclipse/Android程式編輯畫面:

途徑-1:

再按下剛才的小窗戶:

再選取Java就行了。

途徑-2:

按下 組合鍵,就返了。

3. 現在請回到Eclipse/Android程式編輯畫面,編輯一個應用程式:

編寫 ac01.java程式碼:

package com.misoo.pksms;

import android.app.Activity;

import android.os.Bundle;

public class ac01 extends Activity {

private static ac01 appRef = null;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

appRef = this;

setContentView(R.layout.main);

}

public static ac01 getApp(){

return appRef;

}

public void call_back(String str){

setTitle(str);

}

}

編寫 mySMSReceiver.java程式碼:

package com.misoo.pkzz;

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

public class mySMSReceiver extends BroadcastReceiver {

@Override

public void onReceive(Context arg0, Intent arg1) {

// TODO Auto-generated method stub

ac01 app = ac01.getApp();

app.call_back("hi, how are you?");

}

}

4.   現在執行這個程式,出現你所熟悉的畫面:

5.   切換到DDMS。

6.   選取com.misoo.pksms,看到綠色小蟲亮了,如下:

7.   選擇,輸入任意電話號碼,也輸入短訊內容。

8.   按下,就送出簡訊給com.misoo.pksms程式了,於是此程式畫面出現簡訊了:

返回Eclipse/Android程式編輯畫面。

~~~~ by top articles ~~~

講義摘錄之10:佈局(Layout)Android軟體開發上的重要角色

如果以SaaS(Software as a Service)的觀念來看Layout,會更清楚它的角色。如果以舞台劇的一『幕』來比喻Layout,會更傳神。一幕就是一個劇情的片段,此片段有其獨特的演出意涵,也是有頭有尾的完整片段。從SaaS的觀念來說,一個Layout是一個有其獨特目標的服務(Service)片段,或稱為小服務。這些小服務可以組合成為更完整更大的服務(或劇本)。

依據SaaS的概念,Service是有幕後的軟體類別或模組所提供、支撐或實現的。那麼,Layout背後的軟體模組是甚麼呢?答案是:Activity類別。有關於這些Layout的事件的處理程序都寫在Activity類別裡。不同的Activity類別可由不同的開發者負責開發。所以Activity可視為開發的分工單位。不同的開發者發揮其獨特的專業能力而開發出獨特的Activity類別,提供一些獨特的服務(即Layout)。

Activity類別像樹枝,Layout像樹葉,而Android應用程式的用戶就像在樹葉上跑來跑去的金龜蟲。那麼,Android應用程式的角色就是將一系列來自不同Activity的Layout串聯起來。也就是那隻金龜蟲的行動軌跡了。

那麼,Android應用程式又如何串聯這些Layout呢? 在Android平台裡,有個Intent類別。Android應用程式藉由Intent物件來與Android應用框架(Application Framework)核心進行溝通,請求Android框架核心來物色合適的Activity來提供其Layout,來進行服務。至於一個Activity到底提供甚麼目的的服務呢? 則寫在AndroidManifest.xml的Activity裡的Filter裡,基於Filter的條件而找到適當的服務,及其背後的Activity類別。該Activity再透過Layout而展現其服務。

一般而言,一個應用程式常包含多個獨立的服務流程,通稱為Use Case。而每一個Use Case都是由許多Layout提供的服務所組成。而Layout又是由Activity類別的多個函數來聯合服務。因此,傳統的UML和OOAD裡大家所熟悉的用例(Use Case)圖和類別(Class)圖,只要加上一個Layout的角色就能全部派上用場了。

當我們將Layout視為服務時,SOA(Service-Oriented Architecture)和SaaS(Software as a Service)的兩項新的軟體開發技術,也能派上用場了。因之,Layout扮演一個極為重要的角色,連結了最新的軟體開發技術與Android最新的軟體開發平台,將為Android軟體開發團隊帶來可靠的開發技術和工具,也將為Android應用軟體帶來極高的品質和可靠度。

講義摘錄之11Android的類別繼承與委託之範例

1.1 類別繼承的副作用

繼承和委託皆能達到物件再用之目的,各有所長各有所短,相輔相成才是完美的。如下述類別:

在New Collegiate字典上對「正方形」的定義是:

「正方形是一種4邊等長的長方形」

所以使用繼承如下:

然而,上述的實作繼承卻有些缺點,宜避免之。例如,Square從Rectangle繼承了setLength()和setWidth()函數,但這兩個函數對於Square而言是無意義且有害的。因之,上述的繼承關係是不良的。至於如何改善上述的繼承關係呢?可改用委託:

當Square之物件接到外界傳來的area()訊息時,就委託Rectangle之物件代為處理。雖然委託會令程式複雜些,但勉強使用繼承,後遺症將更大。因為副作用可能會延續到Rectangle的各子孫類別!

1.2 Android的繼承與委託之例

茲以Android裡的MediaPlayer類別為例。

1.2.1 操作情境:

1. 此程式開始執行後,出現畫面如下:

2. 按下,就開始播放MP3音樂。

3. 若按下,就結束播放音樂。

4. 若按下,程式就結束了。

1.2.2 範例程式(1):採單純繼承方法

1.2.2.1 撰寫步驟:

Step-1: 建立Android專案:Px01。

Step-2: 撰寫Activity的子類別:ac01,其程式碼如下:

/* ac01.java */

package com.misoo.pkzz;

import android.app.Activity;

import android.graphics.Color;

import android.media.MediaPlayer;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.LinearLayout;

import android.widget.TextView;

public class ac01 extends Activity implements OnClickListener {

private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;

private final int FP = LinearLayout.LayoutParams.FILL_PARENT;

private MediaPlayer mPlayer;

private myButton btn, btn2, btn3;

public TextView tv;

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

btn = new myButton(this);

btn.setId(101);

btn.setText("play");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(btn.get_width(),

btn.get_height());

param.topMargin = 10;

layout.addView(btn, param);

btn2 = new myButton(this);

btn2.setId(102);

btn2.setText("stop");

btn2.setOnClickListener(this);

layout.addView(btn2, param);

btn3 = new myButton(this);

btn3.setId(103);

btn3.setText("exit");

btn3.setOnClickListener(this);

layout.addView(btn3, param);

tv = new TextView(this);

tv.setTextColor(Color.WHITE);

tv.setText("Ready");

LinearLayout.LayoutParams param2 =

new LinearLayout.LayoutParams(FP, WC);

param2.topMargin = 10;

layout.addView(tv, param2);

setContentView(layout);

//---------------------------------------

myMediaPlayer my_player = new myMediaPlayer();

mPlayer = my_player.create(this, R.raw.test_cbr);

}

public void onClick(View v) {

switch(v.getId()){

case 101:

mPlayer.start();

break;

case 102:

mPlayer.stop();

break;

case 103:

finish();

break;

}

}

}

Step-3: 撰寫myMediaPlayer類別,其程式碼如下:

/* myMediaPlayer.java */

package com.misoo.pkzz;

import android.content.Context;

import android.media.MediaPlayer;

public class myMediaPlayer extends MediaPlayer {

public static MediaPlayer create(Context context){

return MediaPlayer.create(context, R.raw.test_cbr);

}

}

Step-4: 撰寫Button的子類別:myButton,其程式碼如下:

/* myButton.java */

package com.misoo.pkcc;

import android.content.Context;

import android.widget.Button;

public class myButton extends Button {

public myButton(Context ctx){

super(ctx);

super.setBackgroundResource(R.drawable.heart);

}

public int get_width(){

return 80;

}

public int get_height(){

return 50;

}

}

Step-5: 執行之。

1.2.2.2 說明:

在ac01類別的指令:

myMediaPlayer my_player = new myMediaPlayer();

mPlayer = my_player.create(this, R.raw.test_cbr);

所傳回來的是MediaPlayer類別之物件參考(即mPlayer的值),而不是myMediaPlayer類別的物件參考。既然是MediaPlayer類別之物件參考,則外界的類別或函數(如ac01類別)就能透過mPlayer參考(即使用MediaPlayer介面)來使用此新誕生的MediaPlayer類別之物件了,如下圖:

雖然在my_player物件裡也有一個MediaPlayer的小物件,但是mPlayer並不是參考到它,而是參考到由create()函數所誕生的心物件。

1.2.3 範例程式(2):採繼承與委託混合型

1.2.3.1 撰寫步驟:

Step-1: 建立Android專案:Px02。

Step-2: 撰寫Activity的子類別:ac01,其程式碼如下:

/* ac01.java */

package com.misoo.pkzz;

import android.app.Activity;

import android.graphics.Color;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.LinearLayout;

import android.widget.TextView;

public class ac01 extends Activity implements OnClickListener {

private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;

private final int FP = LinearLayout.LayoutParams.FILL_PARENT;

private myMediaPlayer my_player;

private myButton btn, btn2, btn3;

public TextView tv;

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

btn = new myButton(this);

btn.setId(101);

btn.setText("play");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(btn.get_width(),

btn.get_height());

param.topMargin = 10;

layout.addView(btn, param);

btn2 = new myButton(this);

btn2.setId(102);

btn2.setText("stop");

btn2.setOnClickListener(this);

layout.addView(btn2, param);

btn3 = new myButton(this);

btn3.setId(103);

btn3.setText("exit");

btn3.setOnClickListener(this);

layout.addView(btn3, param);

tv = new TextView(this);

tv.setTextColor(Color.WHITE);

tv.setText("Ready");

LinearLayout.LayoutParams param2 =

new LinearLayout.LayoutParams(FP, WC);

param2.topMargin = 10;

layout.addView(tv, param2);

setContentView(layout);

//---------------------------------------

my_player = myMediaPlayer.create(this);

}

public void onClick(View v) {

switch(v.getId()){

case 101:

my_player.start();

break;

case 102:

my_player.stop();

break;

case 103:

finish();

break;

}

}

}

Step-3: 撰寫myMediaPlayer類別,其程式碼如下:

/* myMediaPlayer.java */

package com.misoo.pkzz;

import android.app.Activity;

import android.content.Context;

import android.media.MediaPlayer;

import android.util.Log;

public class myMediaPlayer extends MediaPlayer {

private static MediaPlayer mSuper;

private static Context ctx;

public static myMediaPlayer create(Context context){

ctx = context;

mSuper = MediaPlayer.create(context, R.raw.test_cbr);

return new myMediaPlayer();

}

public void start(){

try{

mSuper.start();

((ac01)ctx).tv.setText("Playing audio...");

((Activity)ctx).setTitle("MP3 Music");

} catch (Exception e) {

Log.e("StartPlay", "error: " + e.getMessage(), e);

}

}

public void stop(){

if (mSuper != null) {

((ac01)ctx).tv.setText("Stop");

mSuper.stop();

mSuper.release();

mSuper = null;

}

}

}

Step-4: 撰寫Button的子類別:myButton,其程式碼如下:

/* myButton.java */

package com.misoo.pkcc;

import android.content.Context;

import android.widget.Button;

public class myButton extends Button {

public myButton(Context ctx){

super(ctx);

super.setBackgroundResource(R.drawable.heart);

}

public int get_width(){

return 80;

}

public int get_height(){

return 50;

}

}

Step-5: 執行之。

1.2.3.2 說明:

在ac01物件裡的my_player是參考到myMediaPlayer的物件,此物件裡mSuper再參考到MediaPlayer的物件,如下圖:

在ac01類別裡的指令:

my_player.start();

是呼叫到myMediaPlayer子類別的start()函數,然後才委託呼叫MediaPlayer的start()函數。此時,可看到「繼承的副作用」了,就是:myMediaPlayer繼承了眾多函數,並無法透過my_palyer去呼叫之,甚至可能是有害的。因此宜改用下一小節的單純委託方法。

1.2.4 範例程式(3):採單純委託方法

1.2.4.1 撰寫步驟:

Step-1: 建立Android專案:Px03。

Step-2: 撰寫Activity的子類別:ac01,其程式碼如下:

/* ac01.java */

package com.misoo.pkcc;

import android.app.Activity;

import android.graphics.Color;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.LinearLayout;

import android.widget.TextView;

public class ac01 extends Activity implements OnClickListener {

private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;

private final int FP = LinearLayout.LayoutParams.FILL_PARENT;

private mp3Player mp3_player;

private myButton btn, btn2, btn3;

public TextView tv;

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

mp3_player = new mp3Player(this);

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

btn = new myButton(this);

btn.setId(101);

btn.setText("play");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(btn.get_width(),

btn.get_height());

param.topMargin = 10;

layout.addView(btn, param);

btn2 = new myButton(this);

btn2.setId(102);

btn2.setText("stop");

btn2.setOnClickListener(this);

layout.addView(btn2, param);

btn3 = new myButton(this);

btn3.setId(103);

btn3.setText("exit");

btn3.setOnClickListener(this);

layout.addView(btn3, param);

tv = new TextView(this);

tv.setTextColor(Color.WHITE);

tv.setText("Ready");

LinearLayout.LayoutParams param2 =

new LinearLayout.LayoutParams(FP, WC);

param2.topMargin = 10;

layout.addView(tv, param2);

setContentView(layout);

}

public void onClick(View v) {

switch(v.getId()){

case 101: mp3_player.start(); break;

case 102: mp3_player.stop(); break;

case 103: finish(); break;

}

}

}

Step-3: 撰寫mp3Player類別,其程式碼如下:

/* mp3Player.java */

package com.misoo.pkcc;

import android.app.Activity;

import android.content.Context;

import android.media.MediaPlayer;

import android.util.Log;

public class mp3Player {

private MediaPlayer mPlayer;

private Context ctx;

public mp3Player(Context context){

ctx = context;

mPlayer = MediaPlayer.create(ctx, R.raw.test_cbr);

}

public void start(){

try{

mPlayer.start();

((ac01)ctx).tv.setText("Playing audio...");

((Activity)ctx).setTitle("MP3 Music");

} catch (Exception e) {

Log.e("StartPlay", "error: " + e.getMessage(), e);

}

}

public void stop(){

if (mPlayer != null) {

((ac01)ctx).tv.setText("Stop");

mPlayer.stop();

mPlayer.release();

mPlayer = null;

}

}

}

Step-4: 撰寫Button的子類別:myButton,其程式碼如下:

/* myButton.java */

package com.misoo.pkcc;

import android.content.Context;

import android.widget.Button;

public class myButton extends Button {

public myButton(Context ctx){

super(ctx);

super.setBackgroundResource(R.drawable.heart);

}

public int get_width(){

return 80;

}

public int get_height(){

return 50;

}

}

Step-5: 執行之。

1.2.4.2 說明:

1. 由於mp3Player類別並沒有繼承MediaPlayer類別,清晰地告訴外界的類別或函數(如ac01類別),它們只能透過mp3Player類別定義的兩個函數(即start()和stop()函數)來播放或停止音樂。

2. mp3Player類別之物件顯然單純喜多,如下圖:

3.  其實,這種委託機制,就是本書前面各章介紹過的物件組合方法。只是在這裡拿來與繼承機制比較一番,讓大家能兼具這兩把刷子,並且能交換使用,截長補短、相輔相成,達到完美之境

講義摘錄之12:認識AndroidUID(Unix user ID)與權限

傳統電腦上,UID是跟隨著「人」(例如電腦的使用者或用戶)。Android裡的UID是跟隨著「軟體」(例如Android應用程式)。

由於每個應用程式都有個UID,只有帶著此UID,才能存取該UID所涵蓋的有關資料。所以如果AP-1與AP-2的UID不同,則在預設(Default)情況下,雙方都無法讀取對方的資料。這種分而治之的方式,可以減輕駭客軟體的惡意傷害資料,提升手機的安全性。

當手機使用者(即User)下載你(即開發者)的應用程式,在安裝(Install)時,Android就會給予一個UID。這個UID可連結到該應用程式的 AndroidManifest.xml檔案的內容。所以User在安裝你的應用程式時,在螢幕上的視窗裡可以檢視這個AndroidManifest.xml檔案的內容。在檢視時,用戶會看到你對應用程式的目的、權限等說明。當你接受這支程式的意圖、權限說明之後,Android就安裝它,並給它一個UID。萬一在你的應用程式執行期間有越軌(企圖做出非權限範圍)的行為時,用戶將會得到Android的警告訊息。

有一些特殊情形下,兩支應用程式是可以持有一樣的UID。例如,同一位開發者撰寫的應用程式,常常需要推出新版本,這兩種版本的程式可以持有一樣的UID, 才有權限去將舊版程式所產生的資料拷貝轉移到新版軟體裡。

一個用戶在其使用手機的用例(Use Case)裡,通常由一個Activity啟動開始,透過Intent物件去啟動其他的Activity、Service、ContentProvider等具有不同UID的一連串應用程式,才能滿足一個用例的目標。在傳統電腦上,UID是跟隨著「人」,一個用戶持著一個UID就能執行一連串的應用程式,而且在預設條件下是可以互相共享資料。但是在Android裡,這一連串的應用程式是各有自己的UID,而且可能都持著不同的UID,原則上是不能任意存取對方的資料。

UID與AndroidManifest.xml檔案的連結除了可以保護各程式的私有資料之外,還可以禁止程式越權使用手機的各項資源。例如,開發者在AndroidManifest.xml檔案裡表明此程式有READ_CONTACTS的授權。用戶在安裝此應用程式時,檢視過AndroidManifest.xml的授權內涵,而且接受之。此應用程式就能合法地存取手機裡的通訊錄資料,甚至授權撥打電話、發簡訊等等。

剛才提到過,一個用戶的用例(Use Case)裡,通常由多個不同UID的Activity、Service、ContentProvider等一連串的應用程式所串接,才能滿足用例的目標。此時,如果它們都必須存取通訊錄的話,必須都持有相同的READ_CONTACTS授權才行。

以上就應用程式的開發者、使用者的角度來說明Android的UID之意義和用法。至於Android如何運用Linux的進程(Process)機制及Android的特殊管理政策,在此講義的節錄版裡,就省略之。

By 高煥堂

上課講義摘錄之13AndroidIBinder介面及其安全性機制

Activity物件與Service物件在不同的進程(Process)裡執行,各有不同的UID(Unix user ID)。由於各自獨立執行,所以Activity物件通常依賴Intent物件去請求Android啟動所需要的Service。

就Service物件的開發者而言,Activity物件是屬於外界(因為兩者在不同的進程裡執行)的軟體,也大多是別人開發的。那麼,Service物件如何確定這外來的物件是善意的呢? 這就是安全性的問題了。在Service類別裡,可以做權限的檢查,其常用指令如下圖:

當Service確認了對方的善意,就將IBinder介面的參考(Reference)傳給Activity物件。Activity物件就能透過IBinder介面去使用Binder的服務了。如下圖:

當Activity呼叫IBinder的transact()等函數時,會反向呼叫NotifyBinder子類別的onTransact()函數。此時,也可以進行安全檢驗,例如下圖裡的指令:

int uid = Binder.getCallingUID();

就能取得對方UID來檢驗它的身分等。還可以進行checkCallingPermission()等檢驗。如下圖:

經檢驗而確認來客是善意的,就啟動BinderServer(如影音播放器)來提供實質的服務。

以上是就Service的開發者角度來看Android的IBinder的安全機制的有關概念。至於Android範例程式碼,在此節錄文章裡,就省略了。

講義摘錄之14: Activity之間的Inter-process溝通

在Android裡,一個Package可以含有多個Activity,這些Activity可以在同一個進程(Process)裡執行;也可以在不同的進程裡執行。基於Linux的安全限制,以及進程的基本特性(例如,不同進程的位址空間是獨立的),Activity-a與Activity-b在同一個進程裡執行時,兩者溝通方便也快速。但是,當Activity-a與Activity-b分別在不同的進程裡執行時,兩者溝通就屬於IPC跨進程溝通了,不如前者方便,也慢些。例如:

/* ===== EX-01 ====== */

/* ac01.java */

package xom.misoo.pkzz;

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

public class ac01 extends Activity implements OnClickListener {

private Button btn, btn4;

public static ac01 appRef = null;

private String feedback_data;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

appRef = this;

setContentView(R.layout.main);

this.show_layout_01();

}

@Override

public void onResume(){

super.onResume();

setTitle(feedback_data);

}

void show_layout_01(){

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

btn = new Button(this);

btn.setBackgroundResource(R.drawable.water);

btn.setText("Edit");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(150, 40);

param.topMargin = 5;

layout.addView(btn, param);

btn4 = new Button(this);

btn4.setBackgroundResource(R.drawable.face);

btn4.setText("Exit");

btn4.setOnClickListener(this);

layout.addView(btn4, param);

setContentView(layout);

}

public void setData(String x){

feedback_data = x;

}

public void onClick(View v){

if (v == btn){

Intent intent = new Intent(this, Activity_1.class);

this.startActivity(intent);

}

if(v.equals(btn4))

this.finish();

}

}

/* Activity_1.java */

package xom.misoo.pkzz;

import android.app.Activity;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

public class Activity_1 extends Activity implements OnClickListener {

private Button btn;

@Override

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

btn = new Button(this);

btn.setBackgroundResource(R.drawable.music);

btn.setText("Edit");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(150, 40);

param.topMargin = 5;

layout.addView(btn, param);

setContentView(layout);

ac01.appRef.setData("feedback from Activity_1.");

}

public void onClick(View arg0) {

finish();

}

}

其中的指令:ac01.appRef.setData("feedback from Activity_1."); 只有ac01與Activity_1兩者都在同一個位址空間(即進程)才會有效。如果將AndroidManifest.xml裡的敘述修改為:

其令Activity_1在獨立的進行裡執行,則上述指令:ac01.appRef.setData("feedback from Activity_1."); 就不對了。

那麼,這種跨進程的情形下,該如何溝通呢?

l 使用SharedPreference

可以使用:

import android.content.SharedPreferences.Editor;

於是,可在Activity_1裡撰寫指令如下:

public class Activity_1 extends Activity implements OnClickListener {

private Button btn;

@Override

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

…………………(省略)

Editor passwdfile = getSharedPreferences("ITEM", 0).edit();

passwdfile.putString("ITEM","feedback from Activity_1.");

passwdfile.commit();

}

…………………(省略)

}

並且,在ac01裡撰寫指令如下:

public class ac01 extends Activity implements OnClickListener {

………………………

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

………………………

}

@Override

public void onResume(){

super.onResume();

SharedPreferences passwdfile = getSharedPreferences(

"ITEM", 0);

String im = passwdfile.getString("ITEM", null);

setTitle(im);

}

…………………………

}

這樣就能Activity_1就能將資料喘地給ac01了。

上述的ac01類別還可寫為:

public class ac01 extends Activity implements OnClickListener {

……………………

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

……………………

}

………………………

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data)

{

SharedPreferences passwdfile = getSharedPreferences(

"ITEM", 0);

String im = passwdfile.getString("ITEM", null);

setTitle(im);

}

public void onClick(View v){

……………………

Intent intent = new Intent(Intent.ACTION_EDIT, null);

this.startActivityForResult(intent, 0);

……………………

}

}

這兩寫法一樣都能讓Activity_1傳回資料。

l 使用Intent物件

雖然透過Intent並非最快速,但卻是最有彈性的。無論是同一進程或是跨進程的溝通都可以使用它。例如:

/* ===== EX-02 ====== */

/* ac01.java */

package com.misoo.pkzz;

import android.app.Activity;

import android.content.Intent;

import android.content.SharedPreferences;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

public class ac01 extends Activity implements OnClickListener {

private Button btn, btn2, btn4;

public static ac01 appRef = null;

private final static String KKK = "AnyKey";

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

appRef = this;

setContentView(R.layout.main);

this.show_layout_01();

}

void show_layout_01(){

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

btn = new Button(this);

btn.setBackgroundResource(R.drawable.water);

btn.setText("Edit");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(150, 40);

param.topMargin = 5;

layout.addView(btn, param);

btn2 = new Button(this);

btn2.setBackgroundResource(R.drawable.x_blue);

btn2.setText("shared data");

btn2.setOnClickListener(this);

layout.addView(btn2, param);

btn4 = new Button(this);

btn4.setBackgroundResource(R.drawable.face);

btn4.setText("Exit");

btn4.setOnClickListener(this);

layout.addView(btn4, param);

setContentView(layout);

}

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data)

{

SharedPreferences passwdfile = getSharedPreferences(

"ITEM", 0);

String im = passwdfile.getString("ITEM", null);

setTitle(im);

}

public void onClick(View v){

if (v == btn){

Intent intent = new Intent(Intent.ACTION_VIEW, null);

intent.putExtra(com.misoo.pkzz.ac01.KKK, "Ax004::Intent Extra Msg.");

this.startActivityForResult(intent, 0);

}

if(v.equals(btn2)){

SharedPreferences passwdfile = getSharedPreferences(

"ITEM", 0);

String im = passwdfile.getString("ITEM", null);

setTitle(im);

}

if(v.equals(btn4))

this.finish();

}

}

/* Activity_1.java */

package com.misoo.pkzz;

import android.app.Activity;

import android.content.SharedPreferences.Editor;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

public class Activity_1 extends Activity implements OnClickListener {

private Button btn;

@Override

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

btn = new Button(this);

btn.setBackgroundResource(R.drawable.x_yellow);

btn.setText("Edit");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(150, 40);

param.topMargin = 5;

layout.addView(btn, param);

setContentView(layout);

//-----------------------------------

String ss = this.getIntent().getStringExtra("AnyKey");

//-----------------------------------

Editor passwdfile = getSharedPreferences("ITEM", 0).edit();

passwdfile.putString("ITEM",ss);

passwdfile.commit();

}

public void onClick(View arg0) {

finish();

}

}

/* AndriodManifest.xml */

package="com.misoo.pkzz"

android:versionCode="1"

android:versionName="1.0.0">

android:label="@string/app_name">

這ac01透過Intent傳遞資料給Activity_1,而Activity_1則將之存入SharePreferences裡,再回傳給ac01類別。

<< 摘錄結語 >>

上述談到的情境是:

l 其ac01與Activity_1屬於同一個Package。

l 其ac01與Activity_1 在同一個進程裡執行,或者,各再獨立的進程裡執行。

然而,如果ac01與Activity_1分別屬於不同的Package時,又如何呢?此時,就不能藉由SharePreferences來傳遞資料了,因為SharePreferences是屬於某Package內的。至於IPC的詳細溝通法,由另外的文章說明之。

==== END ====

講義摘錄之15: 從應用框架角度看ListViewAdapter類別

在Android應用框架(Application Framework, 簡稱AF)裡已經設計了許多類別,其中的

三個類別如下:

圖1 AF裡的類別

這用來協助Activity在UI畫面上顯示一個List視窗,例如:

圖2 應用程式(1) 圖3 應用程式(2)

請你注意AF的特性:Activity、ListView和BaseAdapter兩個類別已經由Android AF開發者設計在先。只是當時Android AF開發者並不知道應用程式(Applications, 簡稱AP)裡的List視窗的內容,所以預留了空間給AP程式師去補充,如下:

圖4 應用程式師的工作

AF設計師與AP程式師兩者在不同的時間及空間(即地點)合作。AF設計師在設計AF當時並不知道AP的List視窗裡各選項的長相(文字或是圖像等),因為這長相的決定是來自於AP的用戶。所以只有AP程式師知道,AF設計師並不知道。於是,AF設計師負責撰寫AF裡的Super-class(如BaseAdapter)而AP程式師負責撰寫AP裡的Subclass(如myStringAdapter)。兩者跨時間和空間的智慧會合而成為一支完整好用的應用程式。例如,AP程式師知道他的UI將顯示出單純的文字型List視窗,所以就撰寫myStringAdapter子類別。

當應用程式執行時,AF掌握整個控制權,呼叫ac01的onCreate()函數,詢問AP程式師的見解:

圖5 AF向AP詢問

在ac01的onCreate()函數裡,AP程式師回答:由myStringAdapter子類別來提供內容及長相給ListView。於是,Activity要求顯示ListView的內容時,ListView就從BaseAdapter而反向呼叫到myStringAdapter的getView()等函數,而取得顯示的內容和長相了。

圖6 AF 依據AP的回應而具有完整的執行路線

於此,請看一個簡單的Android程式碼,來體會上述智慧會合的實現。此程式的畫面顯示一個單純的文字List視窗:

其程式碼如下:

/* ac01.java */

package xom.misoo.pkzz;

import android.app.Activity;

import android.os.Bundle;

import android.view.View;

import android.widget.AdapterView;

import android.widget.ListView;

import android.widget.TextView;

import android.widget.AdapterView.OnItemClickListener;

public class ac01 extends Activity implements OnItemClickListener {

@Override

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

setContentView(R.layout.main);

ListView lv = new ListView(this);

lv.setAdapter(new myAdapter(this));

lv.setOnItemClickListener(this);

setContentView(lv);

}

public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) {

if(arg2 == 0)

setTitle(((TextView)arg1).getText());

else if(arg2 == 1)

finish();

}

}

/* myAdapter.java */

package xom.misoo.pkzz;

import java.util.ArrayList;

import android.content.Context;

import android.graphics.Color;

import android.view.View;

import android.view.ViewGroup;

import android.widget.BaseAdapter;

import android.widget.TextView;

public class myAdapter extends BaseAdapter {

private Context ctx;

private ArrayList coll;

public myAdapter(Context context) {

ctx = context;

coll = new ArrayList();

coll.add("Hello!!");

coll.add("Exit");

}

public int getCount() {

return coll.size();

}

public Object getItem(int position) {

return coll.get(position);

}

public long getItemId(int position) {

return position;

}

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

TextView tv;

if (convertView == null)

tv = new TextView(ctx);

else

tv = (TextView)convertView;

tv.setTextColor(Color.CYAN);

tv.setHeight(45);

tv.setTextSize(26);

tv.setText(coll.get(position));

return tv;

}

}

其於,這樣的基本結構,就能進一步發展出各式各樣的Adapter了。例如ArrayAdapter、SimpleAdapter等等。於此,只節錄講義的前段基礎部份,至於後段的應用部分,就省略之。

講義摘錄之16: 善於設計自己的Listener來進行溝通

一、 你所熟悉的Listener

兩個Activity之間,或者Activity與Service之間,如果它們是在同一個進程(Process)裡,則設計Listener來進行溝通是個好方法。就像Activity與Button之間,也常常使用指令:

public class myActivity extends Activity {

public void onCreate(Bundle icicle) {

………….

Button btn1 = new Button(this);

…………

btn1.setOnClickListener(listener);

}

OnClickListener listener = new OnClickListener() {

………

};

}

這個Listener可以讓Button能將資料傳給Activity。

同樣地,我們也可以將這個技巧,應用於兩個Activity之間,或者Activity與Service之間的溝通。

二、範例程式:兩個Activity間之溝通

先執行ac01:

按下,就執行EventActivity:

輸入資料如:Happy NewYeara!!,並按下,返回到ac01:

就看到所傳來的字串了。其程式碼為:

/* aco1.java */

package com.misoo.ppxx;

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

public class ac01 extends Activity{

private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;

private Button btn, btn2;

@Override

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(100, WC);

param.topMargin = 5;

btn = new Button(this);

btn.setText("Go SubActivity");

btn.setOnClickListener(listener);

layout.addView(btn, param);

btn2 = new Button(this);

btn2.setText("Exit");

btn2.setOnClickListener(listener);

layout.addView(btn2, param);

this.setContentView(layout);

EventActivity.setUpdateListener(new UpdateUIListener());

}

OnClickListener listener = new OnClickListener(){

public void onClick(View v) {

if(v == btn){

Intent in = new Intent(ac01.this, EventActivity.class);

startActivity(in);

}

else if(v == btn2){

finish();

}

}

};

class UpdateUIListener implements IListener {

public void update(String s) {

setTitle(s);

}

}

}

/* EventActivity.java */

package com.misoo.ppxx;

import android.app.Activity;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.EditText;

public class EventActivity extends Activity implements OnClickListener {

private static IListener plis;

private EditText et;

private Button btn;

public static void setUpdateListener(IListener listener) {

plis = listener;

}

@Override

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

setContentView(R.layout.event);

et=(EditText)findViewById(R.id.et);

btn = (Button)findViewById(R.id.send);

btn.setBackgroundResource(R.drawable.x_yellow);

btn.setOnClickListener(this);

}

public void onClick(View v) {

plis.update(et.getText().toString());

finish();

}

}

三、範例程式:Activity與Service間之溝通

先執行ac01:

這個ac01立即啟動myService,定時連續傳來數字,如下:

數字連續增加下去。其程式碼為:

/* aco1.java */

package com.misoo.ppvv;

import android.app.Activity;

import android.content.Intent;

import android.graphics.Color;

import android.os.Bundle;

import android.widget.TextView;

public class ac01 extends Activity {

private TextView tx;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

tx = (TextView)findViewById(R.id.tx);

tx.setTextSize(26);

tx.setTextColor(Color.RED);

tx.setBackgroundResource(R.drawable.x_yellow);

//--------------------------------------

myService.setUpdateListener(new UpdateUIListener());

Intent svc = new Intent(this, myService.class);

startService(svc);

}

@Override protected void onDestroy() {

super.onDestroy(); {

Intent svc = new Intent(this, myService.class);

stopService(svc);

}

}

class UpdateUIListener implements IListener {

public void update(String s) {

tx.setText(" " + s);

}

}

}

/* myService.java */

public class myService extends Service {

private static IListener plis;

private static int k = 0;

private Timer timer = new Timer();

public Handler handler = new Handler(){

public void handleMessage(Message msg) {

plis.update(String.valueOf(k++));

}

};

@Override

public void onCreate() {

super.onCreate();

TimerTask task = new TimerTask() {

@Override

public void run() {

handler.sendEmptyMessage(0);

}

};

timer.schedule(task,1000, 3000);

}

@Override

public IBinder onBind(Intent intent) {

return null;

}

public static void setUpdateListener(IListener listener) {

plis = listener;

}

}

四、結語

以上的Listener方法只適合於在同一個進程裡的物件之溝通;並不適合於跨進程(IPC)之溝通。

跨進程之溝通,可使用Intent、Binder等方式溝通。

=== END ===

講義摘錄之17: 如何使用SurfaceView計時繪點或線

大家常撰寫View的子類別,然後在其onDraw()函數裡繪出各式各樣的圖形,例如畫出點或是直線。不過,基本上onDraw()函數是在Canvas畫完所有線條等圖形後,才一塊兒將Canvas顯示出來。

然而,當我們想再畫出一些線條之後,停個幾秒鐘之後,才繼續繪出後續的圖形,這種有停頓的計時性繪圖,又該如何呢?

例如,先繪出一條綠色線段:

停頓5秒鐘之後,才畫出一條黃色線段:

這時,使用SurfaceView會是一個好方法。如下之範例程式碼:

/* ac01.java */

package com.misoo.ppvv;

import android.app.Activity;

import android.os.Bundle;

import android.widget.LinearLayout;

public class ac01 extends Activity {

@Override

protected void onCreate(Bundle icicle) {

super.onCreate(icicle);

MySurfaceView mv = null;

try {

mv = new MySurfaceView(this);

} catch (InterruptedException e) {

e.printStackTrace();

}

//----------------------------------

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(200, 150);

param.topMargin = 5;

layout.addView(mv, param);

//----------------------------------------

setContentView(layout);

}

}

/* MySurfaceView.java */

package com.misoo.ppvv;

import android.content.Context;

import android.graphics.Color;

import android.view.SurfaceHolder;

import android.view.SurfaceView;

class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {

SurfaceHolder mHolder;

private DrawThread mThread;

private dwList dwl;

MySurfaceView(Context context) throws InterruptedException {

super(context);

getHolder().addCallback(this);

dwl = new dwList();

dwl.begin_record();

dwl.record(30, 30, 0, 0);

dwl.record(100, 100, 1, Color.GREEN);

Thread.sleep(50);

dwl.record(100, 30, 1, Color.YELLOW);

Thread.sleep(60);

dwl.record(30, 30, 1, Color.BLUE);

Thread.sleep(40);

dwl.record(30, 100, 1, Color.RED);

}

public void surfaceCreated(SurfaceHolder holder) {

mHolder = holder;

mThread = new DrawThread();

mThread.start();

}

public void surfaceDestroyed(SurfaceHolder holder) {

mThread = null;

}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

}

// ----------------------------------------------------------------------

class DrawThread extends Thread {

DrawThread() {

super();

}

@Override

public void run() {

dwl.draw(mHolder);

}

}

}

/* dwList.java */

package com.misoo.ppvv;

import java.util.ArrayList;

import java.util.Iterator;

import android.graphics.Canvas;

import android.graphics.Paint;

import android.view.SurfaceHolder;

public class dwList {

private Paint paint= null;

private long draw_time;

private ArrayList poList;

public void begin_record()

{

dwPoint.initial_time = System.currentTimeMillis();

poList.clear();

}

public void record(int x, int y, int ty, int color)

{ dwPoint po = new dwPoint(x, y, ty, color);

poList.add(po);

}

public dwList(){

paint = new Paint();

poList = new ArrayList();

}

public void draw(SurfaceHolder holder) {

dwPoint po;

long curr_time;

long base_time =0;

int nnn = 0;

Iterator it = poList.iterator();

while (it.hasNext() ){

po = it.next();

draw_time = po.m_timeSpan * 100;

if(nnn == 0)

base_time = System.currentTimeMillis();

nnn++;

//--------- waiting ----------------------------------

do {

curr_time = System.currentTimeMillis() - base_time;

}

while (curr_time < draw_time);

//------------------------------------------------------

paint(holder, nnn );

}

}

public void paint(SurfaceHolder holder, int k) {

Canvas canvas = holder.lockCanvas();

dwPoint po;

int lastX = 0;

int lastY = 0;

Iterator it = poList.iterator();

for (int i = 0; i

po = it.next();

if(po.m_type == 0 ) {

lastX = po.m_x;

lastY = po.m_y;

}

else {

paint.setColor(po.m_color);

paint.setStrokeWidth(3);

canvas.drawLine(lastX, lastY, po.m_x, po.m_y, paint);

lastX = po.m_x;

lastY = po.m_y;

}

}

holder.unlockCanvasAndPost(canvas);

}

}

/* dwPoint.java */

package com.misoo.ppvv;

public class dwPoint {

public static long initial_time;

public int m_x, m_y, m_type;

public long m_timeSpan;

public int m_color;

public dwPoint() {}

public dwPoint(int x, int y, int ty, int cc) {

m_x = x;

m_y = y;

m_type = ty;

m_color = cc;

m_timeSpan = (long)(System.currentTimeMillis() - initial_time);

}

}

你可能感兴趣的:(Android,Application)