1. 程式人生 > >Android的應用元件

Android的應用元件

Android的應用元件

Android應用元件

應用元件是 Android 應用的基本構建基塊。

共有四種不同的應用元件型別。每種型別都服務於不同的目的,並且具有定義元件的建立和銷燬方式的不同生命週期。

  1. 活動Activity
  2. 服務Service
  3. 廣播接收器(Broadcast Receiver)
  4. 內容提供程式(Content Provider)

Activity、服務和廣播接收器 — 通過名為 Intent 的非同步訊息進行啟動。Intent 會在執行時將各個元件相互繫結(您可以將 Intent 視為從其他元件請求操作的信使),無論元件屬於您的應用還是其他應用。

元件間的通訊Intent

Intent是連線應用程式的三個核心元件——Activity、Service和BroadcastReceiver的橋樑。

Intent負責對應用中操作的動作、動作涉及資料及附加資料進行描述。

一個Intent物件其實就是資訊的捆綁。

顯式Intent與隱式Intent的區別

Intent宣告啟動的元件有兩種方式:

  • 顯式Intent:通過元件名指定啟動的目標元件,比如startActivity(new Intent(A.this,B.class)); 每次啟動的元件只有一個;

  • 隱式Intent:不指定元件名,而指定Intent的Action,Data,或Category,當我們啟動元件時, 會去匹配AndroidManifest.xml相關元件的Intent-filter

    ,逐一匹配出滿足屬性的元件,當不止一個滿足時, 會彈出一個讓我們選擇啟動哪個的對話方塊。

Intent物件

Intent物件由元件(Componen)名稱、操作(Action)、操作類別(Category)、資料(Data)、資料型別(Type)、附加資訊(Extras)及標誌(Flags)七部分組成。

  1. 元件(Component)名稱

    要處理該Intent的元件名稱。通過setComponent()來設定元件名和getComponent()讀取元件名。

    ComponentName cn = new ComponentName(OneActivity.this, TwoActivity.class);
    Intent it = new Intent();
    it.setComponent(cn);
    
  2. 操作(Action)

    一個字串,用於命名要採取的行動。設定該Intent會觸發的操作型別,可以通過setAction()方法進行設定,在Android系統之中已經為使用者準備好了一些表示Action操作的常量,例如:ACTION_CALL、ACTION_MAIN等。
    Action常量

  3. 操作類別(Category)

    對執行操作的類別進行描述,可以通過addCategory()方法設定多個類別,removeCategory()刪除上次新增的類別,getCategories()獲取當前物件所包含的全部類別。
    操作類別

  4. 資料(Data)

    執行動作要操作的資料,用指向資料的一個URI來表示。描述Intent所操作資料的URI及型別,可以通過setData()進行設定,不同的操作對應著不同的Data。
    Data操作

  5. 資料型別(Type)

    指定要傳送資料的MIME型別,可以直接通過setType()方法進行設定。

  6. 附加資訊(Extras)

    其他所有附加資訊的集合,傳遞的是一組鍵值對,可以使用putExtra()方法進行設定,主要的功能是傳遞資料(Uri)所需要的一些額外的操作資訊。
    附加資訊

  7. 標誌(Flags)

    用於指示Android系統如何載入並執行一個操作,可以通過addFlags()方法進行增加。

常用系統Intent合集

//1.撥打電話
// 給移動客服10086撥打電話
Uri uri = Uri.parse("tel:10086");
Intent intent = new Intent(Intent.ACTION_DIAL, uri);
startActivity(intent);

//===============================================================

//2.傳送簡訊
// 給10086傳送內容為“Hello”的簡訊
Uri uri = Uri.parse("smsto:10086");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
intent.putExtra("sms_body", "Hello");
startActivity(intent);

//3.傳送彩信(相當於傳送帶附件的簡訊)
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra("sms_body", "Hello");
Uri uri = Uri.parse("content://media/external/images/media/23");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType("image/png");
startActivity(intent);

//===============================================================

//4.開啟瀏覽器:
// 開啟百度主頁
Uri uri = Uri.parse("http://www.baidu.com");
Intent intent  = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);

//===============================================================

//5.傳送電子郵件:(閹割了Google服務的沒戲!!!!)
// 給[email protected]發郵件
Uri uri = Uri.parse("mailto:[email protected]");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
startActivity(intent);
// 給[email protected]發郵件傳送內容為“Hello”的郵件
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, "[email protected]");
intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
intent.putExtra(Intent.EXTRA_TEXT, "Hello");
intent.setType("text/plain");
startActivity(intent);
// 給多人發郵件
Intent intent=new Intent(Intent.ACTION_SEND);
String[] tos = {"[email protected]", "[email protected]"}; // 收件人
String[] ccs = {"[email protected]", "[email protected]"}; // 抄送
String[] bccs = {"[email protected]", "[email protected]"}; // 密送
intent.putExtra(Intent.EXTRA_EMAIL, tos);
intent.putExtra(Intent.EXTRA_CC, ccs);
intent.putExtra(Intent.EXTRA_BCC, bccs);
intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
intent.putExtra(Intent.EXTRA_TEXT, "Hello");
intent.setType("message/rfc822");
startActivity(intent);

//===============================================================

