2012年10月5日 星期五

Android 開發筆記 - Unable to resume activity : android.database.StaleDataException: Attempted to access a cursor after it has been closed.

以前常在操作 DBHelper 時,常常因為沒有把 mCursor.close() 而常常出現一些錯誤訊息,後來就很習慣每次取一個 mCursor 出來,用完後就執行 mCursor.close() 來當做完整的收尾,結果開始出現這種訊息並且完全不知道是自己哪一行程式出錯:


FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to resume activity {com.example/com.example.YourActivity}: android.database.StaleDataException: Attempted to access a cursor after it has been closed.
       at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2444)
       at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2472)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1173)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:137)
       at android.app.ActivityThread.main(ActivityThread.java:4424)
       at java.lang.reflect.Method.invokeNative(Native Method)
       at java.lang.reflect.Method.invoke(Method.java:511)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
       at dalvik.system.NativeStart.main(Native Method)
 Caused by: android.database.StaleDataException: Attempted to access a cursor after it has been closed.
        at android.database.BulkCursorToCursorAdaptor.throwIfCursorIsClosed(BulkCursorToCursorAdaptor.java:75)
        at android.database.BulkCursorToCursorAdaptor.requery(BulkCursorToCursorAdaptor.java:144)
        at android.database.CursorWrapper.requery(CursorWrapper.java:186)
        at android.app.Activity.performRestart(Activity.java:4505)
        at android.app.ActivityThread.performRestartActivity(ActivityThread.java:2875)
        at android.app.LocalActivityManager.moveToState(LocalActivityManager.java:168)
        at android.app.LocalActivityManager.dispatchResume(LocalActivityManager.java:523)
        at android.app.ActivityGroup.onResume(ActivityGroup.java:61)
        at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1154)
        at android.app.Activity.performResume(Activity.java:4539)
        at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2434)
        ... 10 more


追到最後,終於發現問題的所在點了,因為我的 mCursor 是從 Activity.managedQuery 產生的,在 Activity.managedQuery 有提到:


Warning: Do not call close() on a cursor obtained using this method, because the activity will do that for you at the appropriate time. However, if you call stopManagingCursor(Cursor) on a cursor from a managed query, the system will not automatically close the cursor and, in that case, you must call close().


這應該就是不懂 Android 架構的關係 Orz 果真懂一點不如不懂 XDD


2012年10月4日 星期四

GPS 座標轉換 NE 常用格式與度分秒

找旅遊資料之餘,發現官方單位給的資料都是類似 N25 02 1.25 E121 33 53.01 這種座標型態,查了一下資料才知道這是度分秒單位。


可以把上述那串丟進 Google Maps 一樣可以通啦。


例如台北 101 GPS 座標為 25.033681,121.564726:


25.033681:


25 度


02 分 = 0.33681 * 60 的整數位 = 2.02086


1.25 秒 = 0.02086 * 60 = 1.2516


121.564726:


121 度


33 分 = 0.564726 * 60 的整數位 = 33.88356


53.01 秒 = 0.88356 * 60 = 53.0136


同理,要從分度秒變成常用的 GPS 座標:


25 度 02 分 1.25 秒:


25 + ( 2 + 1.25 / 60 ) / 60 = 25.0336806


121 度 33 分 53.01 秒:


121 + ( 33 + 53.01 / 60 ) / 60 = 121.564725


後來,大學同學的前東家在做導航機,就說這就像 10進位跟 60 進位的單位轉換囉 :D


[OSX] Adium - 因為 Switchboard 發生錯誤,所以訊息無法送出 @ Mac OS X 10.8

adium

網路上還滿常看到 MAC 上使用 MSN 問題的,在 Mac OS X 上頭,雖然微軟有出 Messenger for Mac,但是我還是滿常碰到 Switchboard 的問題。此問題本身應該是傳訊雙方的網路狀態,例如公司有擋 msn 、防火牆等等,則會常出現這類的問題。

網路上的解法很多是調整 msn 設定,如 "透過HTTP連線" 、 "允許直接連線" 、"連線埠" 等等,這些都可以在微軟的官網也能查到對應的資訊 Windows Live Messenger 使用的網路連接埠與 URL

然而,怎樣設定也解決不了問題 Orz 連改用 Messenger for Mac 也都一樣,最後就換回好用的 Adium ,暫時的解法是…一旦出現 "因為 Switchboard 發生錯誤,所以訊息無法送出" 時,就重新登入,一登入完時可以正常發送訊息。只是每次重新登入也很麻煩,最後想到一招可以 reset Switchboard 了,那就是封鎖對方後,再解除封鎖,也能短暫解除這種問題囉。

Updated: 現在 Skype 6.0 for Mac 已經可以支援 MSN 登入囉!用 Skype 後沒有在碰到這種問題了!

2012年9月30日 星期日

Android 開發筆記 - 簡易 手電筒(閃光燈) app 實作

返家過節,家人分享了 android app 手電筒給我把玩,說真的偶爾還挺實用的,甚至一些 feature phone 都也有這種設計(基本上是真的裝了一個燈)。但我仔細看了一下該 app 的權限,卻開了一堆有的沒有的權限,假設把這個行為丟給國內知名防毒大廠的偵測系統進行偵測,那應該就會被判斷成病毒了。所以我就順手練習一下了 XD


