(一) Android 四大組件

Activity

1. 生命週期

正常狀況下:

  • 啓動 Activity:系統先調用onCreate(),而後調用onstart(),最後調用onResume(),Activity 就此進入運行狀態。
  • 退出 Activity:系統先調用onPause(),而後調用onStop(),最後調用onDestroy(),Activity 就此銷燬。
  • 當前設備鎖屏或者點擊 Home 鍵使程序進入後臺,依次執行onPause()onStop(), 從新回到 Activity,依次執行onRestart()onStart()onResume()(Activity 不被回收的狀況下)。
  • 當前 Activity 被 Dialog 主題的 Activity 覆蓋時,執行onPause(),回到前臺執行onResume()
  • 當 Activity 不在前臺或者不可見時被系統回收後,再次回到此 Activity ,系統會依次執行onCreate()onStart()onResume()
  • 啓動一個新 Activity,舊 Activity 會先執行onPause(),而後新 Activity 再啓動。
  • 按是否可見分類:onStart()onStop()
  • 按是否前臺分類:onResume()onPause

注意:當 Activity 彈出對話框時,並不會回調onPause,可是會回調onStop()android

異常狀況下:

當系統內存不足,或者系統配置發生改變(如旋轉方向),Activity 會被殺死。數據庫

  • 因爲是異常狀況下終止的,系統會調用onSaveInstanceState()來保存當前 Activity 的狀態,這個方法的調用時機是在onStop()以前,當 Activity 從新建立後,系統會把銷燬時保存的 Bundle 對象做爲參數傳遞給onCreate()onRestoreInstanceState(),建議在onRestoreInstanceState()中作數據恢復,畢竟專門用來恢復實例狀態的。另外,每一個 View 自己都有onSaveInstanceState()onRestoreInstanceState()方法,所以系統都會默認恢復 View 的基本狀態。
  • 防止從新建立 Activity:在 AndroidManifest.xml 中指定android:configChanges="orientation",彷佛有些設備須要多指定一個參數,即android:configChanges:="orientaion|screenSize"

2. 啓動模式

總共 4 種啓動模式:Standard,SingleTop,SingleTask,SingleInstance。安全

  • Standard:默認的啓動模式,在這種模式下,Activity 會進入啓動它的 Activity 所在的任務棧。
  • SingleTop:若是新 Activity 位於任務棧的棧頂時,Activity 不會被建立,而且它的onNewIntent()方法會被回調,其他生命週期方法均不會回調。
  • SingleTask:若是 Activity 在一個任務棧中存在,那麼屢次啓動此 Activity 都不會建立新實例,但系統會回調onNewIntent()。此外,位於此 Activity 之上的全部 Activity 均會出棧,此時 Activity 位於棧頂。
  • SingleInstance:這種模式下的 Activity 只能單獨存在於一個任務棧中,因爲棧內複用特性,此後的請求均不會建立新的實例。

注意:默認狀況下,全部 Activity 所需的任務棧的名字爲應用的包名,能夠在 AndroidManifest.xml 中經過android:taskAffinity=""來指定任務棧。bash

Service

被啓動的 Service 默認是在主線程下工做的,所以若是須要執行耗時操做,應當另開一個子線程來執行,以避免阻塞主線程致使出現 ANR(Application Not Response)。任何 Activity 均可以控制同一個 Service,而且系統中也只會存在一個 Service 實例。網絡

啓動模式

1. startService()

  • 普通模式:在這種模式下啓動 Service 後,即便 Activity 銷燬後,Service 也會在後臺繼續執行任務,直到在 Activity 中手動調用stopService()或者在在 Service 類中調用stopSelf(),服務纔會中止,Activity 沒法與 Service 進行通訊。
  • onCreate中能夠作一些初始化,onStartCommand()中放置執行任務的代碼,onDestroy()中進行資源的釋放。
  • 在這種方式下啓動 Service,生命週期是onCreate()->onStartCommand()->onDestroy(),當 Service 已經被啓動後,不管執行多少次startServvice(),都不會走onCreate(),而是會調用onStartCommand()

2. bindService()

  • 綁定模式:在這種模式下啓動 Service,當 Activity 銷燬後,Service 也會跟着銷燬,再也不使用 Service 時,調用unbindService()中止。這種模式下能夠進行 Activity 和 Service 的通訊。
  • 這這種方式下啓動 Service,生命週期是onCreate()->onBind()->onUnbind()->onDestroy()onStartCommand()不會有調用機會。