//6.顯示地圖:
// 開啟Google地圖中國北京位置(北緯39.9,東經116.3)
Uri uri = Uri.parse("geo:39.9,116.3");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);

//===============================================================

//7.路徑規劃
// 路徑規劃:從北京某地(北緯39.9,東經116.3)到上海某地(北緯31.2,東經121.4)
Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=39.9 116.3&daddr=31.2 121.4");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);

//===============================================================

//8.多媒體播放:
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse("file:///sdcard/foo.mp3");
intent.setDataAndType(uri, "audio/mp3");
startActivity(intent);

//獲取SD卡下所有音訊檔案,然後播放第一首=-= 
Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);

//===============================================================

//9.開啟攝像頭拍照:
// 開啟拍照程式
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 
startActivityForResult(intent, 0);
// 取出照片資料
Bundle extras = intent.getExtras(); 
Bitmap bitmap = (Bitmap) extras.get("data");

//另一種:
//呼叫系統相機應用程式,並存儲拍下來的照片
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 
time = Calendar.getInstance().getTimeInMillis();
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(Environment
.getExternalStorageDirectory().getAbsolutePath()+"/tucue", time + ".jpg")));
startActivityForResult(intent, ACTIVITY_GET_CAMERA_IMAGE);

//===============================================================

//10.獲取並剪下圖片
// 獲取並剪下圖片
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.putExtra("crop", "true"); // 開啟剪下
intent.putExtra("aspectX", 1); // 剪下的寬高比為1:2
intent.putExtra("aspectY", 2);
intent.putExtra("outputX", 20); // 儲存圖片的寬和高
intent.putExtra("outputY", 40); 
intent.putExtra("output", Uri.fromFile(new File("/mnt/sdcard/temp"))); // 儲存路徑
intent.putExtra("outputFormat", "JPEG");// 返回格式
startActivityForResult(intent, 0);
// 剪下特定圖片
Intent intent = new Intent("com.android.camera.action.CROP"); 
intent.setClassName("com.android.camera", "com.android.camera.CropImage"); 
intent.setData(Uri.fromFile(new File("/mnt/sdcard/temp"))); 
intent.putExtra("outputX", 1); // 剪下的寬高比為1:2
intent.putExtra("outputY", 2);
intent.putExtra("aspectX", 20); // 儲存圖片的寬和高
intent.putExtra("aspectY", 40);
intent.putExtra("scale", true);
intent.putExtra("noFaceDetection", true); 
intent.putExtra("output", Uri.parse("file:///mnt/sdcard/temp")); 
startActivityForResult(intent, 0);

//===============================================================

//11.開啟Google Market 
// 開啟Google Market直接進入該程式的詳細頁面
Uri uri = Uri.parse("market://details?id=" + "com.demo.app");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);

//===============================================================

//12.進入手機設定介面:
// 進入無線網路設定介面(其它可以舉一反三)  
Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);  
startActivityForResult(intent, 0);

//===============================================================

//13.安裝apk:
Uri installUri = Uri.fromParts("package", "xxx", null);   
returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri);

//===============================================================

//14.解除安裝apk:
Uri uri = Uri.fromParts("package", strPackageName, null);      
Intent it = new Intent(Intent.ACTION_DELETE, uri);      
startActivity(it); 

//===============================================================

//15.傳送附件:
Intent it = new Intent(Intent.ACTION_SEND);      
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");      
it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/eoe.mp3");      
sendIntent.setType("audio/mp3");      
startActivity(Intent.createChooser(it, "Choose Email Client"));

//===============================================================

//16.進入聯絡人頁面:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(People.CONTENT_URI);
startActivity(intent);

//===============================================================


//17.檢視指定聯絡人:
Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, info.id);//info.id聯絡人ID
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(personUri);
startActivity(intent);

//===============================================================

//18.呼叫系統編輯新增聯絡人(高版本SDK有效):
Intent it = newIntent(Intent.ACTION_INSERT_OR_EDIT);    
it.setType("vnd.android.cursor.item/contact");    
//it.setType(Contacts.CONTENT_ITEM_TYPE);    
it.putExtra("name","myName");    
it.putExtra(android.provider.Contacts.Intents.Insert.COMPANY, "organization");    
it.putExtra(android.provider.Contacts.Intents.Insert.EMAIL,"email");    
it.putExtra(android.provider.Contacts.Intents.Insert.PHONE,"homePhone");    
it.putExtra(android.provider.Contacts.Intents.Insert.SECONDARY_PHONE,"mobilePhone");    
it.putExtra( android.provider.Contacts.Intents.Insert.TERTIARY_PHONE,"workPhone");    
it.putExtra(android.provider.Contacts.Intents.Insert.JOB_TITLE,"title");    
startActivity(it);

//===============================================================

//19.呼叫系統編輯新增聯絡人(全有效):
Intent intent = newIntent(Intent.ACTION_INSERT_OR_EDIT);    
intent.setType(People.CONTENT_ITEM_TYPE);    
intent.putExtra(Contacts.Intents.Insert.NAME, "My Name");    
intent.putExtra(Contacts.Intents.Insert.PHONE, "+1234567890");    
intent.putExtra(Contacts.Intents.Insert.PHONE_TYPE,Contacts.PhonesColumns.TYPE_MOBILE);    
intent.putExtra(Contacts.Intents.Insert.EMAIL, "[email protected]");    
intent.putExtra(Contacts.Intents.Insert.EMAIL_TYPE, Contacts.ContactMethodsColumns.TYPE_WORK);    
startActivity(intent);

