2011年12月8日 星期四

AIDL, couldn't find import for class sni.lib.Note.GestureOperation

這個問題與路徑有關, 在函式庫的配合時,
sni.lib.Note..........class GestureOperation
com.sni.Note.......package to host the Service
sni.lib.Page..........package as Client to link the Service

製作了一個AIDL在com.sni.Note,
package com.sni.Note;
import com.sni.Note.GestureOperation;
interface INoteService {
 GestureOperation[] getOperations();
 }
出現
couldn't find import for class sni.lib.Note.GestureOperation
的錯誤,

然後, 加上GestureOperation.aidl的AIDL檔:
parcelable GestureOperation;
原來的錯誤沒有了,
但是所產生(gen)的java檔報錯,
GestureOperation cannot be resolved to a type
並要求import,
如果用了import, 那就違反了檔頭所說的:
* This file is auto-generated.  DO NOT MODIFY.
這是因為 GestureOperation.aidl 和GestureOperation.java不在一起,
怎麼辦?

將GestureOperation.aidl移至sni.lib.Note, 讓他們生活在一起,
當然, import的路徑要改,
import com.sni.Note.GestureOperation
結果, 原來import的錯誤又回來了,
如果在GestureOperation前加上package, 如:
package sni.lib.Note;
parcelable GestureOperation;
結果出現新的錯誤:
interface GestureOperation should be declared in a file called sni\lib\Note\GestureOperation.aidl.

所以, 暫時看起來沒有解, 有網路文章說, 與路徑有關, 就是GestureOperation放到sni.lib.Note, 但是 aidl compiler找不到, 目前有一個解, 是可以用, 但是如果有多個class要同時使用時, 就要費一點腦子,
sni.lib.Note .............. 放 GestureOperation (java & aidl), INoteGesture.aidl
com.sni.Note ........... 定義Service, 這是因為所需的資料在這個類別裡,
sni.lib.Page .............. 執行 bindService
這樣子, 是可以正常執行的, 因為
INoteGesture ..... GestureOpeartion.aidl ....... GestureOperation.java
都在一起, 避開了路徑的問題

另外在Serivce, action的定義名稱是自取的, 可以與package名稱不相干.

在bindService時也出現錯誤, (回傳 false), 這是因為在GroupActivity底下的Activity是無法使用bindService, 在我的個案中, 使用
getApplicationContext().bindService 依然不行, 但是使用
getParent().bindService就可以了, 這個在網路上有比較完整的討論,
http://blog.tourizo.com/2009/04/binding-services-while-in-activitygroup.html
http://randomizedsort.blogspot.com/2010/11/service-binding-workaround-in-tab.html

還有一點必須注意的是, bindService傳回true, 不代表就可以使用service interface, 必須等onConnection完成. 這是容易理解.



2011年12月6日 星期二

getLaunchIntentForPackage return null

1.確定被呼叫的APP有安裝 (getInstalledPackage)
2.確定被呼叫的APP可以正常執行 (MAIN, LAUNCHER有設定)
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
可是....就是回傳null,

結果發現是:
    <data android:scheme="id" />
在作怪, 只要有這一行, 就回傳null, 換句話說, 若是有 data/scheme就不能用getLauncherForPackage,

所以, 就改用 setClass
     Intent intent = new Intent(Intent.ACTION_MAIN);
     intent.setClassName(packageName, activityName);
     intent.setData(Uri.parse("id://"+id));
     startActivity(intent);
搞定

attempt to write a readonly database

錯誤:
12-06 10:16:43.523: E/AndroidRuntime(286): java.lang.RuntimeException: Unable to start activity ComponentInfo{sni.Note.Comm/sni.Note.Comm.CommActivity}: android.database.sqlite.SQLiteException: attempt to write a readonly database: BEGIN EXCLUSIVE;

1.確定是以ReadWrite的方式開啟的,
2.不應該要設定檔案夾或是檔案的權限, 因為這是在手機, 使用者是可能不會設定, 而且, 以前也不用,

搞了很久, 真是要去撞牆, 原來是權限忘了設:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

2011年11月13日 星期日

AsyncTask & notifyDataChanged

adapter 的notifyDataChanged()必須在UI thread 中呼叫, 如果不是, 例如, 如果在adapter中, 自行呼叫 notifyDataChanged() 將會造成許多問題, 例如, 按鍵無法接受。在我的個案中,按鍵無法接受,不管是長按或是短按,但是,滑動光學滑鼠之後,就正常了,查網,的確有這個問題。
http://stackoverflow.com/questions/4566101/how-to-fix-notifydatasetchanged-listview-problems-in-dynamic-adapter-wrapper-and

