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();
        }