//===============================================================

//20.開啟另一程式 
Intent i = new Intent();     
ComponentName cn = new ComponentName("com.example.jay.test",     
"com.example.jay.test.MainActivity");     
i.setComponent(cn);     
i.setAction("android.intent.action.MAIN");     
startActivityForResult(i, RESULT_OK);

//===============================================================

//21.開啟錄音機
Intent mi = new Intent(Media.RECORD_SOUND_ACTION);     
startActivity(mi);

//===============================================================

//22.從google搜尋內容 
Intent intent = new Intent();     
intent.setAction(Intent.ACTION_WEB_SEARCH);     
intent.putExtra(SearchManager.QUERY,"searchString")     
startActivity(intent);

通過Intent回傳資料的操作流程

通過Intent回傳資料的操作流程

Activity程式支援的Intent操作方法:

Activity程式支援的Intent操作方法

如果現在Receive需要在回傳給Send資料的話,則就不能使用startActivity()方法,只能通過
startActivityForResult()方法完成了,但是如果要想接收回傳資料的話,則需要Activity常量的支援:

  • 操作正常狀態碼:public static final int RESULT_OK
  • 操作取消狀態碼:public static final int RESULT_CANCELED
  • 使用者將自定義操作狀態碼:public static final int RESULT_FIRST_USER

Intent傳遞簡單資料:

Intent傳遞簡單資料

定義全域性資料

如果你想某個資料可以在任何地方都能獲取到,你就可以考慮使用 Application全域性物件了!

Android系統在每個程式執行的時候建立一個Application物件,而且只會建立一個,所以Application 是單例(singleton)模式的一個類,而且Application物件的生命週期是整個程式中最長的,他的生命週期等於這個程式的生命週期。如果想儲存一些靜態的值(固定不改變的,也可以變),如果你想使用 Application就需要自定義類實現Application類,並且告訴系統例項化的是我們自定義的Application 而非系統預設的,而這一步,就是在AndroidManifest.xml中為我們的application標籤新增:name屬性!

  1. 自定義Application類:

    class MyApp extends Application {
        private String myState;
        private static MyApp instance;
        
        public static MyApp getInstance(){
            return instance;
        }
        
        
        public String getState(){
            return myState;
        }
        public void setState(String s){
            myState = s;
        }
        
        @Override
        public void onCreate(){
            onCreate();
            instance = this;
        }
     
    }
    
  2. AndroidManifest.xml中宣告:

    <application android:name=".MyApp" android:icon="@drawable/icon" 
      android:label="@string/app_name">
    
  3. 在需要的地方呼叫:

    class Blah extends Activity {
        @Override
        public void onCreate(Bundle b){
            ...
            // 呼叫 MyApp.getInstance()來獲得Application的全域性物件
            MyApp appState = MyApp.getInstance();
            String state = appState.getState();
            ...
        }
    }
    

Activity元件

Activity用於顯示使用者介面,使用者通過Activity互動完成相關操作。一個App允許有多個Activity。

Activity生命週期

應用程式元件有其生命週期:由Android初始化它們,以相應Intent響應意圖,直到結束,例項被銷燬。

Activity生命週期

Activity被一個Activity棧管理。堆疊中儲存物件的例項,在一個任務中可能存在多個同一Activity的例項。

生命週期中的五種狀態:啟動,執行,暫停,停止,銷燬。

  • 啟動:Activity被壓入棧頂。
  • 執行:Activity可見並獲得焦點,與使用者進行互動。
  • 暫停:Activity可見但失去焦點。
  • 停止:Activity被另一個Activity完全覆蓋,不可見,系統可以隨時將其釋放。
  • 銷燬:系統將Activity從記憶體中刪除,Activity被彈出出棧。

Activity的生命週期狀態轉變:

Activity的生命週期狀態轉變

Activity生命週期事件處理函式

  • onCreate(Bundle)

    • 首先建立時呼叫該方法。
    • 執行一次性的初始化工作。
    • 提供Bundle引數
      • 如果Activity之前是被凍結狀態,其狀態由Bundle提供。
      • 接受引數為null或由onSaveInstanceState()方法儲存的狀態資訊。
    • 其後呼叫onStart()onRestart()方法。
  • onStart():當Activity對使用者即將可見時呼叫。

  • onResume():使用者可以開始與活動進行互動時會呼叫該方法。

  • onPause():活動將進入後臺時會執行該方法。

  • onStop():在一段時間內不需要某個活動時,呼叫該方法。

  • onRestart():將已處於停止狀態的活動重新顯示給使用者。

  • onDestroy():銷燬活動前呼叫該方法。如果記憶體不足,系統會終止程序,可能不需要呼叫該方法。

  • onSaveInstanceState(Bundle):呼叫該方法讓活動可以儲存每個例項的狀態。

  • onRestoreInstanceState(Bundle):使用onSaveInstanceState()方法儲存的狀態來重新初始化某個活動時呼叫該方法。

