我平常也是很常用記事本來記事的, 不過有時候其實也是會拿推特來隨手記, 在我手機上, 我最常用的一個記事本就是這個Mobisle Notes Free
這記事本有啥優點呢? 其實他沒很多的功能, 但介面乾淨, 簡單好用, 這已經是很大的優點了:
- 無法分類, 分類功能其實是很重要的, 不然筆記一多就很難整理了
- 同步到雲端去, 比如說同步到Google notes或Google docs, 有這類的功能才有辦法跟其他的裝置, 比如說PC共用
一個Android應用程式有許多種存取資料的方法, Sqllite database是一種方式, 而最原始的方法就是檔案
翻開官方的文件, 在Dev Guide -> Data Storage -> Files 裡的第一段:
You can store files directly on the mobile device or on a removable storage medium. By default, other applications cannot access these files.
一個應用程式可以存取的檔案系統, 除了像是SD Card這種可移除式的媒體外, 就是內建的儲存體, 但使用內建的儲存體有一個缺點, 因為每個Android應用程式如果不是特別指定, 它都會擁有一個專屬的uid (請參考Linux/UNIX的基本文件), 預設開檔的模式是只有自己可以讀取, 因此別的應用程式, 是讀取不到這個檔的, 就算知道檔案的絕對路徑, 這當然是一種安全性的考量, 但有時候針對某些應用我們又希望能夠在不同的應用程式間分享檔案
首先先來看一下如何開啟內建儲存體內的檔案作為寫入跟讀取, 以下是相關的methods:
- Context.openFileInput
- Context.openFileOutput
- Context.deleteFile
- Context.fileList
- Context.getFilesDir
- Context.getFileStreamPath
- Context.getCacheDir
如果是要開啟檔案作為輸出用, 可以用openFileInput, 透過這個寫入的檔案會在 "/data/data/your_app_package_name/files"底下, 這method可以有兩個參數,
openFileOutput(String name, int mode), 第一個當然不用多說, 第二個有幾種模式
- MODE_APPEND 開啟檔案作為附加新內容(Append)
- MODE_PRIVATE 這是預設, 也就是說其他的application (不同的uid), 不可以來存取
- MODE_WORLD_READABLE 全世界的人都可以來讀, 只要知道路徑檔名
- MODE_WORLD_WRITABLE 除了開放給所有的人讀也可以寫
從這邊就可以知道, 開MODE_WORLD_WRITABLE, 就可以達到跨行程檔案共用的方式, 但這方法的缺點是, 等於把權限開放給所有人, 只要知道目錄的應用程式都可以來存取, 甚至可以刪除, 完全無法管控, 其實挺危險的
另外一個方式是透過ContentProvider的方式, 這方式因為透過Uri來存取, 可以隱藏實際檔案位置, 而且不需暴露檔案實際位置, 因為是透過ContentProvider所以也加上permission的管控
在ContentProvider裡面寫法很簡單, 只要overwrite "openFile"即可, openFile接受Uri和file mode兩個參數, Uri和檔案的對應關係, 可以由ContentProvider這邊自由決定, file mode是存取模式, API文件裡面寫的是"rwt", r - read, w -write, t - truncates, 但其實這也是可以由實作端控制, 未必一定要照這規則
這個method必須回傳一個ParcelFileDescriptor, 如下
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
File myFile = new File("/sdcard/abc");
return ParcelFileDescriptor.open(myFile,
ParcelFileDescriptor.MODE_READ_WRITE);
}
在另一個行程需要透過這個ContentProvider 存取檔案也很簡單, 只要利用ContentResolver.openFileDescriptor, 呼叫這個method就可以得到由ContentProvider得回來的ParcelFileDescriptor
可是又要怎存取這個ParcelFileDescriptor呢? 透過ParcelFileDescriptor.getFileDescriptor就可以取得FileDescriptor的實體, 利用這個就可以透過FileInputStream和FileOuputStream來存取了, 如下例
ParcelFileDescriptor remoteFD = resolver.openFileDescriptor("myuri://URI", "rw");
FileOutputStream out = new FileOutputStream(remoteFD.getFileDescriptor());
高階應用:
ParcelFileDescriptor其實也可以來自於Socket, 只要用ParcelFileDescriptor.fromSocket(Socket socket)
怎樣查看一個alarm有沒正確的跟Alarm manager註冊了呢?
這時候就是請出bugreport的時候了:
adb bugreport > bugreport.txt
打開bugreport.txt後搜尋"DUMP OF SERVICE alarm", 你就會發現下面的資訊, 這就是你需要的了:
Current Alarm Manager state:
Realtime wakeup (now=1264350748706):
RTC #1: Alarm{43c639e8 type 1 android}
type=1 when=1264435200000 repeatInterval=0 count=0 operation=PendingIntent{43bb7510: PendingIntentRecord{43bf57a0 android broadcastIntent}} RTC #0: Alarm{43dad548 type 1 android}
type=1 when=1264350780000 repeatInterval=0 count=0 operation=PendingIntent{43b9fc48: PendingIntentRecord{43c13278 android broadcastIntent}}
Elapsed realtime wakeup (now=1979294):
ELAPSED_WAKEUP #0: Alarm{43d33930 type 2 android}
type=2 when=70664306 repeatInterval=0 count=0 operation=PendingIntent{43bb39e8: PendingIntentRecord{43b80d90 android broadcastIntent}}
setReaping與setInexactRepeating有啥不同?
在這系列的第一篇有提到, 無故的叫醒手機是很耗費電力的, 但有時候卻又不得不這樣做, 如果盡可能的把喚醒的間隔拉長是可以緩解這問題
但真正的問題在於, 一支手機內不會只有一支程式需要每隔一段時間觸發一次, 如果碰到以下這狀況:
程式A, 05:05.00 設定每隔1小時
程式B, 05:06.30 設定每隔1小時
程式C, 05:07.00 設定每隔半小時
雖然看起來每支程式觸發最小間隔要半個小時, 但其實在這狀況, 手機分別會在05:37.00, 06:05.00, 06:06.30, 06:07.00 被喚醒, 次數並不少, 而且在六點多這時候被喚醒的間隔是很短的
在1.5之前, 用setRepeating來設定週期性的Alarm的確會有這情形, 但其實, 常常程式不是真的需要在很精確的時間被執行(除非是鬧鐘), 這樣的作法相當浪費
因此在1.5的時候, 多了setInexactRepeating, 這method照API文件上的說明的意義就是盡量把相近的Alarm集中在一起觸發, 如果不是很要求精確在某一時間觸發的話, 其實應該要選用這個
但setInexactRepeating其實有陷阱的, 它的間隔只有在以下幾種數值內才有作用:
如果不是落在這五種的範圍內, setInexactRepeating和setRepeating根本就是等義
Alarm type分為四種
ELAPSED_REALTIME則不同, 它計時的方式是以手機開機後開始計算, 不會受到系統時間變更所影響, 只是它的缺點在於它與系統時間沒有絕對關係, 較適合定時觸發的動作
而有WAKEUP和沒WAKEUP的差別呢? 手機在待機時是有可能進入休眠狀態的, 此時整個系統是不會有動作的, 在有WAKEUP的Alarm被觸發後, 此時Alarm Manager會去把手機給叫醒, 並且請求Wake lock鎖住手機不立刻進入休眠, 直到intent receiver的onReceive做完才會釋放wake lock, 而RTC, ELAPSED_REALTIME是不會強行叫醒手機的, 而是會等到手機真的醒了之後才會有機會被觸發
無端端叫醒手機, 其實對手機電力的影響不小的, 所以在使用Alarm manager必須要很小心去避免無故的叫醒正在沈睡的手機
看了自己前兩篇比較技術性的文章, 發現幾個問題:
之前一篇文章囉哩八梭的講了一堆Intent, 這次來看個實例
拿Pluroid的open source - Pluroium來當作實驗對象, 這次要實驗的是如何替它的compose畫面提昇一個簡單而彈性的功能, 先來看一下畫面:
<Button android:id="@+id/Button01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Attach" android:onClick="handleAttachment"></Button>
public void handleAttachment(View target) {Intent intent = new Intent("org.pluroid.pluroium.COMPOSE_PLUGIN");startActivityForResult(Intent.createChooser(intent, "Choose a plugin"), RESULT_COMPOSE_PLUGIN);}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == RESULT_COMPOSE_PLUGIN && resultCode == RESULT_OK) {
String text = data.getStringExtra(Intent.EXTRA_TEXT);
plurkContent.append(" ");
plurkContent.append(text);
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
PackageManager pm = this.getPackageManager();
Intent intent = new Intent("org.pluroid.pluroium.COMPOSE_PLUGIN");
Listativities = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if(ativities.size() == 0) //No activity found for "org.pluroid.pluroium.COMPOSE_PLUGIN"
findViewById(R.id.Button01).setEnabled(false);
public void doOK(View target) {
Intent data = new Intent();
data.putExtra(Intent.EXTRA_TEXT, "Hello Pluroium");
setResult(RESULT_OK, data);
finish();
}
public void doCancel(View target) {
setResult(RESULT_CANCELED);
finish();
}
----------------------------------
<activity android:name=".app.HelloWorld" android:label="@string/activity_hello_world">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
(寫到這邊有點懶得寫了, 直接寫應用, 因為很多都可以看SDK doc)