解決的方法是使用AsyncTask, 例如:
 class AppendTask extends AsyncTask<Integer, Float, Exception> {
    @Override
    protected void onPreExecute() {
    ... run in UI thread
 
 
 return float-value; 
    }
    @Override
    protected Exception doInBackground(Integer... params) {
    ...
    ... run in WORKING thread
    ...publishProgress(a,b,c);
    }
 
 @Override
     protected void onProgressUpdate(Float progress) {
     ...run in UI thread
     }
    @Override
    protected void onPostExecute(Exception e) {
     ... run in UI thread
  }
  AsyncTask 的三個參數中,第一個參數(Integer), 是doInBackground()的輸入參數類別,是不定個數的參數。第二個參數(Float) 是onProgressUpdate的輸入參數類別,也是不定個數。第三個參數(如例中之Exception)則為doInBackground的傳回值類別,也是onPostExecute()的輸入類別。
四個函數,只有doInBackground在Working thread(沒有UI的thread)中執行,onPreExecute()第一個被執行,接著就執行doInBackground(), 這是不能省略的函數,在函數中可呼叫(也可以不呼叫)onProgressUpdate一次或多次。onPostExecute()則是在doInBackground結束後執行。

2011年11月8日 星期二

CharBuffer.toString() trap-Example: int32toHex

使用CharBuffer.toString()時, 要注意, 輸出的字串, 是從指標所在的位置開始寫的, 因此, 在輸出之前, 要加上: position(0), 如:
以下是一個範例, 將32位元有正負的整數, 傳成十六進位數字,
 /**
  * Convert 32 bits integer number to hex-decimal string
  * @param num number to convert
  * @return hex-decimal string
  */
public static String Int32ToHex(int num) {
  final int BUFFER_SIZE=8;
  final int BITS=32;
  String pattern="0123456789ABCDEF";
  CharBuffer digits=CharBuffer.allocate(BUFFER_SIZE);
  int digit=0;
  int ref=0x80000000;
  int bitValue=0x8;
  for(int i=0;i<BITS;++i) {
      int offset=i%4;
      if(offset==0 && i!=0) {
      digits.append(pattern.charAt(digit));
      digit=0;
      bitValue=0x8;
   }
   if((num & ref)!=0) digit += bitValue;
   bitValue >>= 1;
   /***
    * Notice that,

    *        0x80000000(-2147483648) >> 1 is NOT 0x40000000, but 0xC0000000(-1073741824)
    *        (-->E0000000(536870912)-->F0000000(268435456)-->F8000000(-134217728))
    *        Always divide by 2, no matter positive or negative number
    * and 0xFFFFFFFF(-1) >> 1 is NOT 0x7FFFFFFF, but still 0xFFFFFFFF
    * For the positive integer, the bit operation is running just as expectation.
    */
   if(ref==0x80000000) ref=0x40000000;
   else ref >>= 1;
  }
  digits.append(pattern.charAt(digit));
  digits.position(0);
  return digits.toString();
}

2011年11月3日 星期四

Spinner trap

發現spinner設定OnItemSelectedListener時, 會自動呼叫一次,
而且是選項0, 在網站上有找到類似的說法,
http://blog.csdn.net/androidbluetooth/article/details/6670153
結果解決的方法, 有點怪異,
原來是,
spinner.setSelection(itemID);
spinner.setOnItemSelectedListener(...);
結果在執行時, 會自動呼叫listener, 傳入的選項是0,
將這兩行交換,
先設定Listener, 再設定選項,
一切就OK了 !!

2011年11月2日 星期三

reference link

在Java 中都是用物件的連結,
例如:
Date a;
Date b=a;
則常a的值改變時,b也會跟著變,
此時, 用以下兩個指令:
(1) a=new Date(); 和
(2) a.setTime(new Date().getTime());
將會有不一樣的效果,
用前者, 則中斷了b 與 a 的連結, 原來, b 指向 a 的位址, 但是new 指令, 結束了 a 原來的位址, 而產生了一個新的位址(與本體), 因此, a, b 成為獨立的兩個變數.
進一步地探討, 如果:
a=b;
c=a;
那麼b-->a-->c 三個會連動, 但是如果,
a=b;
b=c;
則 a 將獨立, 而b,c 連動.

這一點在函數傳遞時也有影響,

a=null;
func(a); 在執行時, a 改變了, 外面的a 是不會變的, 但是如果
Date a;
func(a);
如果在函數中, 使用 a=b, 則連動就結束了,
但如果用a.setTime(...), 那函數結束後, a 的值也改了,
但如果用 b=a, 那a, b 就會連動,

有趣!!

2011年10月27日 星期四

自行設計的可視元件在Eclipse的layout中報錯

程式執行正常, 可是在Eclipse的layout中報錯,



無法產生元件, 查看 Winodws > Show View > Error Log 可以看到
android.content.res.Resources$NotFoundException: String array resource ID #0x7f050001
 at android.content.res.Resources.getStringArray(Resources.java:385)
 at com.sni.Task.DateIcon.init(DateIcon.java:72)
 at com.sni.Task.DateIcon.<init>(DateIcon.java:47)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
 at com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback.instantiateClass(ProjectCallback.java:387)
 at com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback.loadView(ProjectCallback.java:155)
 at android.view.BridgeInflater.loadCustomView(BridgeInflater.java:198)
 at android.view.BridgeInflater.createViewFromTag(BridgeInflater.java:126)
 at android.view.LayoutInflater.rInflate(LayoutInflater.java:618)
 at android.view.LayoutInflater.rInflate(LayoutInflater.java:621)
 at android.view.LayoutInflater.inflate(LayoutInflater.java:407)
 at android.view.LayoutInflater.inflate(LayoutInflater.java:296)
 at com.android.layoutlib.bridge.Bridge.computeLayout(Bridge.java:397)
 at com.android.ide.common.rendering.LayoutLibrary.createLegacySession(LayoutLibrary.java:492)
 at com.android.ide.common.rendering.LayoutLibrary.createSession(LayoutLibrary.java:337)
 at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart.renderWithBridge(GraphicalEditorPart.java:1510)
 at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart.renderWithBridge(GraphicalEditorPart.java:1347)
 at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart.recomputeLayout(GraphicalEditorPart.java:1078)
 at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart$ReloadListener.reloadLayoutSwt(GraphicalEditorPart.java:1716)
 at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart$ReloadListener.access$0(GraphicalEditorPart.java:1654)
 at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart$ReloadListener$1.run(GraphicalEditorPart.java:1648)
 at org.eclipse.swt.widgets.RunnableLock.run(RunnableLock.java:35)
 at org.eclipse.swt.widgets.Synchronizer.runAsyncMessages(Synchronizer.java:135)
 at org.eclipse.swt.widgets.Display.runAsyncMessages(Display.java:4140)
 at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3757)
 at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:2696)
 at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:2660)
 at org.eclipse.ui.internal.Workbench.access$4(Workbench.java:2494)
 at org.eclipse.ui.internal.Workbench$7.run(Workbench.java:674)
 at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332)
 at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:667)
 at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:149)
 at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:123)
 at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
 at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)
 at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)
 at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:344)
 at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:179)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:622)
 at org.eclipse.equinox.launcher.Main.basicRun(Main.java:577)
 at org.eclipse.equinox.launcher.Main.run(Main.java:1410)
 at org.eclipse.equinox.launcher.Main.main(Main.java:1386)