注意:當startService()bindService()一塊兒被調用後,若想中止服務,必須同時調用stopService()unbindService(),這樣服務纔會中止,順序沒有嚴格要求,但必定要同時調用。異步

示例ide

  • 建立一個 Service
public class MyService extends Service {

    private static final String TAG = "MyService";
    private IBinder mBinder;

    @Override
    public IBinder onBind(Intent intent) {
        if (mBinder == null) {
            mBinder = new MyBinder();
        }
        Log.d(TAG, "-----onBind()-----");
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "-----onUnbind()-----");
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "-----onCreate()-----");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "-----onDestroy()-----");
    }

    public class MyBinder extends Binder {

        // 這裏能夠定義想要通訊的方法,在 Activity 中能夠經過 Binder 實例調用
        public void print(String data) {
            Log.d(TAG, "print: " + data);
        }
    }
}
複製代碼
  • 在 AndroidManifest.xml 中註冊
<!--android:enabled 表示是否啓用,android:exported 表示是否向外界公開-->
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="false" />
複製代碼
  • 在 Activity 中定義 ServiceConnectioin 並進行綁定
public class MainActivity extends AppCompatActivity {

    private ServiceConnection mConnection;
    private MyService.MyBinder mBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mConnection = new MyConnection();
        initView();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }

    private void initView() {
        Button bindService = findViewById(R.id.btn3);
        Button unbindService = findViewById(R.id.btn4);
        bindService.setOnClickListener(view -> {
            bindService(new Intent(MainActivity.this, MyService.class), mConnection, BIND_AUTO_CREATE);
        });
        unbindService.setOnClickListener(view -> {
            unbindService(mConnection);
        });
    }

    private class MyConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 轉換爲本身定義的 Binder
            mBinder = (MyService.MyBinder) service;
            // 調用本身定義的方法進行通訊
            mBinder.print("成功通訊");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 這個方法只有在出現異常的時候纔會被系統調用
        }
    }
}
複製代碼

前臺服務

服務幾乎都是在後臺運行的,系統優先級相對比較低,當系統出現內存不足時就容易被回收,若是但願服務能夠一直保持運行狀態,不會由於內存不足而被回收,這時就可使用前臺服務。與普通服務不一樣,前臺服務會有一個正在運行的圖標在通知欄裏顯示,相似通知。例如騰訊手機管家等通知,會在通知欄顯示此時手機的內存狀態等。學習

示例:在 Service 中經過startForeground()建立前臺服務,而後在 Activity 中經過startService()bindService()開啓,經過stopService()unbindService()關閉。ui

public class MyService extends Service {

    private static final String TAG = "MyService";
    private IBinder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        createForegroundService();
    }
    
    private void createForegroundService() {
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification;
        // 使用了建造者模式,將須要定義到的部分提早設置好
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "Channel_1")
                .setContentTitle("前臺服務")
                .setContentText("This is content text")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setContentIntent(pi);
        // 由於 Android 8.0 添加了 NotificationChannel(通知渠道)
        // 所以須要適配,否則在 8.0 上會顯示不了通知
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            NotificationChannel channel = new NotificationChannel("Channel_1", "前臺服務", NotificationManager.IMPORTANCE_DEFAULT);
            manager.createNotificationChannel(channel);
            notification = builder.build();
        } else {
            notification = builder.build();
        }
        startForeground(1, notification);
    }
    ......
}    
複製代碼

IntentService

在前面就知道了,普通服務默認是運行在主線程中的,若是在服務裏執行一些耗時操做就容易出現 ANR,固然也能夠本身另開線程來執行,而後在合適的時機在 Service 內部調用stopSelf()來中止。可是這樣稍顯麻煩,爲了能夠簡單地建立一個異步的、會自動中止的 Service,Android 提供了 IntentService 類,很好地解決了這個問題。this

  • 經過建立一個 Service 繼承自 IntentService,並重寫onHandleIntent()方法便可,在這個方法中即可以處理耗時操做,而且當執行完畢後,Service 會自動中止。

示例

public class MyIntentService extends IntentService {

    public MyIntentService() {
        // 必須調用父類有參構造
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d("MyIntentService", "currentThread: " + Thread.currentThread().getName());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyIntentService", "-----onDestroy()-----");
    }
}
複製代碼
  • 當執行完畢後,Service 自動中止,詳情見 Logcat

遠程服務

因爲遠程服務我並不熟悉,這裏就先落下了,後面學習了再補充上去。