Activity生命週期中函式的呼叫過程

Activity生命週期中函式的呼叫過程

生命週期函式呼叫舉例

有兩個介面Activity A和Activity B。

1、先啟動第一個介面Activity A,方法回撥的次序是:

生命週期函式呼叫1

2、Activity A不關閉,跳轉第二個Activity B,方法回撥的次序是:

生命週期函式呼叫2

3、在點選back回到第一個介面,這時方法回撥的次序是:

生命週期函式呼叫3

4、在點選Exit退出應用時,方法回撥的次序是:

生命週期函式呼叫4

onSaveInstanceState(Bundle)會在下述情形中被呼叫:

  • 點選home鍵回到主頁或長按後選擇執行其他程式
  • 按下電源鍵關閉螢幕
  • 啟動新的Activity
  • 橫豎屏切換時,肯定會執行,因為橫豎屏切換的時候會先銷燬Act,然後再重新建立

重要原則:當系統"未經你許可"時銷燬了你的activity,則onSaveInstanceState會被系統呼叫, 這是系統的責任,因為它必須要提供一個機會讓你儲存你的資料(你可以儲存也可以不儲存)。

Activity使用流程

Activity使用流程

使用者介面狀態儲存

一個Activity被啟用並執行,即為建立了一個Activity的例項。

Activity例項與使用者互動,產生介面狀態資訊。如使用者所選取的值,游標的位置等。

當Activity例項進入“暫停”或“停止”狀態時,需要儲存這些臨時的狀態資訊。

儲存狀態資訊的方法:SharedPreferences物件和Bundle物件。

  1. SharedPreferences物件

    每個Activity都有一個無名的SharedPreferences物件。其SharedPreferences物件的訪問許可權是私有的。

    SharedPreferences提供一種基於name/value形式的鍵值二元組的儲存方式。

    SharedPreferences支援的資料型別有:String、Long、Float、Integer、Boolean。

    方法:

    • getPreferences():獲取SharedPreferences物件。
    • 使用put...()方法儲存鍵-值對。例如儲存字串型的使用putString()方法。

    使用SharedPreferences物件的一般步驟:

    1. 獲得SharedPreferences物件。
    2. 獲得SharedPreferences.Editor物件。
    3. 使用put...()方法儲存鍵-值對。
    4. commit()方法進行提交。

    例,Activity的SharedPreferences物件讀寫程式碼:

    Protected void saveActivityPreferences(){
       SharedPreferences activityPref=getPreferences(Activity.MODE_PRIVATE);
       //獲取Activity的匿名SharedPreferrences物件
       Editor editor=activityPref.edit();
       TextView textView=(TextView)findViewById(R.id.textView);
       //獲取TextView控制元件物件
       editor.putString(“TextValue”,textView.getText().toString());
       //儲存TextView控制元件資訊
       editor.commit();
    }
    
  2. Bundle物件

    在Activity生命週期的方法中,使用Bundle來完成相關資訊的儲存和讀寫。

    生命週期中幾種狀態呼叫的方法,其輸入引數是Bundle型別的有:onCreate()onSaveInstanceState()onRestoreInstanceState()

    在使用Bundle傳遞資料時,要注意,Bundle的大小是有限制的, Bundle內容大小應小於 0.5MB,如果大於這個值,是會報TransactionTooLargeException異常的!

Bundle與SharedPreferences的區別:

  • SharedPreferences是簡單的儲存持久化的設定,它只是一些簡單的鍵值對儲存方式。它將資料儲存在一個xml檔案中。
  • Bundle是將資料傳遞到另一個上下文中或儲存或回覆你自己狀態的資料儲存方式。它的資料不是持久化儲存狀態。

Service元件

Service是後臺執行的,沒有使用者互動介面的“服務”。如:播放音樂;檢測SD卡上檔案的變化;後臺資料計算,如記錄使用者的地理資訊位置的改變;發出Notification。

Service一般由Activity啟動,但不依賴於Activity 。也可以由其他的Service或者Broadcast Receiver啟動。

Service生命週期

Service生命週期

Service的程序優先順序

  • 如果service正在呼叫onCreate()onStart()onDestory()方法,那麼用於當前service的程序則變為前臺程序以避免被killed。

  • 如果service已經被啟動,擁有它的程序僅比可見的程序低,而比不可見的程序重要,這就意味著service一般不會被killed.

  • 如果客戶端已經連線到service ,那麼擁有它的程序則擁有最高的優先順序,可以認為該service是可見的。

  • 如果service可以使用startForeground(int, Notification)方法來將service設定為前臺狀態,那麼系統就認為該service是對使用者可見的,並不會在記憶體不足時killed。

Service元件使用示例

TestService2.java:

public class TestService2 extends Service {  
    private final String TAG = "TestService2";  
    private int count;  
    private boolean quit;  
      
    //定義onBinder方法所返回的物件  
    private MyBinder binder = new MyBinder();  
    public class MyBinder extends Binder  
    {  
        public int getCount()  
        {  
            return count;  
        }  
    }  
      
    //必須實現的方法,繫結改Service時回撥該方法  
    @Override  
    public IBinder onBind(Intent intent) {  
        Log.i(TAG, "onBind方法被呼叫!");  
        return binder;  
    }  
  
