2011年12月29日 星期四

Android - 開機(boot complete)後自動執行程式(activity/service)

如果希望android裝置一開機時,就能執行某支程式,無論是activity或是service,流程大致如下:

(1) 需要一個BroadcastReceiver

(2) AndroidManifest.xml對這個Receiver定義intent filter去接收android.intent.action.BOOT_COMPLETED

(3) 在AndroidManifest.xml宣告擁有權限android.permission.RECEIVE_BOOT_COMPLETED

(4) 當Recevier收到intent後,去執行activity或是service


一開就就執行某service 的程式範例如下:
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.testboot"
    android:versionCode="1"
    android:versionName="1.0" >

    <application android:icon="@drawable/ic_launcher"
                 android:label="@string/app_name" >
        <receiver android:name=".util.CommonReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
        <service android:name=".service.UpdaterInitService"/>          
    </application>

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />   
       
</manifest>


Receiver程式如下:
public class CommonReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
       
        if(action.equals(Intent.ACTION_BOOT_COMPLETED)) {
            Intent startIntent = new Intent();
            startIntent.setClass(context, TestService.class);
            context.startService(startIntent);
        }
    }
}

如此一來,開機時系統會broadcast intent "ACTION_BOOT_COMPLETED",我們的Receiver一收到,就會啟動一條TestService

Android - FLAG_RECEIVER_REGISTERED_ONLY介紹

一般而言,sendBroadcast()一個Intent時,有兩種receiver可以收到。

(1) component型式的BroadcastReceiver, 要收的Intent會定義在AndroidManifest.xml
(2) 在java code中動態定義的BroadcastReceiver,用IntentFilter去定義要收那些Intent的Action,再利用registerReceiver()指定IntentFilter給Receiver

如果你不希望sendBroadcast()時,所有的Receiver都被Launch而動作的話,可以利用
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
這樣就只有第2種Receiver會有反應,AndroidManifest.xml所定義的是不會有反應的。

例如在有關耳機控制的程式中,HeadsetObserver.java就是利用

Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
ActivityManagerNative.broadcastStickyIntent(intent, null);

來確保只有第2種Receiver會有反應。有關broadcastStickyIntent()可參考另一篇文章介紹

Android - sendStickyBroadcast(1) - 簡介

sendBroadcast()的用法大家比較常用,這邊不介紹。我們介紹sendStickyBroadcast()的用法,請先看官方說明。基本上是說,sticky intent在被broadcast出去之後,還會留在系統內,不會消失。如果有某個component有register這個Intent可以被receive的話,當這個component被執行時,還是可以receive這個「早就broadcast過的Intent」。sendStickyBroadcast()讓有component去register一個receiver收這個Intent時,馬上就可以收到。

也就是說,如果是sendBroadcast()只能實現,先執行component,再broadcast Intent,component才能收到這個Intent。但是改用 sendStickyBroadcast()則可以實現先broadcast Intent,再執行component,然後component去收Intent的順序

下面的範例需要2個Activity

Activity1.java
Activity2.java

我 們在Activity1中先sendStickyBroadcast(), 然後按去start Activity2,會發現Activity2可以收到剛才那個Intent。如果一開始是用sendBroadcast()的話,會發現按下start Activity2,是沒有辦法印出"This is Activity2 onReceive()"這段LOG的。

AndroidManifest.xml內容如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.stickyintent"
    android:versionCode="1"
    android:versionName="1.0" >

    <application
        android:icon="@drawable/ic_launcher">
        <activity
            android:name=".Activity1" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
       
        <activity
            android:name=".Activity2" >
        </activity>           
    </application>

    <uses-permission android:name="android.permission.BROADCAST_STICKY"/>   
       
</manifest>