Broadcast

1. 廣播

標準廣播:

  • 是一種徹底異步執行的廣播,全部的廣播接收器之間幾乎沒有任何前後順序。這種廣播效率會比較高,但同時它是沒法截斷的。
  • sendBroadcast(intent)—— 表示發送標準廣播

有序廣播:

  • 是一種同步執行的廣播,廣播發出後,同一時刻只會有一個廣播接收器接收到這條廣播,這種廣播有前後順序,優先級高的接收器能夠先收到廣播,而且前面的接收器能夠截斷正在傳遞的廣播。
  • sendOrderedBroadcast(intent, null)—— 表示發送有序廣播
  • abortBroadcast()—— 表示截斷接收到的廣播

2. 接收器

注意: BroadcastReceiver 生命週期很短, 若是須要在onReceiver()完成一些耗時操做,應該考慮在 Service 中開啓一個新線程處理耗時操做,不該該在 BroadcastReceiver 中開啓一個新的線程,由於 BroadcastReceiver 生命週期很短,在執行完 onReceiver 之後就結束,若是開啓一個新的線程,可能出現 BroadcastRecevier 退出之後線程還在,而若是 BroadcastReceiver 所在的進程結束了,該線程就會被標記爲一個空線程,根據 Android 的內存管理策略,在系統內存緊張的時候,會按照優先級,結束優先級低的線程,而空線程無異是優先級最低的,這樣就可能致使 BroadcastReceiver 啓動的子線程不能執行完成。

動態註冊(監聽網絡變化):

  • 定義一個 Receiver 類繼承自 BroadcastReceiver,並重寫父類的 onReceive() 方法,調用 addAction() 添加 action 值,當網絡狀態發生變化時,系統發出的正式一條值爲 android.net.conn.CONNECTIVITY_CHANGE 的廣播,想要監聽什麼廣播便添加相應的 action 值。最後,動態註冊的接受器必定要取消註冊。
public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;
    private NetworkChangeReceiver networkChangeReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        networkChangeReceiver = new NetworkChangeReceiver();
        registerReceiver(networkChangeReceiver, intentFilter);
    }
    @Override
    protected void onDestroy(){
        super.onDestroy();
        unregisterReceiver(networkChangeReceiver);
    }
    class NetworkChangeReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent){
            ConnectivityManager manager = (ConnectivityManager)
                    getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = manager.getActiveNetworkInfo();
            if(networkInfo != null && networkInfo.isAvailable()){
                Toast.makeText(context, "網絡沒毛病", Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(context, "網絡不可用", Toast.LENGTH_SHORT).show();
            }
        }
    }
}
複製代碼
  • onReceive() 方法中,經過 getSystemService() 方法獲得了 ConnectivityManager 的實例,這是一個系統服務類,專門用於管理網絡鏈接。而後調用它的 getActiveNetworkInfo() 方法能夠獲得 NetworkInfo 的實例,接着調用 NetworkInfo 的 isAvailable() 方法便可判斷當前是否有網絡。

  • 最後須要在 AndroidManifest.xml 中註冊訪問系統網絡狀態權限。

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

靜態註冊(監聽系統開機):

  • 使用 Android Studio 提供的快捷方式新建一個廣播接收器,Exported 屬性表示是否容許這個接收器接收本程序之外的廣播,Enabled 屬性表示是否啓用這個接收器。
  • AndroidManifest.xml 中 Android Studio 已經自動註冊好了靜態的廣播接收器,因爲須要監聽開機廣播,所以須要聲明接收開機廣播權限,而且 intent-filter 標籤也須要添加相應的 action 值。
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<receiver
    android:name=".BootCompleteReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>
複製代碼

靜態註冊與動態註冊的區別

  • 動態註冊廣播不是常駐型廣播,也就是說廣播跟隨 Activity 的生命週期。須要在 Activity 結束前,移除廣播接收器。 靜態註冊是常駐型,也就是說當應用程序關閉後,若是有信息廣播來,程序也會被系統調用自動運行。
  • 當廣播爲有序廣播時:
  1. 優先級高的先接收
  2. 同優先級的廣播接收器,動態優先於靜態
  3. 同優先級的同類廣播接收器,靜態:先掃描的優先於後掃描的,動態:先註冊的優先於後註冊的。
  • 當廣播爲普通廣播時:
  1. 無視優先級,動態廣播接收器優先於靜態廣播接收器
  2. 同優先級的同類廣播接收器,靜態:先掃描的優先於後掃描的,動態:先註冊的優先於後註冊的。