    //Service被建立時回撥  
    @Override  
    public void onCreate() {  
        super.onCreate();  
        Log.i(TAG, "onCreate方法被呼叫!");  
        //建立一個執行緒動態地修改count的值  
        new Thread()  
        {  
            public void run()   
            {  
                while(!quit)  
                {  
                    try  
                    {  
                        Thread.sleep(1000);  
                    }catch(InterruptedException e){e.printStackTrace();}  
                    count++;  
                }  
            };  
        }.start();  
          
    }  
      
    //Service斷開連線時回撥  
    @Override  
    public boolean onUnbind(Intent intent) {  
        Log.i(TAG, "onUnbind方法被呼叫!");  
        return true;  
    }  
      
    //Service被關閉前回調  
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        this.quit = true;  
        Log.i(TAG, "onDestroyed方法被呼叫!");  
    }  
      
    @Override  
    public void onRebind(Intent intent) {  
        Log.i(TAG, "onRebind方法被呼叫!");  
        super.onRebind(intent);  
    }  
} 

在AndroidManifest.xml中對Service元件進行註冊:

<service android:name=".TestService2" android:exported="false">  
        <intent-filter>  
            <action android:name="com.jay.example.service.TEST_SERVICE2"/>  
        </intent-filter>  
</service> 

MainActivity.java:

public class MainActivity extends Activity {  
  
    private Button btnbind;  
    private Button btncancel;  
    private Button btnstatus;  
      
    //保持所啟動的Service的IBinder物件,同時定義一個ServiceConnection物件  
    TestService2.MyBinder binder;  
    private ServiceConnection conn = new ServiceConnection() {  
          
        //Activity與Service斷開連線時回撥該方法  
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
            System.out.println("------Service DisConnected-------");  
        }  
          
        //Activity與Service連線成功時回撥該方法  
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            System.out.println("------Service Connected-------");  
            binder = (TestService2.MyBinder) service;  
        }  
    };  
      
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        btnbind = (Button) findViewById(R.id.btnbind);  
        btncancel = (Button) findViewById(R.id.btncancel);  
        btnstatus  = (Button) findViewById(R.id.btnstatus);  
        final Intent intent = new Intent();  
        intent.setAction("com.jay.example.service.TEST_SERVICE2");  
        btnbind.setOnClickListener(new OnClickListener() {            
            @Override  
            public void onClick(View v) {  
                //繫結service  
                bindService(intent, conn, Service.BIND_AUTO_CREATE);                  
            }  
        });  
          
        btncancel.setOnClickListener(new OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                //解除service繫結  
                unbindService(conn);                  
            }  
        });  
          
        btnstatus.setOnClickListener(new OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                Toast.makeText(getApplicationContext(), "Service的count的值為:"  
                        + binder.getCount(), Toast.LENGTH_SHORT).show();  
            }  
        });  
    }  
}  

IntentService

由於

  1. Service不是一個單獨的程序,它和它的應用程式在同一個程序中
  2. Service不是一個執行緒,這樣就意味著我們應該避免在Service中進行耗時操作

所以如果把耗時執行緒放到Service中的onStart()方法中,很容易會引起ANR異常(Application Not Responding)。

IntentService是繼承與Service並處理非同步請求的一個類,在IntentService中有 一個工作執行緒來處理耗時操作,請求的Intent記錄會加入佇列

工作流程:

  • 客戶端通過startService(Intent)來啟動IntentService;
  • 我們並不需要手動地去控制IntentService,當任務執行完後,IntentService會自動停止;
  • 可以啟動IntentService多次,每個耗時操作會以工作佇列的方式在IntentService的 onHandleIntent回撥方法中執行,並且每次只會執行一個工作執行緒,執行完一,再到二這樣!

TestService3.java

public class TestService3 extends IntentService {  
    private final String TAG = "hehe";  
    //必須實現父類的構造方法  
    public TestService3()  
    {  
        super("TestService3");  
    }  
  
    //必須重寫的核心方法  
    @Override  
    protected void onHandleIntent(Intent intent) {  
        //Intent是從Activity發過來的,攜帶識別引數,根據引數不同執行不同的任務  
        String action = intent.getExtras().getString("param");  
        if(action.equals("s1"))Log.i(TAG,"啟動service1");  
        else if(action.equals("s2"))Log.i(TAG,"啟動service2");  
        else if(action.equals("s3"))Log.i(TAG,"啟動service3");  
          
        //讓服務休眠2秒  
        try{  
            Thread.sleep(2000);  
        }catch(InterruptedException e){e.printStackTrace();}          
    }  
  
    //重寫其他方法,用於檢視方法的呼叫順序  
    @Override  
    public IBinder onBind(Intent intent) {  
        Log.i(TAG,"onBind");  
        return super.onBind(intent);  
    }  
  
    @Override  
    public void onCreate() {  
        Log.i(TAG,"onCreate");  
        super.onCreate();  
    }  
  
    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        Log.i(TAG,"onStartCommand");  
        return super.onStartCommand(intent, flags, startId);  
    }  
  
  
    @Override  
    public void setIntentRedelivery(boolean enabled) {  
        super.setIntentRedelivery(enabled);  
        Log.i(TAG,"setIntentRedelivery");  
    }  
      
    @Override  
    public void onDestroy() {  
        Log.i(TAG,"onDestroy");  
        super.onDestroy();  
    }  
      
} 