Activity1.java 程式如下:
public class Activity1 extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main1);
       
        OnClickListener lis = new OnClickListener(){
            @Override
            public void onClick(View arg0) {
                switch (arg0.getId()) {
                case R.id.btn1:
                    btn1();
                    break;
                case R.id.btn2:
                    btn2();
                    break;
                }
            }   
        };
       
        Button btn1 = (Button) findViewById(R.id.btn1);
        Button btn2 = (Button) findViewById(R.id.btn2);
        btn1.setOnClickListener(lis);
        btn2.setOnClickListener(lis);
    }
   
    private void btn1(){
        Intent it = new Intent("com.test.stickyintent");
        sendStickyBroadcast(it);       
    }
   
    private void btn2(){
        Intent it = new Intent(this, Activity2.class);
        startActivity(it);
    }
}


Activity2.java程式如下:
public class Activity2 extends Activity {
    /** Called when the activity is first created. */
    private IntentFilter mIntentFilter;
   
    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d("gill", "This is Activity2 onReceive()");
        }
    };
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("gill","Activity2 onCreate()");
       
        setContentView(R.layout.main2);
        mIntentFilter = new IntentFilter();    
        mIntentFilter.addAction("com.test.stickyintent");   
    }
   
    @Override 
    protected void onResume() { 
        super.onResume(); 
        Log.d("gill","Activity2 onResume()");
        registerReceiver(mReceiver, mIntentFilter);  
    }
   
    @Override
    protected void onPause() {
        super.onPause();
        Log.d("gill","Activity2 onRause()");
        unregisterReceiver(mReceiver);
    }
}
程式畫面如下:






















執行結果LOG如下:
D/gill    ( 2443): Activity2 onCreate()
D/gill    ( 2443): Activity2 onResume()
D/gill    ( 2443): This is Activity2 onReceive()

Android - HoneyComb的notification builder

在3.0之後,針對status bar notification,google已經不建議使用
Notification notification = new Notification(icon, tickerText, when);
這樣的用法,
應該要改用 Notification.Builder 來取代。
詳細使用參考網頁

程式範例如下:
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder mNotificationBuilder = new Notification.Builder(this);

Intent anotherIntent = new Intent(context, AnotherActivity.class);

mNotificationBuilder.setSmallIcon(R.drawable.icon)
    .setAutoCancel(false)
    .setContentTitle("Title")
    .setContentText("Notification Message Content")
    .setContentIntent(PendingIntent.getActivity(context, 0, anotherIntent, 0));

mNotificationManager.notify(R.drawable.icon, mNotificationBuilder.getNotification());

執行結果如下

Java - 檔案型態 get the file type

如果你想要得知某個檔案的型態,程式如下:

        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        String type = fileNameMap.getContentTypeFor("/mnt/sdcard/a.jpg");
        Log.d("gill","file type = " + type);

你可以從log看到
D/gill    ( 5710): file type = image/jpeg

這個檔案是image且是jpeg的型態

Java - 檔案處理(2) - 複製檔案

在JDK1.4之前,複製檔案可以利用

(1) FileInputStream
(2) FileOutputStream
(3) read()

這三個關鍵方法來完成,程式如下:
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis  = new FileInputStream("/mnt/sdcard/xxx.apk");
            fos = new FileOutputStream("/mnt/sdcard/xxx2.apk");
            byte[] buf = new byte[1024];
            int i = 0;
            while ((i = fis.read(buf)) != -1) {
                fos.write(buf, 0, i);
            }
            if (fis != null) fis.close();
            if (fos != null) fos.close();
        }
        catch (Exception e) {
            e.printStackTrace();
        }

到了JDK1.4之後,有更具效率的新套件可以用。我們改用FileChannel的方式來實作程式:
        FileChannel fis = null;
        FileChannel fos = null;
        try {
            fis  = new FileInputStream("/mnt/sdcard/xxx.apk").getChannel();
            fos = new FileOutputStream("/mnt/sdcard/xxx2.apk").getChannel();
            fos.transferFrom(fis, 0, fis.size());
            if (fis != null) fis.close();
            if (fos != null) fos.close();
        }
        catch (Exception e) {
            e.printStackTrace();
        }

Java - 檔案處理(1) - 如何複製貼上檔案(copy paste)

