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 所以...只能看網友測試或是自己親自下海囉...


2012年9月28日 星期五

Android 開發筆記 - 解決 HttpUriRequest/HttpGet/HttpPost 之 Host name may not be null

最近碰到一個 bug 卡關,那就是當我 new HttpGet("http://aaa_bbb.ccc.dddd") 出來,交由 HttpClient 執行時,卻會看到以下訊息:


java.lang.IllegalArgumentException: Host name may not be null
org.apache.http.HttpHost.<init>(HttpHost.java:83)
org.apache.http.impl.client.AbstractHttpClient.determineTarget(AbstractHttpClient.java:497)
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)


當下讓我連問隔壁的同事幾次...難道 hostname 不能有底線?改成 new HttpGet("aaa_bbb.ccc.dddd") 則是:


java.lang.IllegalStateException: Target host must not be null, or set in parameters.
org.apache.http.impl.client.DefaultRequestDirector.determineRoute(DefaultRequestDirector.java:561)
org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:292)
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)


原始程式碼:


String target = "http://aaa_bbb.ccc.dddd";


HttpClient mClient = new DefaultHttpClient();
HttpContext mHttpContext = new BasicHttpContext();
HttpUriRequest mHttpUriRequest = new HttpGet(target);
response = mClient.execute(mHttpUriRequest, mHttpContext);


解法:


String hostname = "aaa_bbb.ccc.dddd";
String target = "http://"+hostname;


HttpClient mClient = new DefaultHttpClient();
HttpContext mHttpContext = new BasicHttpContext();
HttpUriRequest mHttpUriRequest = new HttpGet(target);
response = mClient.execute(new HttpHost(hostname), mHttpUriRequest, mHttpContext);


不曉得這是不是一個 framework 的 bug?還是單純我操作錯誤呢...暫時先這樣解掉吧。


Android 開發筆記 - 解決/取消 EditText 自動 focus 問題

想必還滿常碰到一個 Activity 中,擺幾個 EditText 讓人輸入帳密來送出的表單吧!然而,當送出表單成功後,偶時會很偷懶直接把 mEditText.setText("Info") 且 mEditText.setEnable(false) 來處理,想說這樣又可以重複利用 XD 結果就會碰到開啟 Activity 後,自動 focus 在 EditText 並彈跳出 keyboard 的窘境了。如果動態進行 mEditText.setFocusable(false) 的方式,的確可以避開 focus 的問題,但很奇妙地再動態 mEditText.setFocusable(true) 時,卻會出錯而無法點選該欄位 Orz


最後,找到一些很折衷的辦法...那就是在 EditText 前,先讓某個處的 layout 可以被 focusable 就好 XD 這樣的解法真的是 It just works! 的狀態。


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical" >

       <LinearLayout
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:focusable="true"
              android:focusableInTouchMode="true"
              android:gravity="center_vertical">
              <TextView
                     android:layout_margin="5dp"
                     android:layout_width="120dp"
                     android:layout_height="wrap_content"
                     android:text="@string/title_account"
                     android:textAppearance="?android:attr/textAppearanceLarge">
              </TextView>
              <EditText
                     android:id="@+id/edittext_account"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:hint="@string/account_hint"
                     android:ems="10" >
              </EditText>
       </LinearLayout>
       <LinearLayout
              android:id="@+id/linearlayout_password"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:focusable="true"
              android:focusableInTouchMode="true"
              android:gravity="center_vertical">
              <TextView
                     android:layout_margin="5dp"
                     android:layout_width="120dp"
                     android:layout_height="wrap_content"
                     android:text="@string/title_password"
                     android:textAppearance="?android:attr/textAppearanceLarge">
              </TextView>


              <EditText
                     android:id="@+id/edittext_password"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:ems="10"
                     android:hint="@string/password_hint"
                     android:inputType="textPassword" >
              </EditText>
       </LinearLayout>
</LinearLayout>


2012年9月26日 星期三

Android 開發筆記 - 處理 API 回應 XML 資料的通用解法

如果 API 不是定義的很好,回應得資料格式不依,但至少有符合簡易的 XML 雛形,如:


<name>changyy</name>
<url>http://changyy.pixnet.net</url>
<style>blog</style>


並且很多支 API 格式都不一樣時,就會讓人想看看有沒統一解法,不小心就想到 Javascript property 的用法,可惜我對 Java 不熟,暫時就先用個 map 來處理了:


import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;


import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;


public class QueryReturnObject {
       private Map<String,String> properties = new HashMap<String,String>();
       public QueryReturnObject(String xml) {
              parsing(xml);
       }
       public String getProperty(String key) {
              if( key == null || !properties.containsKey(key) )
                     return null;
              return properties.get(key);
       }
       public String toString() {
              if( properties.size() == 0 )
                     return "No Properties";
              String out = "";
              for( Iterator<Entry<String, String>> it = properties.entrySet().iterator() ; it.hasNext() ; ) {
                     Entry<String, String> item = it.next();
                     out += "Key=["+item.getKey()+"], Value=["+item.getValue()+"]\n";
              }
              return out;
       }
       void parsing(String raw) {
              properties.clear();
              try {
                     XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
                     XmlPullParser parser = factory.newPullParser();
                     parser.setInput( new StringReader ( raw ) );
                     int eventType = parser.getEventType();
                     String fieldName = null;
                     while( eventType != XmlPullParser.END_DOCUMENT ) {
                            switch(eventType) {
                                   case XmlPullParser.START_DOCUMENT:
                                          break;
                                   case XmlPullParser.START_TAG:
                                          fieldName = parser.getName();
                                          break;
                                   case XmlPullParser.END_TAG:
                                          fieldName = null;
                                          break;
                                   case XmlPullParser.TEXT:
                                          if( fieldName != null )
                                                 properties.put(fieldName, parser.getText());
                                          break;
                            }
                            eventType = parser.next();
                     }
              } catch (XmlPullParserException e) {
                     e.printStackTrace();
              } catch (IOException e) {
                     e.printStackTrace();
              }
       }
}


如此一來,任何 API 回應的資料,就直接用 data = new QueryReturnObject(response) 來使用,接著就可以用 data.getProperty("Key") 的方式來存取囉。