2010年11月2日 星期二

Android Service-幾種應用範例(二)

一.實作要點

1.說明
Service提供兩種的使用方式,如果Client只是透過startService去執行Service的功能,只是把要做的功能寫在onStartCommand內即可,若是Client透過bindService來執行Service,就需要在Service內產生Ibinder物件,讓onBind可以把binder物件傳給Client,讓Client可以由取到的binder物件再取到Service物件,然後就可以呼叫Service上提供的method

2.Activity(使用Service的Client)
1.extends Activity物件
2.實做ServiceConnection(需要bindService時使用)
  實做並產生一個connection instance,在connection內
  override其onServiceConnected(ComponentName className, IBinder service)
  與onServiceDisconnected(ComponentName className)

3.onServiceConnected被自動呼叫(需要bindService時使用)
  在與Service連線成功後,onServiceConnected會被自動呼
  叫並接收到由Service內部onBinder所發送出來的IBinder物件,
  通常會是extends 自Binder
4.取得Service物件(需要bindService時使用)
  將取得的Binder物件呼叫其Class內定義的方法(如getService)
  來取得Service物件,如Service(binder.getService())
5.利用取得的Service呼叫執行中Service上的public method

3.Service(提供服務的元件)
1.extends Service物件
2.設計binder class extends Binder(需要bindService時使用)
  設計一個內部Binder Clas並產生binder instance,在class內
  建立getService function,可以透過這binder取得所在的service
3.overrider onBinder function(需要bindService時使用)
  將已建立的binder instance傳出去
4.override onStartCommand(需要用startService時使用)
  若只用startService啟動Service,只需要override這
  function即可

二.Sample:LocalService 使用startService

1.說明
透過startService呼叫Service會執行Service內部的onStartCommand(),所以把要執行的工作放在override的onStartCommand()內

2.Activity
Intent intent = new Intent(this, MusicService.class);
intent.putExtra("songPath","sdcard/mp3/mySong.mp3");
this.startService(intent);
3.Service
@Override
public void onCreate() {    
    super.onCreate();
    mp=new MediaPlayer();    
}
@Override
public void onStart(Intent intent, int startId) {
    super.onStart(intent, startId);
    String songPath=intent.getExtras().getString("songPath");
    try{
        mp.setDataSource(_path);
        mp.prepare();
        mp.start();
    }catch(Exception e){};
}

三.Sample:LocalService 使用bindService

1.說明
透過bind方式要執行Service上的功能,是透過建立連線物件後,再直接對Service做操作,要做的工作就寫成Service內public method 讓人呼叫執行

2.Activity
private MusicService mService;
@Override
public void onCreate(Bundle _state){
    doBindService()
}
private void doBindService(){
    Intent intent = new Intent(MyActivity.this, MusicService.class);
    bindService(intent, connc,Context.BIND_AUTO_CREATE);
}
private ServiceConnection connc=new ServiceConnection() {            
    @Override
    public void onServiceDisconnected(ComponentName name) {    
    }
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {        
        mService=((MusicService.LocalBinder)service).getService();        
    }
};

要使用service上的function就可以直接用,如 mService.start(),mService.stop()...等定義在Service內public的方法。若要取得回傳值,只要service內的function有回傳值,直接接收就可以了,例如
String _path=mService.getSongPath();

3.Service
內定義一個內部Binder class可以用來取得service
public class LocalBinder extends Binder {
     MusicService getService() {
        return  MusicService.this;
    }
}
private final IBinder mBinder = new LocalBinder();
@Override
public IBinder onBind(Intent intent) {    
    return mBinder;
}

2010年11月1日 星期一

Android Service-概念(一)

一.說明
Service本身是個無介面,只單純提供功能讓其他元件(Activity)呼叫使用的元件,如果有需要長時間執行的獨立程序,可以寫成Service元件提供其他人呼叫使用。