而 android app 的概念則是使用相機的閃光燈,讓閃光燈的狀態停留在 FLASH_MODE_TORCH 時,就可以當做手電筒使用了。然而真正實作上的細節,則必須先開啟相機才能使用閃光燈,也就是用閃光燈之前必須開啟鏡頭並且也會開始把鏡頭收到的影像傳給系統了。簡言之,使用閃光燈其實會操到鏡頭跟系統資料,等同於有資料不停地從鏡頭收進來。


簡單的實作:


layout/main.xml:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:id="@+id/background"
       xmlns:tools="http://schemas.android.com/tools"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <SurfaceView
              android:id="@+id/camera_preview"
              android:layout_width="1dip"
              android:layout_height="1dip"/>


       <TextView
              android:layout_width="150dp"
              android:layout_height="150dp"
              android:layout_centerHorizontal="true"
              android:layout_centerVertical="true"
              android:gravity="center"
              android:text="@string/light"
              android:textColor="#AAA"
              android:textSize="50dp" />


</RelativeLayout>


AndroidManifest.xml:(事實上可以只需android.permission.CAMERA即可,其他只是最佳化的效果)


<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>


Code:


import java.io.IOException;


import android.graphics.Color;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.RelativeLayout;


public class MyFlashLightActivity extends Activity implements SurfaceHolder.Callback {
       boolean have_light = false;


       Camera mCamera = null;
       SurfaceView mSurfaceView;
       SurfaceHolder mSurfaceHolder;
       WakeLock mWakeLock;

       @Override
       public void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_my_flash_light);

              if( ( have_light = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) ) ) {
                     try {
                            mCamera = Camera.open();
                            mSurfaceView = (SurfaceView) findViewById(R.id.PREVIEW);
                            if(mSurfaceView!= null) {
                                   mSurfaceHolder = mSurfaceView.getHolder();
                                   mSurfaceHolder.addCallback(this);
                                   mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

                                   Parameters mParameters = mCamera.getParameters();
                                   mParameters.setFlashMode(Parameters.FLASH_MODE_TORCH);
                                   mCamera.setParameters(mParameters);
                                   mCamera.startPreview();


                            }
                     } catch( Exception e ) {
                            e.printStackTrace();
                     }
              } else {
                     RelativeLayout mRelativeLayout = (RelativeLayout)findViewById(R.id.background);
                     if(mRelativeLayout!=null)
                            mRelativeLayout.setBackgroundColor(Color.WHITE);
              }
       }


       @Override
       protected void onDestroy() {
              // TODO Auto-generated method stub
              super.onDestroy();
              if( have_light ) {
                     mCamera.stopPreview();
                     mCamera.release();
              }
              System.out.println("[D] finish");
       }


       @Override
       protected void onPause() {
              // TODO Auto-generated method stub
              super.onPause();
              if( mWakeLock != null )
                     mWakeLock.release();
       }


       @Override
       protected void onResume() {
              // TODO Auto-generated method stub
              super.onResume();
              if( mWakeLock == null ) {
                     PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
                     mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WAKE_LOCK_TAG");
              }
              mWakeLock.acquire();
       }


       @Override
       public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {


              // TODO Auto-generated method stub
              System.out.println("[i] surfaceChanged");
       }


       @Override
       public void surfaceCreated(SurfaceHolder holder) {
              System.out.println("[i] surfaceCreated");
              try {
                     mCamera.setPreviewDisplay(holder);
                     holder.addCallback(MyFlashLightActivity.this);
              } catch (IOException e) {
                     e.printStackTrace();
              }
       }


       @Override
       public void surfaceDestroyed(SurfaceHolder holder) {
              // TODO Auto-generated method stub
              System.out.println("[i] surfaceDestroyed");


              mCamera.stopPreview();
              mSurfaceHolder = null;
       }
}


此外,有興趣的人,強烈建議去翻一下 Torch, an Android flashlight application 這個 open source ,算是找資料驗證時無意間發現的好物。


2012年9月29日 星期六

找尋支援 Apple NB 記憶體資訊

KTA-MB 1333


最近常看到 MBP 運作的很緩慢,查看記憶體使用量才發現,原先 4GB 的空間已經被用到不到 100MB 了。網路上隨意打滾一下,有的在露天賣 NB 記憶體,說啥有 Apple 認證,或是可以支援等小道消息,抑或看到網路購物直接打出 "Apple認證" 的關鍵字,但一條記憶體硬是比別人多 50%~100% 的價格啊(例如 NB DDR3-1333 8GB 一條現今約台幣1k,但有的主打 Apple 認證賣到一條 2k 左右,更誇張的還有一次賣兩條要價 4.5k 等等 )...


目前正在考慮 金士頓 記憶體,所幸從官網上可以搜尋:


search_ram_01


search_ram_02


如此一來,至少可以先驗證一下,那些露天賣家說的是真是假啦。


註:此例以 Macbook pro 13" late 2011 版本,在 Apple 官網上規格描述 MacBook Pro (13-inch, Late 2011) - Technical Specifications 所述,記憶體規格為 4GB (two 2GB SO-DIMMs) of 1333MHz DDR3 memory; two SO-DIMM slots support up to 8GB。網路上滿多人說可以插到 16GB (8GBx2) 來用,雖然金士頓官網有說 8GB 記憶體可以使用,但並沒有說 2條 8GB 記憶體可以跑 XD 所以...只能看網友測試或是自己親自下海囉...