看這一長串可怕的資料, 其實指到一行:
weekDays = this.getResources().getStringArray(R.array.week_short);
(resource ID #0x7f050001)當然, 資源是有的, 否則程式不能執行正常, 但是在Eclipse的Layout中讀不到陣列的資料, 因此就報錯了, 發現, 所有使用Context的資料都會報錯, 解決的方法就是用 this.isInEditMode()包起來, 如:
if (! this.isInEditMode()) {
     ....(使用到Context的Code, 或是, 全部包起來)
}
然後就在Eclipse和執行都正常了, 當然, 是在constructor中使用.

2011年10月26日 星期三

Dialog 中的 ListView 只顯示文字長度

在Dialog中, 使用ListView, 乖乖, 選項只有半截, 或者說, 只有文字的長度, 選擇時, 也只能選擇文字所在的區域, 選擇後面的空白就變成無效的選擇, 如下圖:
怎麼會這樣, 一直都是好好的呀, 只好一行一行去試, 結果發現, 是 FEATURE_NO_TITLE,
只要設定這個選項時, 就會有這個錯誤, 如下例:
Dialog dlg = new Dialog(this);
dlg.requestWindowFeature(Window.FEATURE_NO_TITLE);

ArrayList<HashMap<String,Object>> laHelp=new ArrayList<HashMap<String,Object>>();
HashMap<String,Object> map=new HashMap<String,Object>();
map.put(HelpDialog.ICON_KEY, R.drawable.task_cancelled);
map.put(HelpDialog.TEXT_KEY, "test");
laHelp.add(map);
ListView lvw=new ListView(this);
lvw.setAdapter(new SimpleAdapter(this,laHelp, R.layout.help_item,
            new String[] {HelpDialog.ICON_KEY, HelpDialog.TEXT_KEY}, new int[] {R.id.HelpItemImage, R.id.HelpItemText} ));
dlg.setContentView(lvw);
dlg.show();
關鍵就在紅字那一行, 如果刪除, 就正常了, 但是多了抬頭, 怎麼辦呢? 我的方向就是不用SimpleAdapter, 改寫自己的 Adapter, 就正常了.

2011年10月20日 星期四

Re-indent

如果你的原代碼所有的指令都在一行, 只有一行, 很長的一行, 好啦, 許多行也行, 反正就是格式亂七八糟, 如何在Eclipse中排整齊,
使用 Ctrl-Shift-F, 如果有 "//" 就麻煩一點, 必須先將 // 刪除, 或是改成 /* ... */, Ctrl-Shift-F 的選項在,
Source / Format

http://stackoverflow.com/questions/1207264/how-do-i-reindent-all-my-codes-in-eclipse

2011年10月17日 星期一

query like

使用Android 的query指令時,
使用, "Title like ?%", new String[] { string }
或是, "Title like '?%'", new String[] { string }
都是錯誤的, 前者在全部符合時才傳回, 後者根本找不到,
或是改成, "Title like ?", new String { string+"%" }
就可以了,
異哉 !! (應該是 % 的處理, 但是改成 \% 則文法錯誤)

2011年10月13日 星期四

SQLite 的效能

要寫入近二百條的資料到數據庫, 很慢, 受不了, 後來查網路文章, 竟然後發, 將指令包在transaction之內, 就變得很快了, 差有百倍以上,
真是好.

2011年9月21日 星期三

Button Background

按鍵如果設定背景色, 將會從按鍵形狀, 變成一個方型,
如果要透過程式變會原來的按鍵形狀, 並沒有清除背景的設定,
可以用
<code>
  Button btn=new Button(this);
...
  btn.setBackgroundResource(android.R.drawable.btn_default);
</code>

Eclipse start but return exit code=1

環境: Win 7, 64 bits, eclipse 1.6.26 64 bits
錯誤:
在 eclipse.ini 中加入這一行, 就搞定了,
-XX:-UseCompressedOops
其中 -UseCompressed Oops中的減號, 代表 "不要用",
因此, 讓是元件(Oops)壓縮(Compress)程解壓縮的錯誤,

找到一些說明:

-XX command-line options


JVM command-line options that are specified with -XX are not stable and are not recommended for casual use.
These options are subject to change without notice.
-XXallowvmshutdown:[false|true]
This option is provided as a workaround for customer applications that cannot shut down cleanly, as described in APAR IZ59734. Customers who need this workaround should use -XXallowvmshutdown:false. The default option is -XXallowvmshutdown:true forJava 6 SR5 onwards.
-XX:MaxDirectMemorySize=<size>
Sets the maximum size for an nio direct buffer. By default, the maximum size is 64 MB.
-XX:-StackTraceInThrowable
This option removes stack traces from exceptions. By default, stack traces are available in exceptions. Including a stack trace in exceptions requires walking the stack and that can affect performance. Removing stack traces from exceptions can improve performance but can also make problems harder to debug.
When this option is enabled, Throwable.getStackTrace() returns an empty array and the stack trace is displayed when an uncaught exception occurs. Thread.getStackTrace() and Thread.getAllStackTraces() are not affected by this option.
-XX:+UseCompressedOops
(64-bit only) This option enables compressed references in 64-bit JVMs and is provided to help when porting applications from the Sun JVM to the IBM® JVM. This option might not be supported in subsequent releases. The -XX:+UseCompressedOops option is similar to specifying -Xcompressedrefs, which is detailed in the topic JVM command-line options .
-XX:-UseCompressedOops
(64-bit only) This option prevents the use of compressed references in 64-bit JVMs. The option is provided to help when porting applications from the Sun JVM to the IBM JVM, and might not be supported in subsequent releases.
Reference:
http://stackoverflow.com/questions/5054035/eclipse-cdt-crashes-java
http://publib.boulder.ibm.com/infocenter/javasdk/v6r0/index.jsp?topic=%2Fcom.ibm.java.doc.user.aix64.60%2Fdiag%2Fappendixes%2Fcmdline%2Fcommands_jvm_xx.html

*******
其實這個問題並沒有解決, 偶爾還是會, 後來配置又作了一些修改:
-startup
plugins/org.eclipse.equinox.launcher_1.2.0.v20110502.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.100.v20110502
-vm
C:/Program Files/Java/jdk1.6.0_26/bin/javaw.exe
-product
org.eclipse.epp.package.java.product
--launcher.defaultAction
openFile
--launcher.XXMaxPermSize
256M
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
512m
--launcher.defaultAction
openFile
-vmargs
-server
-Xss2m
-XX:+DoEscapeAnalysis
-XX:+UseConcMarkSweepGC
-Dosgi.requiredJavaVersion=1.5
-Xms200m
-Xmx1024m
-XX:-UseCompressedOops
-Xint

2011年8月29日 星期一

無法上傳程式至安卓市集

從星期日開始,就無法上傳程式到安卓市集,出現以下的錯誤。
想,可能是谷歌服務器的關係,就不理會,等一陣子再說吧! 等了一天,還是如此,想,會不會是週末放假了! 或是我們白天,美國晚上的關係,又等了一天,也上網查一下,是否有這方面的訊息或報導。結果,並沒有發現,到了今天早上,想,不該如此的,就換了一部電腦,結果....上傳正常。那就是電腦的問題了!!接著想,那換一個瀏覽器看看,換成火狐,卻出現另外一個問題。無法報到,出現如下訊息:
上面說,是Cookie的問題,或許是,就到IE去,把Cookie給刪了...結果仍然相同,看火狐上面的解釋,都是舊版本的說明,是否是IE9.0的原因呢???但是上傳圖片是好的,是權限的問題嗎?
先治標吧,到火狐,上傳程式,就正常了。




2011年8月9日 星期二

invalidate 部分畫面

寫了一個程式, 自己設計了一個view, MyView 繼承至View
在畫圖(onDraw())時, 座標的計算是相對座標, 例如, (0,0) 對應的是畫布的左上角, 但是在整個螢幕上, 並不是左上角。
在整個螢幕上,有二行,一行是系統的狀態行,一行是標題行。
找Google, 很容易可以找到指令。

Rect rectgle= new Rect();
Window window= getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
int StatusBarHeight= rectgle.top;
int contentViewTop=
window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
int TitleBarHeight= contentViewTop - StatusBarHeight;

重點是:這些指令必須在view所在的thread中執行, 因此,如果你想要在Activity中執行,必須加上:

MyView.post(new Runnable() {
@Override
public void run() {
/* 將執行程式放在此處 */
}
});

另外一個方式是,直接放在View的onDraw()函數。
看起來好像可以用了, 但是,
Invalidate()會調用onDraw(), 沒有錯,
但限定範圍的Invalidate(範圍), 產生的結果卻很怪異,
通常前幾次 (幾乎都是三次,也出現過部分是一次),會畫整個畫面,如同invalidate() (没有參數),造成整個畫布的重劃,這些次數過後,就正常了,只重劃指定的部分。改成postInvalidate()也是相同,只是怪。
指定的操作是:
  1. 設定繪畫方法為call back
  2. 執行invalidate調用onDraw
  3. onDraw呼叫設定的繪畫方法

/**
* Show item is checked
* @param canvas
* @param date
* @param id
*/
public void ShowChecked(Canvas canvas, int date, int id) {
XY xy=this.getXY(date, id);
if(checked==null) {
checked = BitmapFactory.decodeResource(getResources(),R.drawable.yes);
checked = Bitmap.createScaledBitmap(checked,(int)(unit_width*0.8),(int)(unit_height*0.8), true);
}
canvas.drawBitmap(checked, xy.x+(unit_width*0.1f), xy.y, new Paint());
}
/**
* Call the ShowChecked from outside
* @param date
* @param id
*/
public void ShowChecked(final int date, final int id) {
action.add(new Action() {
@Override
public void GoDraw(Canvas canvas) {
ShowChecked(canvas,date,id);
}
});
this.invalidate(region(date,id));
}

2011年8月8日 星期一

態度養成進度: 畫面

花了將近半天的時間, 調整了一個不錯的結果, 效率, 其實可以更好一點。這個工作主要是:
1. 把手機螢幕調成橫的(landscape)。
2. 把這個畫面畫出來。
所謂態度養成,就是每天追蹤。讓我們的思想,變成習慣。

防弊不如興利

我是一個正版軟體的擁護者,尤其是國內的軟體。
我買過許多軟體,大家可能沒聽到。
中文Window 3.0,這是只有三天壽命,由資策會開發的軟天,說來話長了,花了NT$ 3000元。二十年前。
我曾是新翰藝的整套系統的用戶,從霹靂卡、彩虹卡到新翰藝雷射印表機,這是倚天正紅的時候的產品,花了就不知道多少個十萬了,在二十年前。
現在,我依然是。
Dr. Eyes, 譯典通, 因著微軟版本的不斷更新,我也不斷更新,也買了近十套的正版軟體,一般都在一千元上下。因著電腦的不穩定,總是要備份。備份,當然,就連著譯典通一起備份了下來。
問題來了,系統重貫時,這個譯典通一直無法註冊。
當然,我估計是這樣一個原因,譯典通把某一個驗證碼存到特定的地方,在備份時,並沒有備份下來,因此,一直保留在電腦裡,只是,系統回復時,譯典通程式認為要重新註冊,但是拿原來那一組註冊碼去註冊時,與電腦裡的儲存相同,拒絕接受,因此,就卡住了。
打擊罪犯,可能效果相反。為了防範小人,卻造成許多守法的人的不方便。就像是台北市政府,說要保護消費者,結果消費者不能買安卓市集的付費產品。就像許多政客,口口聲聲愛台灣,卻一步一步把台灣逼到死地。人生就是這麼吊詭。
如此一來,每次要回覆之後,都必須把譯典通移除重裝。真是不方便。與客服人員溝通,其實她不能決定什麼,問題是,這個大陸人,連話都不明白。也難為她了。客服人員難為。只是,如果用盜版,反而沒有問題,真是!@#$%︿&
記得高中老師說,人與人之間,70%的時間,用在防弊與猜忌。是嗎?
負擔得起的狀況下,我依然用正版。(負擔不起就考慮不要用)。
勿以善小而不為
勿以惡小而為之
願自己自我警惕 !!

以下是Google Sense廣告:

2011年8月7日 星期日

態度養成(Attitude Builder)

今天,開始《態度養成》應用軟件的開發。態度,就是思考的習慣。我們的判斷與決策,95%來自直覺。也就是習慣。也就是態度。態度決定了我們的決策品質。至關重要,我們明白,然而,如何改善我們的態度呢?
《心理制約》決定了我們的態度。
然而,又如何改變我們的《心理制約》呢?
答案是《間隔重覆》
換句話說:
【間隔重覆】 >> 【心理制約】 >> 【態度】
透過手機工具,追蹤我們的進行,進而養成態度。
例如每天希望早5點早起,每日早起,就在態度追蹤表上,打個勾。
連續實行了21次,就養成習慣了。
如果沒有做到呢? 就要思考一下,這個習慣真的是我們要的嗎? 還是可有可無。思考一下,養成了,有什麼好處?為什麼要養成這個習慣。如果沒有養成呢?有什麼損失,如果是可有可無,那就把它劃掉,我們不必列出來 “給別人看”。或是不斷提醒自己一件,自己不想做的事。


計劃在8/15日前上線. 定價 US$ 0.99元

珍珠生命(Pearly Life)發展計劃

夢想是人生的源頭。有幾個字,是有關的:
夢想、願望、願景、異像。有人說,由人來的,實際的是願望,虛幻的是夢想;由神來的是異像?是或不是,我不知道。天主教曾是追殺基督徒的元兇,基督徒之間,彼此的攻擊也不少,那些大牧者,或許就是作亂的源頭。或許有人明白,但是,我不明白。一切真理的源頭是神。因此,一切真理的實際操作是,禱告。佐之以讀經、交通。保持柔軟謙卑。
這是『我的禱告簿(com.sni.Prayer)』的由來。朋友的姪女,得到總統奬,二十萬,高中生。他說,他的姪女,每天禱告一個小時,一個小時,拿出禱告單,與神切切地交通,尋求神的旨意。這是我應該學習的。每天,依著禱告簿,好好的禱告。
禱告是與神交通,與神交通的目的是尋求神的旨意。神要我們做什麼?我們該做什麼?我們又不該做什麼?我們現在所行所是,對嗎?需要調整嗎?那些該做的我們沒有做?那些沒有做的我們該做?神的旨意,就是異像(我以為是)。異像的抽像,或是統整,就是『人生使命』這就是『我的願景板(com.sni.Vision)』的由來,我的願景板中包含了願景清單人生使命
有了願景,必須要有行動,因此有了以下,正在開發中的軟體:
  1. 分析評估:要達到目標,永遠有三個步驟:
    1. 了解,清楚了解,確實想要,要去那裡?
    2. 了解,深入了解,徹底明白,我在那裡?
    3. 思考,找到,從這裡如何到那裡?
  2. 有了評估分析,接著要採取行動,行動有三個要素:
    1. 目標與計劃,即是行動計劃,行動方案。
    2. 每個月的計劃,這是開始落實。
    3. 正確態度(思考的習慣)的養成
  3. 而所有的行動,都必須要落實在兩件事上
    1. 每日的行程規劃與進行。這就是行事曆。
    2. 每日的工作清單,工作清單來自於『月計劃』『目標計劃』『態度養成』等。



這就是我想要開發的Pearly Life (珍珠生命)的系統。人生的每一天,都應該像珍珠一般地去珍惜,妥善運用。

Android案例開發完全講義(共694頁)

今天,K完這本書,這是一本不錯的書。內容很紮實,而作者是一個有實際設計經驗的人,因此,讀起來,不空洞,很實在,也實際有收獲。對Android沒有認識想認識的人,或是有一些認識想更認識的人,至於高手,那就要看高到什麼程度了。

使用手機測試程式

工具:eclipse
手機:HTC Desire A8181
環境:Win7
用emulator來測試,當然很好。但是有一些與硬體實際相關的功能,或是,直接用手機測試,是否更『精準』一點。
好主意,試試,直接用手機來測試程式。
  1. 在Eclipse端的設定,必須在AndroidMenufest.xml加上可以在手機上測試的選擇,可以有二個方式選一,一個是直接改在xml上,如圖一,一個是在application標簽上,將debuggable設為true, 如圖二。
  2. 在電腦端要安裝驅動程式,這個驅動因手機而不同,在HTC,目前的版本是
    setup_3.0.5557
  3. 在手機端的設定,總共有二件事: 
    • 在設定/應用程式/勾選【未知的來源】,這表示,可以直接安裝apk, 而不須要都是透過android market.
    • 在設定/應用程式/開發,勾選【USB除錯中】
然後,你再執行eclipse的RUN/DEBUG, 就會出現選項,看要在實際的手機上或是模擬器上執行,而且,在手機上執行,仍然可以擷取畫面。Good!!

2011年8月4日 星期四

Android 市集

台北市政府和android market鬧翻了,付費的軟體,不能下載,因此,就只好考慮電信三雄的市集了。

遠傳S市集,入會費500元永久會員,支付扣除營業稅後的70%。
中華電信Softplaza, 無入會費,支付中華電信30%含稅佣金
台灣大哥大match Market, 網頁上沒有註明入會費及拆帳方式
中國移動mm (mobile market), 似乎沒有入會費,70%歸開發商

後記:
今天加入中華電信的 Softplaza, 清晨六點半加入的, 看什麼時候可以收到確認函。選擇中華電信的原因很簡單,因為免費。抽成30%己經很高了,再收費….實在….當然,還是老大說了算….

簽署APK

要放上android market的程式包(apk)必須要簽署,簽署的意義就是將程式設計者相關的資訊以加密的方式附在程式上。別人可以讀得到這些資料,但是不能修改。這樣有一個好處。就是有『產權糾紛』或『版權爭議』時,可以明確界定。使用eclipse開發,在沒有特殊設定時,是沒有簽署的,沒有簽署的程式,依然可以複製到手機上安裝使用。但是要放上android market就必須要簽署,簽署的方式也很簡單,已經整合到eclipse環境之中。
  1. 在所要上線的專案上按右鍵
  2. 選擇 Android tools / Export Signed Application Package…
  3. 接著按指示操作,第一次會要求產生一個store存放加密的鍵值
  4. 有了store後,會要求增加鍵(key)值,可以所有的應用程式共用一個鍵值,或是分群使用,在store及key值都會設定密碼,可以設成一樣或不一樣,在新增key值時,會要求輸入設計者相關的資料。就這樣了。

AdSense被取消

因為要寫收費的Android應用程式,所以必須申請
  1. Google Checkout
  2. AdSense
而申請AdSense需要一個網頁,因此,就依Google的建議,申請了一個blog

http://soundnetinternational.blogspot.com/

然後,向Google申請了一段廣告碼,放在程式後面。
<script type="text/javascript">
<!-- google_ad_client = "pub-2966378125551072";
/* 300x250, 已建立 2011/8/2 */
google_ad_slot = "2454852632";
google_ad_width = 300;
google_ad_height = 250; //-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
乖乖,用了一天,就被停掉了,然後,收到Google的來信:

您好:

如歡迎使用電子郵件所述,只要您開始放送廣告,我們就會開始審查您的 AdSense 帳戶。在我們最近一次審查中,您的帳戶因下列違規而被停權。

問題:
- Google AdSense Support
---------------------
詳細原因:
內容不夠豐富:若想符合加入 AdSense 的資格,您網站的文字內容必須充足;這樣一來,AdSense 專家才能進行審核,我們的檢索器也才能找出適合顯示在您網頁上的相關廣告。因此,我們建議您在網站上加入更多完整的文句和段落。根據規定,網站內容必須全面上線且正常運作,讓使用者能夠透過選單、網站導覽或適當的連結來瀏覽整個網站。只要您網站絕大部分都已完備且運作正常,我們會很樂意重審您的申請。

請注意,只有 AdSense 帳戶獲得核准的發佈商,才能使用 AdSense 網域廣告或 AdSense 網域廣告線上版。

所以,
  1. 網頁內容不足,申請AdSense就不過
  2. AdSense不過,Android Market就沒有辦法付帳
其實,現在也沒有什麼帳要收,而且台北市政府和Google鬧翻了,台灣也無法下載付費的軟體。而這個網頁才剛開,當然沒有什麼內容。自己也不想說什麼,遊戲規則既是如此,就寫一些內容放上去吧。Google這樣規定也無可厚非,但是,對於開發Android的開發商而言,一定要和AdSense綁在一起…..反正,老大說了就算。
  1. 2011/8/7 依Email所說,再提出升請,說,要一個星期或更久,FINE,就等等看。
  2. 2011/8/8 Email來函,授權通過,重新開放使用。
  3. 2011/8/9 重新申請了廣告碼,可是沒有連線上,出現以下訊息。上回剛開始也是如此,不過這次持續比較久。

Google為開發者提供的工具

放上市集的應用程式,在Google中有不錯的分析,會告訴你,有多少人正確安裝。這些人,使用那些廠牌的手機,使用那一個版本的作業系統,是從那一個區域(國家)下載,使用什麼語言。看到自己的程式被全世界使用,是一種快感。自己不是,也不想寫大家歡迎的應用程式,因此,也不期待一炮而紅,就希望,自己的努力,可以幫助一些人,實際、具體的幫助一些人。
自己的『我的禱告簿』,放上去15天,有45人使用。有趣。

static 的使用(在Android)

看自己放到Android Market上的程式,有錯誤報導。

java.lang.NullPointerException
at com.sni.Prayer.PrayerData.Update(PrayerData.java:148)
at com.sni.Prayer.WordActivity$6.onClick(WordActivity.java:173)
at android.view.View.performClick(View.java:2461)
at android.view.View$PerformClick.run(View.java:8888)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4627)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:858)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
at dalvik.system.NativeStart.main(Native Method)