AndroidManifest.xml註冊下Service

<service android:name=".TestService3" android:exported="false">  
    <intent-filter >  
        <action android:name="com.test.intentservice"/>  
    </intent-filter>  
</service>  

在MainActivity啟動三次服務:

public class MainActivity extends Activity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
          
        Intent it1 = new Intent("com.test.intentservice");  
        Bundle b1 = new Bundle();  
        b1.putString("param", "s1");  
        it1.putExtras(b1);  
          
        Intent it2 = new Intent("com.test.intentservice");  
        Bundle b2 = new Bundle();  
        b2.putString("param", "s2");  
        it2.putExtras(b2);  
          
        Intent it3 = new Intent("com.test.intentservice");  
        Bundle b3 = new Bundle();  
        b3.putString("param", "s3");  
        it3.putExtras(b3);  
          
        //接著啟動多次IntentService,每次啟動,都會新建一個工作執行緒  
        //但始終只有一個IntentService例項  
        startService(it1);  
        startService(it2);  
        startService(it3);  
    }  
} 

BroadcastReceiver元件

BroadcastReceiver是對廣播訊息進行過濾並響應的控制元件。

兩種廣播型別

廣播型別

接收系統廣播

接收廣播服務的過程

  1. 開發BroadcastReveiver類的子類,在其中過載onReceive()方法;
  2. 等待接收廣播:
  3. 我們註冊的BroadcastReceiver並非一直在後臺執行,一旦當事件或相關的Intent傳來,就會被系統呼叫,處理onReceive()方法裡的響應事件。

兩種註冊廣播的方式

註冊廣播方式

  • 動態註冊:通過呼叫registerReceiver()方法來註冊。程式碼如:
    MyReceiver receiver=new MyReceiver();   //建立相關物件
    IntentFilter filter=new IntentFilter();  
    filter.addAction(DATE_CHANGED);  
    registerReceiver(receiver, filter);   //動態註冊BroadcastReceiver 
    
  • 靜態註冊:在AndroidManifest.xml中新增宣告:
    <receiver android:name=".MyReceiver">
       <intent-filter>
           <action android:name="android.intent.action.DATE_CHANGED"/>
           <category android:name="android.intent.category.HOME"/>
       </intent-filter>
    </receiver>
    

兩種註冊方式比較:

  • 動態註冊方式特點:在程式碼中進行註冊後,當應用程式關閉後,就不再進行監聽。
  • 靜態註冊方式的特點:在應用程式安裝之後,無論該應用程式是否處於活動狀態,BroadcastReceiver始終處於被監聽狀態。

動態註冊例項(監聽網路狀態變化)

  1. 自定義一個BroadcastReceiver,在onReceive()方法中完成廣播要處理的事務,比如使用Toast提示"網路狀態發生改變"。

    public class MyBRReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context,"網路狀態發生改變~",Toast.LENGTH_SHORT).show();
        }
    }
    
  2. MainActivity.java中動態註冊廣播:

    public class MainActivity extends AppCompatActivity {
    
        MyBRReceiver myReceiver;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //核心部分程式碼:
            myReceiver = new MyBRReceiver();
            IntentFilter itFilter = new IntentFilter();
            itFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
            registerReceiver(myReceiver, itFilter);
        }
    
        //別忘了將廣播取消掉哦~
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unregisterReceiver(myReceiver);
        }
    }
    

靜態註冊例項(接收開機廣播)

  1. 自定義一個BroadcastReceiver,重寫onReceive完成事務處理

    public class BootCompleteReceiver extends BroadcastReceiver {
        private final String ACTION_BOOT = "android.intent.action.BOOT_COMPLETED";
        @Override
        public void onReceive(Context context, Intent intent) {
        if (ACTION_BOOT.equals(intent.getAction()))
            Toast.makeText(context, "開機完畢~", Toast.LENGTH_LONG).show();
        }
    }
    
  2. 在AndroidManifest.xml中對該BroadcastReceiver進行註冊,新增開機廣播的intent-filter!別忘了加上android.permission.RECEIVE_BOOT_COMPLETED的許可權!

    <receiver android:name=".BootCompleteReceiver">
        <intent-filter>
            <action android:name = "android.intent.cation.BOOT_COMPLETED">
        </intent-filter>
    </receiver>
    
    <!-- 許可權 -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    

重啟下手機會發現過了一會兒,就會彈出開機完畢這個Toast。

使用廣播的注意事項

不要在廣播裡新增過多邏輯或者進行任何耗時操作,因為在廣播中是不允許開闢執行緒的, 當onReceiver( )方法執行較長時間(超過10秒)還沒有結束的話,那麼程式會報錯(ANR), 廣播更多的時候扮演的是一個開啟其他元件的角色,比如啟動Service,Notification提示, Activity等!

傳送廣播