Service本身並非一個獨立的Process,也不會脫離Main Thread獨立運作,如果需要做長時間的CPU運算,為了不影響到主程式運作,可以在Service內產生Thread,讓thread幫你作一些工作,Service的子類別IntentService即是一個有自己Thread 的Service,服務給並無直接與使用者互動的介面。
當Service元件被產生時,一定會執行其onCreate(),如果有需要產生Thread,則在這裡去產生新的thread

當Service有設定在Manifest上,就可以被自己與其他的App來做存取,其他App要使用service,需在他自己App的Manifest內的uses-permission作宣告

二.執行Service功能的兩種方式
1.startService
當有元件呼叫Context.startService(),系統會讀取被呼叫的Service(產生instance並執行其onCreate()),然後將client所傳進來的intent帶進Service的onStartCommand(Intent, int, int)內。這service會一直執行,直到有人呼叫Context.stopService()或內部呼叫stopSelf()而停止。

無論Context.startService()被呼叫了多少次,如果要停止Service,只要執行一次Context.stopService(),或在Service內執行stopSelf(),就會被停止。
如果是在Service內使用stopSelf(int),可以保證intent已經有被處理了,才會被停止。

對於已被啟動的Service,如果需要有不同的Service執行模式,可以在override onStartCommand()時,設定不同的回傳值。這回傳值常數已經被定義好在Service Class內
1.START_STICKY:
  當Service在執行時被砍掉後,若沒有新的intent進來,
  Service會停留在started state,但intent資料不會被保留
2.START_NOT_STICKY或START_REDELIVER_INTENT:
  當Service在執行時被砍掉後,若沒有新的intent進來,
  service會離開started  state,若沒有很明確的再啟動,
  將不會產生新的service物件

2.bindService
Client可以利用Context.bindService() 去建立與service持續性的連線,
如果建立連線時,還沒產生Service instance,會自動產生並呼叫執行onCreate。與startService不同的地方是,接下來並不會呼叫onStartCommand

要執行bindService,首先要先建立一個serviceConnection物件,把這conection物件當作參數放到bindService內讓Context與Service建立連線,Context.bindService(Intent service, ServiceConnection conn, int flags)
flags參數預設是Context.BIND_AUTO_CREATE,也就是bind的時候會自動產生service

當連線成功後,會自動呼叫執行這個connection內的onServiceConnected(ComponentName className, IBinder service) function,在這function會接收到由service內的onBinde()所丟出來的Ibinder物件
利用這IBinder物件取得Service物件,就可以直接操作Service內各個public 的method

三.Service Process重要性判斷
系統會盡量保持Service的Process運作,但若需要刪除Process,以下是重要性判斷準則
1.正在執行onCreate(), onStartCommand()或onDestroy()的
  Service會被當成前景執行程序,不會被刪除
2.假若這ervice已經被啟動(started),那這service所在的
  process的重要性,會是在visible process與hide process之間
3.如果service所在的Process上有元件是visible,那這Service
  的重要性就相當於是visible
4.一個已經started的service可以利用
  startForeground(int,Notification)放在比較重要的
  前景執行狀態,在記憶體少的時候比較不會被刪除

當記憶體不足Service被刪除後,它稍後還是會被重新啟動產生出來,但通常重新啟動後,原本的intent並不會被保留,如果需要在重新啟動後,再重發intent給Service,必須在override onStartCommand()時return START_REDELIVER_INTENT讓重新啟動的程序裡的onStartCommand(Intent intent, int flags, int startId)內的flags值=START_FLAG_REDELIVERY

Android 資料儲存

一.資料儲存方式
在Android內要保存資料,有以下幾種方式可以選擇
1.SharedPreferences
    以key-value方式儲存只有Private data
2.Internal Storage
    使用device內部的記憶體儲存Private data
3.External Storage
    使用如SD卡這樣的外部儲存媒介,儲存Public data