而錯誤的程式是:

private static Cursor getWords(int prayerId) {
Cursor cursor=null;
try {
cursor = database.query(WORDS_TABLE, null, PRAYER_ID_IS+prayerId, null, null, null, null);
} catch(SQLException e) {
Log.e(TAG,e.getMessage());
}
return cursor;
}

Null Exception, 那當然就是database了,但是,為什麼如此? 執行到這一行,應該早就開啟了,為什麼沒有開啟呢? 當然,簡單加個 if(database==null) 的判斷是容易的,我的想法是,一定要找出原因,而且,不必執行的就不必執行,不要執行重覆的判斷。經過分析,發現:
自己很喜歡用static 變數,覺得
  1. 當作global來用
  2. 不必new就可以使用
  3. 只有一份記憶體,省空間
這個database類別,被自己搞成,每一個member都是static, 自己覺得不妥,但是懶得改,真是糟糕,給自己一個警惕。如果都是static, 沒有instance, 是否在記憶體管理時,會被整個御載,御載後再載入時,其static data member的值是否就沒了,變成null了,當然,改成每次讀的時候都check,這是一個方式,但是,這麼多了許多判斷。因此,保留data member 為static, 但是要使用這個資料的函數,都取消,不是static, 保留那些完全沒有使用資料成員的函數為static。每個Activity在調用時,必須先創建(new)一個instance。而在Constructor中加入判斷資料庫database是否為空的判斷。…OK
同時也想到,記憶體管理是以class, Activity為單位。因此,保留每一個類別的”完全”獨立,有助於記憶體管理。因此,將所有static改成private, 或是移到不是Activity的class中。