傳送廣播的方式

  1. 標準廣播:用sendBroadcast和sendStickyBroadcast傳送廣播

    • 所有滿足條件的BroadcastReceiver都會執行其onReceive()方法來處理響應,是對廣播訊息進行過濾並響應的控制元件。
    • 當有多個滿足條件的BroadcastReceiver時,不能保證其onReceive()方法的執行順序。
  2. 有序廣播:用sendOrderBroadcast傳送

    • 通過sendOrderBroadcast傳送的Intent,會根據BroadcastReceiver註冊時IntentFilter設定的優先順序順序來執行onReceive()方法。優先順序範圍為-1000~1000。
    • 對於相同優先順序的BroadcastReceiver,不能保證其onReceive()方法的執行順序。

sendStickyBroadcast 與其它傳送方式的不同之處:Intent在傳送之後一直存在,並且在以後呼叫registerReceive註冊相匹配的Receive時會把這個Intent直接返回給新註冊的Receive。

可以呼叫abortBroadcast()截斷廣播的繼續傳遞。

標準廣播示例

  1. 自定義廣播子類來接收廣播

    public class MyBroadcastReceiver extends BroadcastReceiver {
        private final String ACTION_BOOT = "com.example.broadcasttest.MY_BROADCAST";
        @Override
        public void onReceive(Context context, Intent intent) {
            if(ACTION_BOOT.equals(intent.getAction()))
            Toast.makeText(context, "收到告白啦~",Toast.LENGTH_SHORT).show();
        }
    }
    
  2. 在AndroidManifest.xml中註冊下,寫上Intent-filter:

    <receiver android:name=".MyBroadcastReceiver">
        <intent-filter>
            <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
        </intent-filter>
    </receiver>
    
  3. 把上面這個程式專案執行下,然後關掉,接下來我們新建一個專案, 在這個專案裡完成廣播發送~新建Demo2,佈局就一個簡單按鈕,然後在MainActivity中完成廣播發送:

    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button btn_send = (Button) findViewById(R.id.btn_send);
            btn_send.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    sendBroadcast(new Intent("com.example.broadcasttest.MY_BROADCAST"));
                }
            });
        }
    }
    

LocalBroadcastManager本地廣播

前面寫的廣播都是全域性廣播!這意味著我們APP發出的廣播,其他APP都會接收到, 或者其他APP傳送的廣播,我們的APP也同樣會接收到,這樣容易引起一些安全性的問題!而 Android中給我們提供了本地廣播的機制,使用該機制發出的廣播只會在APP內部傳播,而且 廣播接收者也只能收到本應用發出的廣播!

使用流程

  1. 呼叫LocalBroadcastManager.getInstance()獲得例項;
  2. 呼叫LocalBroadcastManager.registerReceiver()註冊廣播;
  3. 呼叫LocalBroadcastManager.sendBroadcast()傳送廣播;
  4. 呼叫LocalBroadcastManager.unregisterReceiver()取消廣播。

注意事項

  1. 本地廣播無法通過靜態註冊方式來接受,相比起系統全域性廣播更加高效;
  2. 在廣播中啟動Activity的話,需要為intent加入FLAG_ACTIVITY_NEW_TASK的標記,不然會報錯,因為需要一個棧來存放新開啟的Activity;
  3. 廣播中彈出AlertDialog的話,需要設定對話方塊的型別為TYPE_SYSTEM_ALERT,不然是無法彈出的。

程式碼示例(別處登陸踢使用者下線)