如果想要把某一個檔案,複製到另一個地方,程式如下:

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis  = new FileInputStream("/mnt/sdcard/sample.apk");
            fos = new FileOutputStream("/mnt/sdcard/sample.apk");
            byte[] buf = new byte[1024];
            int i = 0;
            while ((i = fis.read(buf)) != -1) {
                fos.write(buf, 0, i);
            }
            if (fis != null) fis.close();
            if (fos != null) fos.close();
        }
        catch (Exception e) {
            e.printStackTrace();
        }

Android - 利用DownloadManager下載檔案

在Android2.3中,官網已經提供透過http下載檔案的API,請看官網說明
這裡直接使用範例

package com.test.testdownload;

import android.app.Activity;
import android.app.DownloadManager;
import android.app.DownloadManager.Request;
import android.net.Uri;
import android.os.Bundle;

public class Main extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
       
        //指定檔案來源,必需是http,hppts會有錯誤
        Request request = new Request(
        Uri.parse("http://www.ballpure.com/uploads4/userup/1107/25003614Z55.jpg"));
       
        //存檔路徑及檔名
        Uri uri = Uri.parse("file:///mnt/sdcard/download_name.jpg");
        request.setDestinationUri(uri);
       
        //開始下載
        dm.enqueue(request);
    }
}

AndroidManifest.xml 如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.test.testdownload"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="9" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".Main"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
   
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   
</manifest>

Java - CRC32驗證程式或檔案

如果想要驗證你的程式或是檔案有沒有被更改過時,CRC32是一個很簡單的方式。比如你有一支程式可以幫手機進行檔案的更新,通常作法是先讓使用者 下載新的程式,然後再安裝。但是如果下載後,過了一段時間,程式檔案已經發生損毀,使用者不知道還進行安裝,有可能發生不可預期的錯誤。

因 此,應該在安裝前先檢查一下準備安裝的程式是不是和原本下載的程式一模一樣。CRC32的運作流程是,首先去網路上找一個CRC32的軟體,它可以計算出 你的程式的checksum,然後安裝前再用你自己寫的CRC32程式碼再計算一次當下的程式的checksum, 這兩個checksum如果相同,就代表程式沒有變動過。

用java計算 checksum程式如下:

先要
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;

然後如下:

try {
            CheckedInputStream cis = null;
            cis = new CheckedInputStream(new FileInputStream("/data/test.apk), new CRC32());
            byte[] buf = new byte[128];
            while(cis.read(buf) >= 0) {
            }

            checksum = cis.getChecksum().getValue();

        } catch (Exception e) {
            e.printStackTrace();
        }

假設你原本用網路上抓下來的軟體計算出來的checksum是1000,
然後  checksum = cis.getChecksum().getValue();  這裡計算出來的也是1000,
表示目前這個test.apk和原本提供給使用者的程式是相同的。如果計算出來不是1000,就表示test.apk和當時的程式已經不同了。

Android - 取得系統時間 - Calendar

如果要取得目前的系統時間,可用
import java.util.Calendar;
               
long time=System.currentTimeMillis();
final Calendar mCalendar=Calendar.getInstance();
mCalendar.setTimeInMillis(time);
int mHour=mCalendar.get(Calendar.HOUR);                        //小時
int mMinuts=mCalendar.get(Calendar.MINUTE);                 //分
int mSecond = mCalendar.get(Calendar.SECOND);             //秒
int mMilliSecond = mCalendar.get(Calendar.MILLISECOND);  //千分之1秒

Android - 按鈕平均分配寬度 (rid of button padding)

如果想在畫面的「某一列」,放3個按鈕,而這3個按鈕要平均分配寬度,程式如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="
horizontal" android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <Button android:id="@+id/button1" android:layout_height="wrap_content" android:text="11111"
            android:layout_width="fill_parent" android:layout_weight="1"/>
           
    <Button android:id="@+id/button2" android:layout_height="wrap_content" android:text="2222222"
            android:layout_width="fill_parent" android:layout_weight="1"/>
           
    <Button android:id="@+id/button3" android:layout_height="wrap_content" android:text="3333333333"
            android:layout_width="fill_parent" android:layout_weight="1"/>

</LinearLayout>
如果不要按鈕之間有padding,有幾種作法

(1) 把padding設負值

(2) 給Button 設android:background="#000cab" 隨便給一個顏色,但是高度會變小,要自己設一下

(3) 自己做一張.9.png圖,給Button 設android:background="你的圖"

Android - 動畫TranslateAnimation 和 AnimationSet 的使用

下面範例是利用 TranslateAnimationAnimationSet 來製造ImageView上下移動的動畫。主要觀念為
(1)使用TranslateAnimation設定動畫時間、移動位置、重覆…等效果
(2)AnimationSet可以把多個動畫集合後,一次交付給某元件執行。本例就是下、上、下、上等四個動畫

程式如下:
public class Main extends Activity {
    ImageView img;
    AnimationSet animSet;
    TranslateAnimation anim;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        img = (ImageView) findViewById(R.id.img);
        img.setImageResource(R.drawable.icon);
        animSet = new AnimationSet(true);

        int[] step = {100,-100,100,-100 };
        for (int index = 0; index < 4; index++) {
            anim = new TranslateAnimation(0, 0, 0, step[index]);

            //動畫週期為0.5秒
            anim.setDuration(500);

            //避免元件又回到初始位置
            anim.setFillAfter(true);

            //因為這裡用了四個動畫,所以必須為每個動畫設置初始時間
            //0秒、0.5秒、1秒、1.5秒 為四個動畫初始執行的時間
            anim.setStartOffset(500 * index);

            // 把動畫集合起來
            animSet.addAnimation(anim);
        }

        // img元件開始執行動畫
        img.startAnimation(animSet);
    }
}

Android - Thread 和 Handler (1)

大家都知道
(1) 為了避免ANR,耗時的工作不要放在 UI Thread,要另外拉一條Thread或是用Service
(2) 在Thread工作後如果想要更改UI,要利用Handler來處理畫面
(3) 要注意,不要在Thread的run()裡面才new handler,應該在先前就要new好了。因為handler是為了綁定某個thread,為了處理畫面,應該讓handler綁定main thread。


Java - String split 拆解字串用法(含dot)

如果想要把字串,依據某字串拆解開來,可以使用split。程式如下:

String tempString="aaa:bbbb:ccc:ddddd";
String[] splitStringArray = tempString.split(":");

for (String splitString:splitStringArray){
    Log.d("gill", splitString);
}

則輸出
aaa
bbbb
ccc
ddddd

如果想要拆解的依據是特殊字元的話,則要在前面加上\ 
例如:
\b  \t  \n  \f  \r  \"  \'  \\

最後要注意的是,如果要拆解的依據是句點,也就是 "."
則要使用
\\.

Java - 字串 包含另一 字串 的檢查 indexof

如果要檢查某一字串是否有出現在某一字串,可以用string類別內的indexof,如果回傳值為-1,就表示沒有出現。如果不為-1,就表示有出現。舉例來說

string longString = "abcdefg";
string shortString = "cd";
string errorString="ck";

int havePosition = longString.indexof(shortString);
int noPosition = longString.indexof(errorString);

havePosition的值會等於2,即第1次出現的索引位置。
noPosition的值會等於-1,表示longString裡面不包含errorString這個字串。

Android - 取消Activity動畫

如果程式會連續開啟相同畫面的Activity,卻又不希望每次都有動畫出現,可以在onCreate()加上

getWindow().setWindowAnimations(0);


另外如果是用intent呼叫Activity時,也可以使用

Intent it = new Intent("com.gill.test.intentactivity");
it.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(it);

Eclipse hotkey (熱鍵、快捷鍵)

ctrl + z 回復上一次步驟
ctrl + y 重做下一次步驟

ctrl + / 註解 (取消註解)

ctrl + k 快速搜尋關鍵字 (如果先用ctrl + f 然後再找下一筆)
ctrl + f 搜尋關鍵字

ctrl + shift + o 自動匯入所欠缺的類別

ctrl + l 移至指定行數

ctrl + 滑鼠左鍵 跳至定義位置
alt + ← 跳到上一次游標所在位置
alt + → 跳到下一次游標所在位置
(這個功能常配合ctrl + 滑鼠左鍵,當查完定義後,要再回到原程式位置)

ctrl + shift + s 全部儲存

ctrl + shift + p 跳至匹配的括號
ctrl + q 跳至上一次編輯的位置

-----------------------------------------------------------------------------------------------------

ctrl + shift + L 顯示按鍵輔助

alt + / 程式碼輔助

alt + ↑ 上移 (快速的移動一行或一個段落)
alt + ↓ 下移 (快速的移動一行或一個段落)

ctrl + shift + k 快速搜尋關鍵字 (如果先用ctrl + f 然後再找上一筆)
ctrl + o 搜尋所有位於function名的關鍵字
ctrl + s 儲存

ctrl + D 單行刪除

ctrl + j 向下增量搜尋 (每打1個字母就開始向下搜尋)
ctrl + shift +j 向上增量搜尋 (每打1個字母就開始向上搜尋)

ctrl + shift + f 格式化程式碼

紅色字體為較少見,但是實用的hotkey

Android - 產生scroll bar讓使用者往下拉

我們可以把所有的元件放到ScrollView裡面,這樣就可以在畫面產生scroll bar讓使用者往下拉。但是,ScrollView裡面只可以作用於一個元件,最常見的做法就是,在ScrollView裡面放一個LinearLayout,再把所有的元件放在這個LinearLayout裡面就可以了。

xml範例如下:

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

   <ScrollView   
             android:layout_width="fill_parent"   
             android:layout_height="wrap_content" > 

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

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="This is a TextView" />

    <CheckBox
        android:layout_height="wrap_content"
        android:layout_width="fill_parent"
        android:text="This is a CheckBox" />

    </LinearLayout>

  </ScrollView>

</LinearLayout>

Android - Layout Tool

想要簡單而且很快的繪製程式的畫面,可以使用一個叫的工具。無論是改變元件大小或是位置,都相單快速。就像VB等開發工具。可以從這裡下載程式。畫好畫面只要按下「Generate」就可以產生layout的 xml檔案。












Android - View常見的attribute

layout_marginTop = "10px" 本元件和上元件距離10px
layout_marginBottom = "10px" 本元件和下元件距離10px
layout_marginLeft = "10px" 本元件和左元件距離10px
layout_marginRight = "10px" 本元件和右元件距離10px

singleLine="true"  文字內容最多只有一行,常用於TextView元件

layout_above = "@id/xxxxx" 本元件放在xxxxx元件的上方
layout_below = "@id/xxxxx" 本元件放在xxxxx元件的下方
layout_toLeftOf = "@id/xxxxx" 本元件放在xxxxx元件的左方
layout_toRightOf = "@id/xxxxx" 本元件放在xxxxx元件的右方

layout_alignTop = "@id/xxxxx" 本元件頂部和xxxxx元件頂部切齊
layout_alignBottom = "@id/xxxxx" 本元件底部和xxxxx元件底部切齊
layout_alignLeft = "@id/xxxxx" 本元件左側和xxxxx元件左側切齊
layout_alignRight = "@id/xxxxx" 本元件右側和xxxxx元件右側切齊

layout_alignParentTop = "true" 本元件頂部和外層父元件頂部切齊
layout_alignParentBottom = "true" 本元件底部和外層父元件底部切齊
layout_alignParentLeft = "true" 本元件左側和外層父元件左側切齊
layout_alignParentRight = "true" 本元件右側和外層父元右側切齊

layout_centerHorizontal = "true" 本元件位於水平方向的中央
layout_centerVertical = "true" 本元件位於垂直方向的中央
layout_centerInParent = "true" 本元件位於外層父元件內,水平和垂直方向的中央

background="@drawable/background_pic" 指定background_pic為背景圖片
background="#000000" 指定背景為黑色

visibility="gone" 該元件不可視,而且不佔用面積