連續變換方向導致記憶體不足

發現自己開發的Android軟體,有一個問題。如果頻煩地更換方向,一下直,一下橫,多幾次,系統就出現異常錯誤。
因此,就追蹤了一下,直/橫的變化,系統作了些什麼? 結果發現,方向變化時,系統會執行onCreate(), 自己在onCreate中,讀數據庫,讀出列表,顯示在螢幕上,列表中,每一列又有圖標,這麼一下,來來回回,不斷重做,讀出的記憶體,沒有及時釋放,(自己一直不想去控制GC, Garbage Collection, 自己的程式還不是那麼大,殺雞焉用牛刀),結果系統就當掉了,因此,就想,那讀出來的資料就不要再讀了,做過的事,就不要再做了,因此,
  1. 第一個想到,就將變數移成class member, 在讀出來前,先判斷一下,是否為null, 若是null再重讀。
  2. 乖乖,結果相同,想當然耳,會執行onCreate(), 自然是重新產生一個instance,所有的data member當然被reset了,那怎麼辦?
  3. 那就用static,將這個變數鎖住,測試,對了正常了,怎麼變換都不會當了,只有第一次做,以後就不做了。可是有一個問題要處理。每一次啟動這個Activity, 變數將會保留上一個,也不會重新讀取(如果每次需要讀不同的資料)。
  4. 那把清空的動作加在onStop()好了,在結束時設為null, 下回進來就會重讀。測試,結果方向變化也會執行onStop()…..怎麼辦?
  5. 那試onDestroy()好了,方向變換應該不會進行”破壞”吧!很不幸地,方向變換時,依然執行了onDestroy(), 那怎麼辦?
  6. 這麼基本的需求,一定有解。結果發現,在onDestroy()中加上isFinishing()的判斷就好了,如果是方向變換,isFinishing()會傳回false, 如果是結束,就會傳回true。(在onStop()中做,應該也是可以的,想Stop()未必結束,只要沒有Destroy應該都不必重讀,放在onDestroy()中比較省功。

@Override
protected void onDestroy() {
/*
* When you change orientation, onDestroy will be invoked too.
*/
super.onDestroy();
if(this.isFinishing()) {
files=null;
index=0;
bm=null;
}
}

讀Bitmap導致記憶體不足

做一個Android應用程式, 可以更換不同的圖片, 以選擇一幅自己要的, 採用ImageSwitch, 經歷了幾個階段
  1. 一開始, 我使用Dialog來選擇圖片, 可以總選擇了幾個以後, 就出現異常錯誤, 後來用程式碼抓了一下, 發現是記憶體不足的錯誤。

    try {
    bm = BitmapFactory.decodeFile(files[index]);
    imvPickVisual.setImageBitmap(bm);
    } catch(Exception e) {
    Log.e(TAG,e.getMessage());
    } catch(OutOfMemoryError e) {
    Log.e(TAG,e.getMessage());
    }

    原來,有catch(Exception e), 並不能捕捉出記憶體不夠的錯誤,加了 catch(OutOfMemoryError e) 後,補捉到了這個錯誤,然而,為什麼如此呢?
  2. 第一個念頭是,會不會在Dialog中實現,在複雜了,導致錯誤? 因此就改用Activity來現實,在辛苦了一下之後,Dialog沒有了,直接啟動Activity,結果還是一樣。
    接著想,是否是ImageSwitcher的問題,自己用這個元件來實現,這個元件一開始調用了makeView()函數(而且被調用了兩次), 然後圖片轉換時,就不再調用了,是否ImageSwitcher必須要配合Gallery來使用? 因此就加上Gallery好了,結果在2.2版本中,左側的元件表是有Gallery, 但是,放入layout後,系統就說,找不到這個類別(class),必須重新定義,但是改成2.3版就正常了,既然這個元件在2.2版中有定義,也就不必自己在2.2來一回。
  3. 接著想,那改成ImageView好了,反正就只有簡單的,左劃下一張,右劃上一張,如此而己。又辛苦了一下,把ImageSwitcher改成ImageView, 結果仍然相同,選了幾張以後,就Out of memory了。
  4. 再爬一下網路,發現,原來罪魁禍首是 decodeFile(), 這個函數將整個圖片抓下來,釋放沒有及時,幾張圖後,記憶體就完了。要在decode時,加上scale, 而scale最好是2的指數,運算會比較快,實際decode的圖,會是原來圖的1/scale。因此,
    1. 先讀出圖的尺寸
    2. 決定scle
    3. 依scale來decode

    public static Bitmap decodeFile(File file, int max_size) {
    Bitmap bm = null;
    try {
    //Check the image size to be decode, to decide sample size
    BitmapFactory.Options op = new BitmapFactory.Options();
    op.inJustDecodeBounds = true;
    FileInputStream isFile = new FileInputStream(file);
    BitmapFactory.decodeStream(isFile, null, op);
    isFile.close();
    int scale = 1;
    if (op.outHeight > max_size || op.outWidth > max_size) {
    scale = (int) Math.pow(2, (int) Math.round(Math.log(max_size / (double) Math.max(op.outHeight, op.outWidth)) / Math.log(0.5)));
    }

    //Decode with inSampleSize
    op = new BitmapFactory.Options();
    op.inSampleSize = scale;
    isFile = new FileInputStream(file);
    bm = BitmapFactory.decodeStream(isFile, null, op);
    isFile.close();
    } catch(Exception e) {
    Log.e(TAG, e.getMessage());
    }
    return bm;
    }

2011年8月3日 星期三

我的願景板上線了!

昨天,我的願景板上線了。這是我的第一個收費的應用程式,收費0.99美元。這個錢,只有一點點,是跨越公益與商業的界線。因為台北政府為了保護台灣民眾,所以台灣民眾不能下載收費的應用程式。所以,大家目前仍然不能下載使用。
願景,是我們人生的指標。
願景,是讓我們好還要更好的幫助。
願景,是讓我們走過人生低谷的良藥。
願景,是讓我們喜樂的泉源。
願景,是讓我們奮鬥的力量。
願景,是讓我們心連心,手牽手,共同分享、只同努力的紅線。
許多人,在成功後,每下愈況。
許多人,在失敗後,一蹶不振。
許多人,痛苦渡日,怨天尤人。
就是因為,沒有好好地看顧好自己的願景。
因此,就想寫一個程式,隨身可用。
可以記錄、追蹤、實現,我們每一個願景。


新河願景板
我的禱告簿





新河願景板
我們有更詳細的使用說明,如果你有須要較為完整的使用說明,請Email給我,我會回傳給你。
faust.dehng@gmail.com


程式一開始, 會列出所有願景項目。你可以點選後進入該禱告項目之內容。底色表示願景相關的人生領域(身、心、靈、家、公、財、事),可以多選。底色將依所選的領域混色。
整個軟體在操作上的原則就是:
1.按{選單}(Menu)鍵可以調用其他的選項。
2.改什麼就點選什麼。
3.按{回復}(Back)鍵可以回上層選單。
4.在願景清單中,長按,可以有更多的選項。
資料會放在SD卡內 soundnet路徑中。


在願景清單中,點選,可以看到願景的詳細內容,每一個願景可以配一張視覺化圖像,顯示在左上角,如果點選,可以放大這張視覺化圖片,如果長按,可以更改這張視覺化圖片。你可以預先將圖片,放在SD卡內soundnet/picture路徑下,如此,在選擇上就會比較方便。目前提供選擇圖片(長按圖片),會有
1. 拍照的路徑 (SD卡內 DCIM 路徑)
2. 預設圖片路徑 (SD卡內 soundnet/picture)
3. SD卡之根路徑
供選擇,選持圖片時,可以
1. 往左劃/ 往右劃。
2. 點選這一張。

2011年7月20日 星期三

我的禱告簿上架了

第一個開發的Android軟體,我的禱告簿,正式在Android Market上架了,就在今天,大約九點這個時候。
人生,就是一個一直往前走的過程。
從DOS時代,到Windows 3.1, NT, XP, Vista, System 7.....一路跟,很辛苦。
從Fortran, Pascal, Cobol, VB, C, C++, C#, Java....一路學,很辛苦。
從語音系統開發,網頁程式開發,網路系統開發到手機程式開發....一路做,很辛苦。
從自以為是,自負自滿,到歷盡滄桑,反求諸已....一路改,很辛苦。

很辛苦,也很喜樂。
喜樂的是,今是而昨非,生命在改變。
將來會怎麼變,就交託給神了。
就是要努力,要反省,要謙卑,要悔改。

就是要常常喜樂,不住禱告,凡事謝恩。


我的禱告簿





我的禱告簿
如果你有須要較為完整的使用說明,請Email給我,我會回傳給你。faust.dehng@gmail.com


程式一開始, 會列出所有禱告項目。你可以點選後進入該禱告項目之內容。顯示黃色的底是代表『為人代禱』。
整個軟體在操作上的原則就是:
1.按{選單}(Menu)鍵可以調用其他的選項。
2.改什麼就點選什麼。
3.按{回復}(Back)鍵可以回上層選單。
資料會放在SD卡內 soundnet路徑中。

2011年6月27日 星期一

好用的Android軟體

開始使用HTC Desire,
這要感謝Eric,這位老兄是典型的HTC粉絲,
買了HTC Sensation,就把換下來的Desire送給在下了,
實在感激不盡,
感恩之餘,就只能加速軟體的開發了。

要實現:Information at fingers (一機在握,萬事在我) 的理想世界,就先玩一下Android Market的軟體世界。
經過一些摸索,採用
Jorte Calendar 來管理行事曆, 任務 (To Do List)
FunnyGraph 來追蹤一些必要項目, 如總收入,重量...
Note Everything 記錄文字 / 繪畫 / 語音 等記事,要選擇 menu/更多/匯出文字記事到SD記憶卡中,才會匯出,另外,可以選擇 menu/傳送記事 到日曆或是Google文件,很方便。
Thinking Space Pro (NT$161) 用mind map來思考,身為軟體開發者,覺得好用,就付給費用,一百多元新台幣,對作者也是鼓勵和贊助。如果要試用,可以用Thinking Space 免費版
Money Wise Pro (NT$141) 記錄所有的銀行帳本,開銷,收入。
ColorDict 當作英/漢字典

在桌面的Widget(小工具)放著的是
Jorte Calendar 選擇 (目份表示) 4x4
Jorte Calendar 選擇(待辦事項) 4x2
Jorte Calendar 選擇(行事曆) 4x2

另外,找了兩個免費的軟體
荒漠甘泉,KJV Bible,放在桌面,隨時充電一下,
感謝贊美主,
可不要玩物喪志,捨本逐末。

Jorte的同步問題

原來使用Jorte日曆(1.2.25) / HTC Desire, 在任務(Task)時會卡在10%, "取得Google認證" 中,
經爬網站的文章(http://www.mobile01.com/topicdetail.php?f=423&t=1876996&m=f&last=24076203), 解決的方法很簡單
1.先設定成英文 (menu/設定/語言與鍵盤/選取語言/English)
2.選擇同步(選擇待辦事項與備忘錄|menu/同步).....OK
3.再設回中文
4.再試一下同步

當然,你要先設定可以同步
menu/設定/待辦事項帳戶/點選 (gmail帳戶)

Jorte 顯示Task, 還簡捷,但是不能新增/刪除/變更List
同時,也沒有顯示所有List的功能,
比GTask略遜一點,不過,總的來講,很棒的了,而且還免費。