像微信一樣,正在執行的微信,如果我們用別的手機再次登陸自己的賬號,前面這個是會提醒賬戶"在別的終端登入",然後把我們開啟的所有Activity都關掉,然後回到登陸頁面。

  1. 準備一個關閉所有Activity的ActivityCollector

    public class ActivityCollector {
        private static List<Activity> activities = new ArrayList<Activity>();
        public static void addActivity(Activity activity) {
            activities.add(activity);
        }
        public static void removeActivity(Activity activity) {
            activities.remove(activity);
        }
        public static void finishAll() {
            for (Activity activity : activities) {
                if (!activity.isFinishing()) {
                    activity.finish();
                }
            }
        }
    }
    
  2. 先寫要給簡單的BaseActivity,用來繼承,接著寫下登陸介面!

    public class BaseActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ActivityCollector.addActivity(this);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            ActivityCollector.removeActivity(this);
        }
    }
    

    LoginActivity.java:

    public class LoginActivity extends BaseActivity implements View.OnClickListener{
    
    
        private SharedPreferences pref;
        private SharedPreferences.Editor editor;
    
        private EditText edit_user;
        private EditText edit_pawd;
        private Button btn_login;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_login);
            pref = PreferenceManager.getDefaultSharedPreferences(this);
    
            bindViews();
        }
    
        private void bindViews() {
            edit_user = (EditText) findViewById(R.id.edit_user);
            edit_pawd = (EditText) findViewById(R.id.edit_pawd);
            btn_login = (Button) findViewById(R.id.btn_login);
            btn_login.setOnClickListener(this);
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            if(!pref.getString("user","").equals("")){
                edit_user.setText(pref.getString("user",""));
                edit_pawd.setText(pref.getString("pawd",""));
            }
        }
    
        @Override
        public void onClick(View v) {
            String user = edit_user.getText().toString();
            String pawd = edit_pawd.getText().toString();
            if(user.equals("123")&&pawd.equals("123")){
                editor = pref.edit();
                editor.putString("user", user);
                editor.putString("pawd", pawd);
                editor.commit();
                Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                startActivity(intent);
                Toast.makeText(LoginActivity.this,"喲,竟然蒙對了~",Toast.LENGTH_SHORT).show();
                finish();
            }else{
                Toast.makeText(LoginActivity.this,"這麼簡單都輸出,腦子呢?",Toast.LENGTH_SHORT).show();
            }
    
        }
    }
    
  3. 自定義一個BroadcastReceiver,在onReceive裡完成彈出對話方塊操作,以及啟動登陸頁面。

    public class MyBcReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(final Context context, Intent intent) {
            AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
            dialogBuilder.setTitle("警告:");
            dialogBuilder.setMessage("您的賬號在別處登入,請重新登陸~");
            dialogBuilder.setCancelable(false);
            dialogBuilder.setPositiveButton("確定",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            ActivityCollector.finishAll();
                            Intent intent = new Intent(context, LoginActivity.class);
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            context.startActivity(intent);
                        }
                    });
            AlertDialog alertDialog = dialogBuilder.create();
            alertDialog.getWindow().setType(
                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
            alertDialog.show();
        }
    }
    

    別忘了AndroidManifest.xml中加上系統對話方塊許可權: < uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

  4. 在MainActivity中,例項化localBroadcastManager,拿他完成相關操作,另外銷燬時 注意unregisterReceiver!

    public class MainActivity extends BaseActivity {
    
        private MyBcReceiver localReceiver;
        private LocalBroadcastManager localBroadcastManager;
        private IntentFilter intentFilter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            localBroadcastManager = LocalBroadcastManager.getInstance(this);
    
            //初始化廣播接收者,設定過濾器
            localReceiver = new MyBcReceiver();
            intentFilter = new IntentFilter();
            intentFilter.addAction("com.jay.mybcreceiver.LOGIN_OTHER");
            localBroadcastManager.registerReceiver(localReceiver, intentFilter);
    
            Button btn_send = (Button) findViewById(R.id.btn_send);
            btn_send.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent("com.jay.mybcreceiver.LOGIN_OTHER");
                    localBroadcastManager.sendBroadcast(intent);
                }
            });
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            localBroadcastManager.unregisterReceiver(localReceiver);
        }
    }
    

ContentProvider元件

ContentProvider是實現兩個程式間進行資料交換的元件。

Android程式中的資料(如:SharedPreferences、檔案資料和資料庫資料等)都是私有的。

當我們想允許自己的應用的資料允許別的應用進行讀取操作,可以讓我們的APP實現ContentProvider類,同時註冊一個Uri,然後其他應用只要使用ContentResolver根據Uri就可以操作我們的APP中的資料了。

ContentProvider執行原理

設定許可權

對ContentProvider中的資料進行操作,都需要在AndroidManifest.xml檔案中新增相應的許可權。

例如,對手機的通訊錄進行查詢和修改操作,則在AndroidManifest.xml檔案的<manifest>標籤內需要新增下列許可權設定:

…
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
…

ContentProvider和ContentResolver方法

ContentProvider類為程式提供一組標準的抽象介面,程式通過該介面暴露自己的資料。

ContentProvider方法

當外部應用程式需要對ContentProvider中的資料進行新增、刪除、修改和查詢操作時,可以使用ContentResolver 介面來完成。

ContentResolver提供的介面與ContentProvider中需要實現的介面對應。

獲取ContentResolver物件的方法:getContentResolver()

例:ContenteResolver cr=getContentResolver();

ContentResolver方法

Uri

在ContentProvider和ContentResolver中用到的Uri形式:

  • 指定全部資料。例:content://contacts/people/ —— 指定全部的聯絡人資料
  • 指定某個ID的資料。例:content://contacts/people/2 —— 指定ID為2的聯絡人資料

Uri類常用的操作方法

Uri類常用的操作方法

Uri的輔助操作類:ContentUris

由於所有的資料都要通過Uri進行傳遞,以增加操作為例,當用戶執行完增加資料操作後往往需要將增加後的資料ID通過Uri進行返回,當接收到這個Uri的時候就需要從裡面取出增加的ID,為了方便使用者這種取出資料的操作,在Android中又提供了一個android.content.ContentUris的輔助工具類,幫助使用者完成Uri的若干操作;

ContentUris類常用的操作方法

  • 從指定Uri之中取出ID:public static long parseId(Uri contentUri)
  • 在指定的Uri之後增加ID引數:public static Uri withAppendedId(Uri contentUri, long id)

Uri的輔助操作類:UriMatcher類

由於在使用ContentProvider類操作的時候,某一個方法都可能要傳遞多種Uri,例如:以query()方法為例,有可能表示查詢全部,有可能表示按ID查詢,所以必須對這些傳遞的Uri
進行判斷後才可以決定最終的操作形式,為了方便的使用者的判斷,專門提供了android.content.UriMatcher類,進行Uri的匹配;

UriMatcher類常用的操作方法:

UriMatcher類常用的操作方法

自定義ContentProvider流程解析

自定義ContentProvider流程解析

通過ContentObserver監聽ContentProvider的資料變化

通過ContentObserver監聽ContentProvider的資料變化