4.SQLite Databases
    以資料庫結構儲存private data
5.Network Connection
    儲存資料到網路主機上
對於Private Data,在Android利用 Content Providers Class來作存取

二.SharedPreferences
利用 SharedPreferences Class可以將基本型別的資料(boolean,string,float,int..)以key-value方式作存取,data會持續保留在user session,即使app被砍了,資料還是保留著

1.取得SharedPreferences的兩種方式
1.需要用到多個Preference的狀況
  getSharedPreferences(name):如果需要用到多個
  preference file儲存,利用name來取得
  例:
  SharedPreferences settings = Context.getSharedPreferences("myPre", 0);
2.只需要用到一個Preference
  Activity.getPreferences(int mode):如果你的Activity
  只會用到一個preference

2.寫入preference的流程
1.呼叫edit()取得SharedPreferences.Editor
    SharedPreferences settings = getSharedPreferences("myPre", 0);
    SharedPreferences.Editor editor = settings.edit();
2.利用以下method把值寫入putBoolean() and putString()
    editor.putString("myStr", "strValue");
3.使用commit()把改變的資料真正寫入
    editor.commit();

3.讀取Preference值
使用SharedPreferences的getBoolean()、getString()等method
String str = settings.getString("myStr","defaultStr");

4.範例
public class Calc extends Activity {
    public static final String PREFS_NAME = "MyPrefsFile";

    @Override
    protected void onCreate(Bundle state){         
       super.onCreate(state);
       . . .
       // Restore preferences
       SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
       boolean silent = settings.getBoolean("silentMode", false);
       setSilent(silent);
    }

    @Override
    protected void onStop(){
       super.onStop();
      SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
      SharedPreferences.Editor editor = settings.edit();
      editor.putBoolean("silentMode", mSilentMode);
      editor.commit();
    }
}

三.Internal Storage
你可以把資料直接儲存在device的內存,預設儲存在內存的檔案是只有該App可讀取,其他App無法讀取,當App移除時,這資料會一併被移除

1.在內存產生一個private file,寫入資料流程
1.提供一個檔案名稱來呼叫openFileOutput(name)
  取得FileOutputStream物件(Context的method)
2.FileOutputStream物件使用write()寫入資料    
3.FileOutputStream物件使用close()關閉檔案


String FILENAME = "hello_file";
String string = "hello world!";

FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write(string.getBytes());
fos.close();
範例裡的參數MODE_PRIVATE是指當有同名檔案會自動覆蓋

2.由內存讀取檔案資料
1.提供檔名呼叫openFileInput(name)來取得
  FileInputStream物件
2.FileInputStream物件使用read()讀取bytes資料
3.FileInputStream物件使用close()關閉

3.使用暫存
若只是暫存資料,而不是永久存檔可以利用getCacheDir()來取得一個File物件當作暫存檔,當系統內存不足時,這些cacheFile就會被刪除,使用暫存檔需要自己時時去清理

4.其他method
1.getFilesDir()
    取得內存在檔案系統的目錄絕對路徑
2.getDir()
    在內存空間建立目錄或開啟已存在目錄
3.deleteFile()
    刪除內存檔案
4.fileList()
    取得目前App在內存所儲存的檔案清單Array

四.External Storage
所有Android相容device均支援利用外部儲存裝置來儲存檔案(如SD卡),
使用者利用USB即可將資料傳輸到電腦內

1.檢查外存媒介是否存在
在使用外存之前都需先用getExternalStorageState()檢查外存媒介是否存在


String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
    //可以對media作讀寫
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
    //只可以對media作讀取
} else {
    //無法作讀寫
}

2.讀取外存檔案
1.API level8
  使用 Context.getExternalFilesDir(String type)取得一個目錄的
  File物件,這method的type參數,是指定子目錄的類型,
  如DIRECTORY_MUSIC,DIRECTORY_RINGTONES或傳入null取得
  App root目錄,再透過File取得檔案,如果檔案已經存在則會打開
  該檔案。利用這method可直接建立目錄,當App移除時,目錄會跟著
  移除    
    
  例
  File path=Context.getExternalFilesDir(null);
  File file=new File(path,"mypic.jpg")    