3. 本地廣播

本地廣播用了一個 LocalBroadcastManager 來對廣播進行管理,並提供了發送廣播和註冊廣播接收器的方法。另外,本地廣播是沒法經過靜態註冊的方式來接收的

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;
    private LocalReceiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = 
                        new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent);
            }
        });
        intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
        localReceiver = new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver, intentFilter);
    }
    @Override
    protected void onDestroy(){
        super.onDestroy();
        localBroadcastManager.unregisterReceiver(localReceiver);
    }
    class LocalReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent){
            // 編寫邏輯操做
        }
    }
}
複製代碼

ContentProvider

ContentProvider 是 Android 四大組件之一的內容提供器,它主要的做用就是將程序的內部的數據和外部進行共享,爲數據提供外部訪問接口,被訪問的數據主要以數據庫的形式存在,並且還能夠選擇共享哪一部分的數據。這樣一來,對於程序當中的隱私數據能夠不共享,從而更加安全。ContentProvider 是 Android 中一種跨程序共享數據的重要組件。

1. 使用系統的 ContentProvider

系統的 ContentProvider 有不少,如通話記錄,短信,通信錄等等,都須要和第三方的 App 進行共享數據。既然是使用系統的,那麼 ContentProvider 的具體實現就不須要咱們擔憂了,使用內容提供者的步驟以下:

  • 獲取 ContentResolver 實例
  • 肯定 Uri 的內容,並解析爲具體的 Uri 實例
  • 經過 ContentResolver 實例來調用相應的方法,傳遞相應的參數,可是第一個參數老是 Uri,它制定了咱們要操做的數據的具體地址

能夠經過讀取系統通信錄的聯繫人信息,顯示在Listview中來實踐這些知識。不要忘記在讀取通信錄的時候,在清單文件中要加入相應的讀取權限。

2. 自定義 ContentProvider

系統的 ContentProvider 在與咱們交互的時候,只接受了一個 Uri 的參數,而後根據咱們的操做返回給咱們結果。系統究竟是如何根據一個 Uri 就可以提供給咱們準確的結果呢?只有本身親自實現一個看看了。和以前提到的同樣,想從新自定義本身程序中的四大組件,就必須從新實現一個類,重寫這個類中的抽象方法,在清單文件中註冊,最後纔可以正常使用。

從新實現 ContentProvider 以後,發現咱們重寫了 6 個重要的抽象方法

  • onCreate()
  • query()
  • update()
  • insert()
  • delete()
  • getType()

大部分的方法在數據庫那裏已經見過了,他們內部的邏輯可想而知都是對數據的增刪改查操做,其中這些方法的第一個參數大多都是 Uri 實例。其中有兩個方法比較特殊:

onCreate()方法應該是內容提供者建立的時候所執行的一個回調方法,負責數據庫的建立和更新操做。這個方法只有咱們在程序中獲取 ContentResolver 實例以後準備訪問共享數據的時候,纔會被執行。

getType()方法是獲取咱們經過參數傳遞進去的 Uri 的 MIME 類型,這個類型是什麼,後面會有實例說明。

內容提供者首先要作的一個事情就是將咱們傳遞過來的 Uri 解析出來,肯定其餘程序到底想訪問哪些數據。Uri 的形式通常有兩種:

  1. 以路徑名爲結尾,這種 Uri 請求的是整個表的數據,如: content://com.demo.androiddemo.provider/table1標識咱們要訪問 table1 表中全部的數據。
  2. 以 id 列值結尾,這種Uri請求的是該表中和其提供的列值相等的單條數據。content://com.demo.androiddemo.provider/table1/1 標識咱們要訪問 table1 表中 _id 列值爲 1 的數據。

若是是內容提供器的設計者,那麼咱們確定知道這個程序的數據庫是什麼樣的,每一張表,或者每一張表中的 _id 都應該有一個惟一的內容 Uri 。咱們能夠將傳遞進來的 Uri 和咱們存好的 Uri 進行匹配,匹配到了以後,就說明數據源已經找到,即可以進行相應的增刪改查操做。

最後

四大組件的學習能夠參考第一行代碼裏面的知識點,都是比較基礎的東西,本身碼出來練習練習便可,不必死記,須要用到時腦殼裏有這麼個印象而後知道哪裏能夠查就好了,長此以往天然而然就記住了。

相關文章
相關標籤/搜索