2.API Level 7以下版本    
  則用Environment.getExternalStorageDirectory()取得根目錄的
  File物件,也就是讀取的資料會是在
  /Android/data/package_name/files/目錄下
  package_name是如同com.example.android.app的名稱
    
  例
  File path=Environment.getExternalStorageDirectory()
  File file=new File(path,"mypic.jpg")


3.儲存檔案
若要儲存檔案,不指定給特定App用,且希望在App移除時檔案不被刪除,
請儲存到外存上的 Music/, Pictures/, Ringtones/等目錄

1.API Level 8
  使用Context.getExternalStoragePublicDirectory(String type)
  取得目錄的File物件。這method的type參數,設定值為
  DIRECTORY_MUSIC, DIRECTORY_PICTURES,DIRECTORY_RINGTONES,
  如果目錄不存在,會自動產生
    
  例
  File path=Context.getExternalFilesDir(null);
  File file=new File(path,"mypic.jpg")
2.API Level 7 以下版本
  使用Environment.getExternalStorageDirectory()取得根目錄的
  File物件。儲存的資料會是在以下的目錄
  
  Music/ - media scanner會掃描這裡找音樂檔
  Podcasts/ - media scanner會掃描這裡找podcast.
  Ringtones/ - media scanner會掃描這裡找音樂手機檔鈴聲.
  Alarms/ - media scanner會掃描這裡找alarm sound.
  Notifications/ - media scanner會掃描這裡找notification sound.
  Pictures/ - 所有照片包括相機拍的都在這裡.
  Movies/ - 所有影片包括相機拍的都在這裡.
  Download/ - 其他下載檔案.
  
  例
  File rootPath=Environment.getExternalStorageDirectory();
  File path=new File( rootPath.getParent() + "/Music" );
  File file=new File(path,"my.mp3")  


4.使用暫存
1.API Level 8
  使用getExternalCacheDir() 開啟cache File,當App被移除時,
  檔案也會被自動移除,但在App還在的狀態下,使用者要自己管
  理暫存避免空間不足
2.API Level 7以下版本
  使用getExternalStorageDirectory()開啟cache File,檔案會被
  放在/Android/data/package_name/cache/目錄下

五.使用資料庫

1.建立資料庫
Android完全支援SQLite,在App內所有Class都可以透過name來使用DB
要在Android內操作使用DB,可以建立SQLiteOpenHelper 的子類別
並在override onCreate()內,建立產生所要的資料庫資料表


public class MyDbOpenHelper extends SQLiteOpenHelper {
    private static final int DATABASE_VERSION = 2;
    private static final String DICTIONARY_TABLE_NAME = "dictionary";
    private static final String DICTIONARY_TABLE_CREATE =
                "CREATE TABLE " + DICTIONARY_TABLE_NAME + " (" +
                KEY_WORD + " TEXT, " +
                KEY_DEFINITION + " TEXT);";

    DictionaryOpenHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(DICTIONARY_TABLE_CREATE);
    }
}

2.資料庫存取
透過實體化這Class透過呼叫 getWritableDatabase() and getReadableDatabase()取得
SQLiteDatabase物件來對資料庫作讀寫,
查詢動作使用SQLiteDatabase query(),
若是比較複雜的查詢可以用SQLiteQueryBuilder,它會提供一些method來建立查詢

3.查詢結果
每次SQLite 作查詢都會回傳一個Cursor,用來指到查詢結果,
透過Cursor來瀏覽db資料

六.使用網路連線
使用網路連線來將資料儲存於遠端的伺服器
利用以下package提供的物件功能
java.net.*
android.net.*