再看Android基礎之溫故知新

目錄

一:Activity

1.1 Intent

1.1.1 顯示Intent

1.1.3 隱式Intent

1.2 傳遞數據

1.3 返回數據給上一個活動

1.3 生命週期

1.3.1 返回棧

1.4 活動的啓動模式

1.4.1 standard

1.4.2 singleTop

1.4.3 singleTask

1.4.4 singleInstance

1.5 Android活動管理

 二:Fragment

2.1 基本介紹

2.2 動態添加碎片

2.3 碎片中模擬返回棧

2.4 Fragment和Activity的通信

2.4.1 Activity調Fragment中方法

2.4.2 Fragment調Activity中方法

2.5 Fragment生命週期

2.6 動態加載佈局技巧

2.6.1 限定符

2.6.2 最小寬度限定符

三  運行時權限

四  廣播

4.1 廣播分類

4.1.1 標準廣播

4.1.2 有序廣播

4.2 接受系統廣播 

4.2.1 動態註冊(代碼中註冊)

4.2.2 靜態註冊(AndroidManifest.xml) 

4.3 發送自定義廣播

4.3.1 發送標準廣播

4.3.2 發送有序廣播

4.4 發送本地廣播 

五 ContentProvier 

5.1 ContentResolver訪問其他程序中的數據

5.1.1 查

5.1.2 增

5.1.3 改

 5.1.4 刪

5.2 ContentProvider內容提供器

5.2.1 重寫方法:

5.2.2 

5.3 總結:

六:Android線程和異步消息處理機制

6.1 Android線程

6.2 Android異步消息處理機制

6.3 AsyncTask 

6.3.1 AsyncTask 的基本用法:

6.3.2 啓動任務

七 通知

7.1 通知基本用法

7.1.1 創建通知NotificationManager對象

7.1.2 構造通知Notification對象

7.1.3 顯示通知

7.1.4 通知點擊效果

7.1.5 取消通知

7.2 通知進階用法

7.2.1 通知發出時播放音頻/震動

7.2.2 通知未被看時(如鎖屏狀態)LED提示

7.3通知高級功能

7.3.1 setStyle()方法

7.3.2 setPriority()方法

八 服務

8.1 基本用法

8.1.1 定義和開關服務

8.2 服務和活動的通信

8.3 服務的生命週期

8.4 服務高級技巧之前臺服務

8.5 IntentService


一:Activity

1.1 Intent

1.1.1 顯示Intent

Intent intent = new Intent(this, SecondActivity.class);  
startActivity(intent); 

1.1.3 隱式Intent

首先在activity的註冊文件中的<intent-filter>中添加:

        <activity android:name=".ThirdActivity">
            <intent-filter>
                <action android:name="com.example.activitytest.ACTION_START" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="com.example.activitytest.MY_CATEGORY" />
            </intent-filter>
        </activity>
 Intent intent = new Intent("com.example.administrator.activitytest.ACTION_START");
 startActivity(intent);

另外隱式intent中還可以通過傳入Intent.ACTION_VIEW等參數進行打電話、打開瀏覽器網址等操作;

打開百度:

intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);

打電話:

Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);

1.2 傳遞數據

發送數據:

public void onClick(View v) {
                String data = "你好啊,小娜";
                Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
                //傳遞字符串data
                intent.putExtra("my_data",data);
                startActivity(intent);
            }

接收數據:

Intent intent = getIntent();
        //接收字符串數據(getXxxExtra("key"))
        String data = intent.getStringExtra("my_data");
        //彈出消息提示
        Toast.makeText(this,data,Toast.LENGTH_SHORT).show();

1.3 返回數據給上一個活動

示例:SecondActivity   -->   ThirdActivity    -->  SecondActivity:

//SecondActivity點擊事件
btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SecondActivity.this,ThirdActivity.class);
                startActivityForResult(intent,1);
            }
        });
 //ThirdActivity點擊返回
        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.putExtra("data_return", "CMCC");
                setResult(RESULT_OK, intent);
                finish();
            }
        });  
//SecongActivity中重寫onActivityResult方法
     protected void onActivityResult(int requestCode,int resultCode,Intent data){
        switch (requestCode){
            case 1:
                if(resultCode == RESULT_OK){
                    String returnedData = data.getStringExtra("data_return");
                    Toast.makeText(this,returnedData,Toast.LENGTH_SHORT).show();
                }
                break;
            default:
        }
    }

*注:上面這種方式當用戶通過Back返回鍵回到SecondActivity時會失效,我們可以通過重寫onBackPressed()方法解決這個問題。

1.3 生命週期

1.3.1 返回棧

        Android的活動是可以層疊的。每啓動一個新活動,就會覆蓋在原有活動之上,然後點擊Back鍵會銷燬最上面的活動,下面的一個活動就會重新顯示出來。Android是使用任務(Task)來管理活動的,一個任務就是一組存放在棧裏的活動的集合,這個棧也被稱爲返回棧(Back Stack)。在默認情況下,每當我們啓動了一個新的活動,它會在返回棧中入棧,並處於棧頂的位置。而每當我們按下Back鍵或調用finish()方法去銷燬一個活動時,處於頂棧的活動就會出棧,這時前一個入棧的活動就會重新處於棧頂的位置。系統總是會顯示處於棧頂的活動給用戶。

Activity類的七個回調方法,覆蓋了活動生命週期的每一個環節。

1.onCreate()————————初始化操作,活動第一次被創建時調用

2.onStart()—————————啓動操作,活動由不可見變爲可見時調用

3.onResume()————————交互操作,活動準備與用戶交互時調用

4.onPause()—————————暫停操作,系統準備啓動或恢復另一個活動時調用

5.onStop()——————————停止操作,活動完全不可見的時候調用

6.onDestroy()————————回收操作,活動被銷燬之前調用

7.onRestart()————————重啓操作,活動由停止變爲運行狀態之前調用

以上方法除了onRestart()方法,其他都是對應的,從而又可以將活動分爲三種生存週期

完整生存期:

  活動onCreate()方法和onDestroy()方法之間所經歷的,就是完整生存週期。一般情況下,一個活動會在onCreate()方法中完成各種初始化操作,而在onDestroy()方法中完成釋放內存的操作。

可見生存期:

  活動在onStart()方法和onStop()方法之間所經歷的,就是可見生存週期。在可見生存期內,活動對於用戶總是可見的,即便有可能無法和用戶進行交互。可以通過這兩種方法,合理的管理那些對用戶可見的資源。

前臺生存期:

  活動在onResume()方法和onPause()方法之間所經歷的,就是前臺生存週期。在前臺生存期內,活動總是處於運行狀態的,此時的活動是可以和用戶進行交互的,和用戶接觸最多的也就是這個狀態下的活動了。


1.4 活動的啓動模式

1.4.1 standard

       standard是活動的默認啓動模。在standard模式下,每當啓動一個新的活動,它就會在返回棧中入棧,並處於棧頂的位置。對於使用standard模式的活動,系統不會在乎這個活動是否在返回棧中存在,每次啓動都會創建該活動的一個新的實例。

1.4.2 singleTop

       在啓動活動的時候如果發現返回棧的棧頂已經是該活動,則認爲可以直接使用它,不會再創建新的活動實例。

1.4.3 singleTask

       每次啓動該活動時系統首先會在返回棧中檢查是否存在該活動的實例,如果發現就直接調用,並把這個活動之上的所有活動出棧。沒有發現就重新創建。

1.4.4 singleInstance

       下圖啓動次序:FirstActivity->SecondActivity->ThirdActivity;但是SecondActivity存在在第二個返回棧中;因此,當在ThirdActivity中點擊返回時,會返回到本返回棧中的FirstActivity中,當本返回棧全部返回完,纔會返回到第二個返回棧中。


1.5 Android活動管理

ActivityCollector.java

import android.app.Activity;
import java.util.ArrayList;
import java.util.List;

//活動彙總管理
public class ActivityCollector {
    public static List<Activity> activities = new ArrayList<Activity>();

    //添加活動
    public static void addActivity(Activity activity) {
        if (!activities.contains(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();
            }
        }
    }

}

BaseActivity.java

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

//重寫Activity基本類
public class BaseActivity extends Activity {
    @Override

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //顯示父類名稱
        Log.d("woider", getClass().getSimpleName());
        //添加至活動中心
        ActivityCollector.addActivity(this);
    }
    protected void onDestroy(){
        super.onDestroy();
        //從活動中心移除
        ActivityCollector.removeActivity(this);
    }

}

 二:Fragment

 

2.1 基本介紹

       碎片(Fragment)是一種可以嵌入活動當中的UI片段,可以合理充分地利用大屏幕,因此在平板上的應用廣泛。可以將碎片理解成一個迷你型的活動。選擇support-v4中的Fragment以達到最大兼容。

public class LeftFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        //return super.onCreateView(inflater, container, savedInstanceState);
        return inflater.inflate(R.layout.left_fragment, container, false);
    }
}

2.2 動態添加碎片

//實例化動態碎片
AnotherRightFragment fragment = new AnotherRightFragment();
//獲取FragmentManger
FragmentManager fragmentManager = getFragmentManager();
//開啓一個事務,通過調用beginTransaction開啓
FragmentTransaction transaction = fragmentManager.beginTransaction();
//更改FrameLayout中的碎片,並提交
transaction.replace(R.id.right_layout, fragment);
transaction.commit();        

動態添加碎片主要分爲五步:

  1.  創建待添加的碎片實例。

  2.  獲取到 FragmentManager,在活動中可以直接調用getFragmentManager()方法得到。

  3.  開啓一個事務,通過調用beginTransaction()方法開啓。

  4.  向容器內加入碎片,一般使用replace()方法實現,需要傳入容器的id和待添加的碎片實例。

  5.  提交事務,調用 commit()方法來完成。

2.3 碎片中模擬返回棧

AnotherRightFragment fragment = new AnotherRightFragment();
FragmentManager fragmentManager = getFragmentManager();
//開啓一個事務,通過調用beginTransaction開啓
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, fragment);
//addToBackStack()方法用於將一個事務添加到返回棧中
transaction.addToBackStack(null);
transaction.commit();

       關鍵代碼:

transaction.addToBackStack(null);

       按下Back鍵,程序不會退出,而是回到了RightFragment界面,再次按下Back鍵程序纔會退出。


2.4 Fragment和Activity的通信

2.4.1 Activity調Fragment中方法

        FragmentManager 提供了一個類似於 findViewById()的方法,專門用於從佈局文件中獲取碎片的實例,得到相應碎片的實例後就能輕鬆地調用碎片裏的方法了。

RightFragment  rightFragment = (RightFragment) getFragmentManager().findFragmentById( R.id.right_fragment );

2.4.2 Fragment調Activity中方法

        碎片中都可以通過調用getActivity()方法來得到和當前碎片相關聯的活動實例:

MainActivity activity = (MainActivity) getActivity();

2.5 Fragment生命週期

運行狀態:當碎片可見,且它所關聯的活動正處於運行狀態時,該碎片也處於運行狀態。

暫停狀態:當活動進入暫停狀態時(由於另一個未佔屏幕的活動被添加到了棧頂),與它關聯的可見碎片就會進入到暫停狀態。

停止狀態:當活動進入停止狀態時,與它關聯的碎片就會進入到停止狀態。(或者通過FragmentTransaction 的 remove()、replace()方法將碎片從活動中移除),停止狀態的碎片對用戶來說完全不可見,有可能會被系統回收。

銷燬狀態:碎片總是依附於活動而存在的,因此當活動被銷燬時,與它相關聯的碎片就會進入到銷燬狀態。

 

同樣,Fragment類中也提供了一系列的回調方法,以覆蓋碎片什麼週期的每個環節。

onAttach() 當碎片和活動建立關聯的時候調用
onCreateView() 爲碎片創建視圖(加載佈局)時調用
onActivityCreated() 確保與碎片相關的活動已經創建完畢的時候調用
onDestroyView() 當與碎片關聯的視圖被移除的時候調用
onDetach() 當碎片和活動解除關聯的時候調用

2.6 動態加載佈局技巧

2.6.1 限定符

         在res目錄下新建layout-large文件夾,在此文件夾下創建佈局;以此,在layout/activity_main中的編寫普通手機上的佈局(比如單頁模式),在layout-large/activity_main中編寫平板上的佈局(比如雙頁模式)。

         一些常見的限定符可見:https://blog.csdn.net/jinmie0193/article/details/81813945

2.6.2 最小寬度限定符

       指定large具體大小:在res目錄下新建layout-sw600dp文件夾,在此文件夾下新建activity_main.xml佈局;此時,當程序運行在屏幕寬度大於600dp的屏幕上時,會加載layout-sw600dp/activity_mainn佈局,當程序運行在屏幕寬度小於600dp的設備上時,默認加載layout/activity_main佈局。


三  運行時權限

Android 運行時權限終極總結


四  廣播

4.1 廣播分類

4.1.1 標準廣播

        異步;廣播接收器幾乎同時收到;效率高;無法截斷;

4.1.2 有序廣播

        同步;同一時刻只有一個廣播接收器收到;優先級高的廣播先收到並且可以截斷讓後面的廣播收不到;


4.2 接受系統廣播 

       Android中內置了多個系統廣播:只要涉及到手機的基本操作(如開機、網絡狀態變化、拍照等等),都會發出相應的廣波,每個廣播都有特定的Intent - Filter(包括具體的action),android常用系統廣播action如下:

Operation     action
監聽網絡變化     android.net.conn.CONNECTIVITY_CHANGE
關閉或打開飛行模式     Intent.ACTION_AIRPLANE_MODE_CHANGED
充電時或電量發生變化     Intent.ACTION_BATTERY_CHANGED
電池電量低     Intent.ACTION_BATTERY_LOW
電池電量充足(即從電量低變化到飽滿時會發出廣播    Intent.ACTION_BATTERY_OKAY
系統啓動完成後(僅廣播一次)     Intent.ACTION_BOOT_COMPLETED
按下照相時的拍照按鍵(硬件按鍵)時    Intent.ACTION_CAMERA_BUTTON
屏幕鎖屏     Intent.ACTION_CLOSE_SYSTEM_DIALOGS
設備當前設置被改變時(界面語言、設備方向等)     Intent.ACTION_CONFIGURATION_CHANGED
插入耳機時     Intent.ACTION_HEADSET_PLUG
未正確移除SD卡但已取出來時(正確移除方法:設置–SD卡和設備內存–卸載SD卡)     Intent.ACTION_MEDIA_BAD_REMOVAL
插入外部儲存裝置(如SD卡)     Intent.ACTION_MEDIA_CHECKING
成功安裝APK     Intent.ACTION_PACKAGE_ADDED
成功刪除APK     Intent.ACTION_PACKAGE_REMOVED
重啓設備     Intent.ACTION_REBOOT
屏幕被關閉    Intent.ACTION_SCREEN_OFF
屏幕被打開     Intent.ACTION_SCREEN_ON
關閉系統時    Intent.ACTION_SHUTDOWN
重啓設備     Intent.ACTION_REBOOT

4.2.1 動態註冊(代碼中註冊)

先繼承BroadCastReceiver():

//自定義內部類,繼承自BroadcastReceiver
public class NetworkChangeReceiver extends BroadcastReceiver {
   @Override
   public void onReceive(Context context, Intent intent) {//connectivityManger是一個系統服務類,專門用於管理網絡連接
      ConnectivityManager connectivityManager = (ConnectivityManager) 
      getSystemService(Context.CONNECTIVITY_SERVICE);
      NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
      //調用NetworkInfo的isAvailable()方法判斷是否聯網
      if(networkInfo != null && networkInfo.isAvailable()){
          Toast.makeText(context,"網絡已連接",Toast.LENGTH_SHORT).show();
      }else{
          Toast.makeText(context,"網絡不可用",Toast.LENGTH_SHORT).show();
      }
    }
}

*注意: 這邊在網路狀態變化時告訴了用戶此時是有網還是沒網,所以要加上Manifest權限:

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

一般在onCreate()/onResume()方法中註冊:

intentFilter = new IntentFilter();
//爲過濾器添加處理規則
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
//註冊廣播接收器
registerReceiver(networkChangeReceiver, intentFilter);

onCreate ()註冊的,onDestory()中取消註冊;onResume()中註冊的,onPause()中取消註冊():

//動態的廣播接收器最後一定要取消註冊
unregisterReceiver(networkChangeReceiver);

4.2.2 靜態註冊(AndroidManifest.xml) 

靜態註冊可以讓程序在未啓動的情況下就能接收到廣播。比如開機啓動:

新建一個 BootCompleteReceiver 繼承自 BroadcastReceiver,代碼如下所示:

public class BootCompleteReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"程序已啓動",Toast.LENGTH_SHORT).show();
    }
}

在 AndroidManifest 中將廣播接收器的類名傳遞進去:

<receiver android:name=".BootCompleteReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
</receiver>

再加上權限:

 <!--聲明監聽系統開機廣播權限-->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

注意:不要再 onReceive() 方法中添加過多的邏輯或者進行任何耗時的操作,當onReceive()方法運行了較長時間而沒有結束時,程序就會報錯。廣播接收器更多是扮演打開其他組件的角色,比如創建一條狀態欄通知,或者啓動一個服務。


4.3 發送自定義廣播

4.3.1 發送標準廣播

先繼承BroadcastReceiver類:

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"Woider 已經收到信息",Toast.LENGTH_SHORT).show();
    }
}

註冊:

 <receiver android:name=".MyBroadcastReceiver">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BROADCAST" />
            </intent-filter>
</receiver>

啓動發送我的自定義標準廣播:

//把要發送的廣播值傳入Intent對象
 Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
//調用Context的 sendBroadcast()方法發送廣播
sendBroadcast(intent);

4.3.2 發送有序廣播

在標準廣播上,如果需要發送有序廣播,只需要修改一行代碼: sendBroadcast()方法改成 sendOrderedBroadcast()方法:

//把要發送的廣播值傳入Intent對象
 Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
//調用Context的 sendBroadcast()方法發送廣播
sendOrderdBroadcast(intent,null);

有序廣播的優先級在註冊的時候指定:

 <receiver android:name=".MyBroadcastReceiver">
            <intent-filter android:priority="100">
                <action android:name="com.example.broadcasttest.MY_BROADCAST" />
            </intent-filter>
</receiver>

如要設置截斷,在BroadCastReceiver的onReceive()方法中調用abortBroadcast()方法:

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"Woider 已經收到信息",Toast.LENGTH_SHORT).show();
        abortBroadcast();
    }
}

4.4 發送本地廣播 

       系統廣播可以被其他任何程序接收到,不安全。比如發送關鍵數據的廣播有可能被其他的應用程序截獲,或者其他的程序不停地向我們的廣播接收器裏發送各種垃圾廣播。因此Android 引入本地廣播機制,這個機制發出的廣播只在應用程序的內傳遞,廣播接收器也只接收本應用程序發出的廣播,解決了安全問題。

        使用上和之前的動態註冊廣播接收器和發送廣播一樣,只是註冊時用LocalBroadcastManage的registerReceiver()方法;發廣播時用ocalBroadcastManage的sendBroadcast()方法;首先通過LocalBroadcastManager 的 getInstance() 方法得到它的一個實例:

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);
        //獲取LocalBroadcastManger實例
        localBroadcastManager = localBroadcastManager.getInstance(this);

        TextView textView = (TextView)findViewById(R.id.broadcast);
        textView.setText("廣播地址:\ncom.example.broadcasttest.LOCAL_BROADCAST");
        Button 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");
                intent.putExtra("woider","青春的逝去並不可怕,可怕的是失去了勇敢地熱愛生活的心");
                //發送本地廣播
                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) {
            String data = intent.getStringExtra("woider");
            Toast.makeText(context,data,Toast.LENGTH_SHORT).show();
        }
    }
}

*注意:本地廣播無法通過靜態註冊來接收(靜態註冊服務於程序未啓動時收到廣播,發本地廣播程序肯定啓動了)

本地廣播的幾點優勢:

1.  可以明確地知道正在發送的廣播不會離開我們的程序,因此不需要擔心機密數據泄露的問題。

2.  其他的程序無法將廣播發送到我們的程序內部,因此不需要擔心會有安全漏洞的隱患。

3.  發送本地廣播比起發送系統全局廣播將會更加高效。


五 ContentProvier 

5.1 ContentResolver訪問其他程序中的數據

       想要訪問內容提供器中共享的數據,就一定要藉助 ContentResolver 類,可以通過 Context 中的 getContentResolver()方法獲取到該類的實例。

  ContentResolver 中提供了一系列方法用於對數據進行增:insert() 方法,刪:delete()方法,改:update()方法,查:query(),操作方式與 SQLiteDatabase 類似,只不過它們在方法參數上稍微有一些區別。

  不同於 SQLiteDatabase,ContentResolver 中的增刪改查方法都是不接受表名參數的,而是使用一個URI參數代替,這個參數被稱爲內容 URI。內容 URI 給內容提供器中的數據建立了唯一標識符,它主要由兩部分組成,權限(authority)和路徑(path)。權限是用於對不同的應用程序做區分的,一般爲了避免衝突,都會採用程序包名的方式來進行命名。路徑則是用於對同一應用程序中不同的表做區分的,通常都會添加到權限的後面。

  例如某個程序的包名是 com.example.app,那麼該程序對應的權限就可以命名爲 com.example.app.provider。而該程序的數據庫裏存在兩張表,table1和table2,這時就可以將路徑分別命名爲/table1和/table2,然後把權限和路徑進行組合,內容 URI 就變成了 com.example.app.provider/table1 和 com.example.app.provider/table2,最後還需要在字符串的頭部加上協議聲明。因此,內容 URI 最標準的格式寫法如下:     

content://com.example.app.provider/table1
content://com.example.app.provider/table2

  內容 URI 可以非常清楚的表達出我們想要訪問哪個程序中哪張表裏的數據。也正是如此,ContentResolver 中的增刪改查方法才都接收 Uri 對象作爲參數。在得到內容URI字符串之後,還需要將它解析成 Uri 對象纔可以作爲參數傳入。只需要調用 Uri.parse() 方法,就可以將內容 URI 字符串解析成 Uri 對象了。    

Uri uri = Uri.parse("content://com.example.app.provider/table1")
//Cursor c = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
Cursor c = getContentResolver().query(uri, null, null, null, null);

ContentProvider向外界提供數據操作的接口:

query(Uri, String[], String, String[], String)
insert(Uri, ContentValues)
update(Uri, ContentValues, String, String[])
delete(Uri, String, String[])

一個查通訊錄的例子:

private void readContacts() {
        Cursor cursor = null;
        try {
            // 查詢聯繫人數據
            cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    // 獲取聯繫人姓名
                    String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    // 獲取聯繫人手機號
                    String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                    contactsList.add(displayName + "\n" + number);
                }
                adapter.notifyDataSetChanged();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

 


5.1.1 查

  現在我們就可以使用上述的 Uri 對象來查詢 table1 表中的數據了,代碼如下所示:

     Cursor cursor = getContentResolver().query( uri , projection , selection , selectionArgs , sortOrder );

  這些參數和 SQLiteDatabase 中 query()方法裏的參數很像,但總體來說要簡單一些,畢竟這是在訪問其他程序中的數據,沒必要構建過於複雜的查詢語句。

query() 方法參數描述:

uri 指定查詢某個應用程序下的某一張表,對應 from table_name
projection 指定查詢的列名,對應 select column1,column2
selection 指定 where 的約束條件,對應 where column = value
selectionArgs 爲 where 中的佔位符提供具體的值
orderBy 指定查詢結果的排序方式,對應 order by column

  查詢完成後返回的仍然是一個 Cursor 對象,這時我們就可以將數據從 Cursor 對象中逐個讀取出來。讀取的思路仍然是通過移動遊標的位置來遍歷 Cursor 的所有行,然後取出每一行相對應的數據。

if (cursor != null) {
while (cursor.moveToNext()) {
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}

5.1.2 增

ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);

5.1.3 改

ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[] {"text", "1"});

 5.1.4 刪

getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });

5.2 ContentProvider內容提供器

5.2.1 重寫方法:

        如果想實現跨程序共享數據的功能,官方推薦的方式就是使用內容提供器,可以通過新建一個類去繼承 ContentResolver 的方式來創建一個自己的內容提供器。

         ContentProvider 類中有六個抽象方法,使用子類繼承它的時候,需要將這六個方法全部重寫。

onCreate():初始化內容提供器的時候調用。通常會在這裏完成對數據庫的創建和升級等操作,返回 true 表示內容提供器初始化成功,返回 false 則表示失敗。注意,只有當存在 ContentResolver 嘗試訪問我們程序中的數據時,內容提供器纔會被初始化。


query():從內容提供器中查詢數據。使用 uri 參數來確定查詢哪張表,projection 參數用於確定查詢哪些列,selection 和 selectionArgs 參數用於約束查詢哪些行,sortOrder 參數用於對結果進行排序,查詢的結果存放在 Cursor 對象中返回。


insert():向內容提供器中添加一條數據。使用 uri 參數來確定要添加到的表,待添加的數據保存在 values 參數中。添加完成後,返回一個用於表示這條新記錄的 URI。


update():更新內容提供器中已有的數據。使用 uri 參數來確定更新哪一張表中的數據,新數據保存在 values 參數中,selection 和 selectionArgs 參數用於約束更新哪些行,受影響的行數將作爲返回值返回。


delete():從內容提供器中刪除數據。使用 uri 參數來確定刪除哪一張表中的數據,selection 和 selectionArgs 參數用於約束刪除哪些行,被刪除的行數將作爲返回值返回。


getType():根據傳入的內容 URI 來返回相應的 MIME 類型。

public class DatabaseProvider extends ContentProvider {

    public static final int BOOK_DIR = 0;

    public static final int BOOK_ITEM = 1;

    public static final int CATEGORY_DIR = 2;

    public static final int CATEGORY_ITEM = 3;

    public static final String AUTHORITY = "com.example.databasetest.provider";

    private static UriMatcher uriMatcher;

    private MyDatabaseHelper dbHelper;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }

    @Override
    public boolean onCreate() {
        dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        // 查詢數據
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                cursor = db.query("Book", projection, "id = ?", new String[] { bookId }, null, null, sortOrder);
                break;
            case CATEGORY_DIR:
                cursor = db.query("Category", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                cursor = db.query("Category", projection, "id = ?", new String[] { categoryId }, null, null, sortOrder);
                break;
            default:
                break;
        }
        return cursor;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // 添加數據
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
            case BOOK_ITEM:
                long newBookId = db.insert("Book", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
                break;
            case CATEGORY_DIR:
            case CATEGORY_ITEM:
                long newCategoryId = db.insert("Category", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId);
                break;
            default:
                break;
        }
        return uriReturn;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        // 更新數據
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updatedRows = 0;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                updatedRows = db.update("Book", values, selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updatedRows = db.update("Book", values, "id = ?", new String[] { bookId });
                break;
            case CATEGORY_DIR:
                updatedRows = db.update("Category", values, selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updatedRows = db.update("Category", values, "id = ?", new String[] { categoryId });
                break;
            default:
                break;
        }
        return updatedRows;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // 刪除數據
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deletedRows = 0;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                deletedRows = db.delete("Book", selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                deletedRows = db.delete("Book", "id = ?", new String[] { bookId });
                break;
            case CATEGORY_DIR:
                deletedRows = db.delete("Category", selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                deletedRows = db.delete("Category", "id = ?", new String[] { categoryId });
                break;
            default:
                break;
        }
        return deletedRows;
    }

    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.book";
            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.category";
        }
        return null;
    }

}

5.2.2 

       ContentResolver 幾乎每一個方法都會帶有 Uri 這個參數,我們需要對傳入的 Uri 參數進行解析,從而分析出期望訪問的表和數據。一個標準的內容 URI 寫法:content://com.example.app.provider/table

         內容 URI 的格式主要就只有以上兩種,以路徑結尾就表示期望訪問該表中所有的數據,以 id 結尾就表示期望訪問該表中擁有相應 id 的數據。我們可以使用通配符的方式來分別匹配這兩種格式的內容 URI,規則如下:

  1. (*)表示匹配任意長度的任意字符

  2. (#)表示匹配任意長度的數字

    所以,一個能夠匹配任意表的內容 URI 格式就可以寫成:

    content://com.example.app.provider/*

    而一個能夠匹配 table 表中任意一行數據的內容 URI 格式就可以寫成:

    content://com.example.app.provider/table/#

 

藉助 UriMatcher 類實現匹配內容 URI的功能

  UriMatcher 中提供了一個 addURI() 方法,這個方法接收三個參數,可以分別把權限、路徑和一個自定義代碼傳進去。這樣,當調用 UriMatcher 的 match() 方法時,就可以將一個 Uri 對象傳入,返回值是某個能夠匹配這個 Uri 對象所對應的自定義標識碼,利用這個代碼,我們就可以判斷出期望訪問的是哪張表中的數據了。

    UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    uriMatcher.addURI("com.example.app.provider", "table/#" , TABLE_ITEM);

    switch( uriMatcher.match( uri ) ){ ... }

  其實 query()、update()、insert()、delete() 這幾個方法實現是差不多的,它們都會攜帶 Uri 這個參數,然後同樣利用 UriMatcher 的 match() 方法判斷出期望訪問的表,再對該表中的數據進行相應的操作就可以了。

  除此之外,還有一個 getType() 方法。它是所有的內容提供器都必須提供的一個方法,用於獲取 Uri 對象所對應的 MIME 類型。一個內容 URI 所對應的 MIME 字符串主要由三部分組成,Android 對這三個部分做了如下格式規定:

    1. 必須以 vnd 開頭。

    2. 如果內容 URI 以路徑結尾,則後接 android.cursor.dir/,如果內容 URI 以 id 結尾,則後接 android.cursor.item/。

    3. 最後接上 vnd.<authority>.<path>。

  例如:"vnd.android.cursor.dir/vnd.com.example.app.provider.table"

  或:"vnd.android.cursor.item/vnd.com.example.app.provider.table"

  到這裏,一個完整的內容提供器就創建完成了,現在任何一個應用程序都可以使用 ContentResolver 來訪問我們程序中的數據。那麼如何保證隱私數據不會泄露出去呢?其實多虧了內容提供器的良好機制,這個問題在不知不覺中已經被解決了。

  因爲所有的操作都一定要匹配到相應的內容 URI 格式才能進行,而我們當然不可能向 UriMatcher 中添加隱私數據的 URI,所以這部分數據根本無法被外部程序訪問到,安全問題也就不存在了。


5.3 總結:

  ContentResolver 中的基本操作和 SQLiteDatabase 中的操作類似,只不過 ContentResolver 不像 SQLiteDatabase 一樣接收表名作爲參數,而是使用內容 URI 來定位具體的程序和數據表,而內容URI是由 "content://" + 權限 + 路徑 組成,通過 Uri.parser() 方法將內容 URI 字符串解析成 Uri 對象。


六:Android線程和異步消息處理機制

6.1 Android線程

安卓線程即java線程,使用中大致分爲三種:

繼承Thread類:extends Thread

實現Runable接口:implements Runnable

匿名內部類:

new Thread(new Runnable(){
@Override
   public void run(){
       //邏輯
  }
}).start();

6.2 Android異步消息處理機制

Android 中的異步消息處理主要由四個部分組成,Message、Handler、MessageQueue、Looper。

1. Message:

  Message 是在線程之間傳遞的消息,它可以在內部攜帶少量的信息,用於在不同線程之間交換數據。通常使用 Message 的 what 字段攜帶命令,除此之外還可以使用 arg1 和arg2 字段來攜帶一些整形數據,使用 obj 字段攜帶一個 Object 對象。

2. Handler:

  Handler 顧名思義也就是處理者的意思,它主要是用於發送和處理消息的。發送消息一般是使用 Handler 的 sendMessage()方法,而發出的消息經過一系列地輾轉處理後,最終會傳遞到 Handler 的 handlerMessage()方法中。

3. MessageQueue:

  MessageQueue 是消息隊列的意思,它主要用於存放所有通過 Handler 發送的消息。這部分消息會一直存在於消息隊列中,等待被處理。每個線程中只會有一個 MessageQueue 對象。

4. Looper:

  Looper 是每個線程中的 MessageQueue 的管家,調用 Looper 的 loop() 方法後,就會進入到一個無限循環當中,然後每當發現 MessageQueue 中存在一條消息,就會將它取出,並傳遞到 Handler 的 handleMessage() 方法中。每個線程中也只會有一個 Looper 對象。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    public static final int UPDATE_TEXT = 1;

    private TextView text;
    private Button changeText;

    private Handler handler = new Handler() {


        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TEXT:
                    //在這裏執行UI操作
                    text.setText("Nice to meet you");
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text = (TextView) findViewById(R.id.text);
        changeText = (Button) findViewById(R.id.change_text);
        changeText.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.change_text:
                new Thread(new Runnable() {

                    @Override
                    public void run() {
                        //子線程中改變UI導致程序崩潰
                        //text.setText("Nice to meet you");

                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        //將Message對象發送出去
                        handler.sendMessage(message);
                    }
                }).start();
        }
    }
}

* Looper的prepare是幹嘛的?

Looper封裝了android線程中的消息循環,默認情況下一個線程是不存在消息循環(message loop)的,需要調用Looper.prepare()來給線程創建一個消息循環,調用Looper.loop()來使消息循環起作用,從消息隊列裏取消息,處理消息。主線程中默認打開了一個Looper,而在子線程中需要程序員自己去執行一次Looper.prepare()。

6.3 AsyncTask 

       爲了更加方便我們在子線程中對 UI 進行操作,Android 還提供了另外一些好用的工具,AsyncTask 就是其中之一。藉助 AsyncTask,即使你對異步消息處理機制完全不瞭解,也可以十分簡單地從子線程切換到主線程。當然,AsyncTask 背後的實現原理也是基於異步消息處理機制的,只是 Android 做了很好的封裝而已。

6.3.1 AsyncTask 的基本用法:

  首先來看一下 AsyncTask 的基本用法,由於 AsyncTask 是一個抽象類,所以如果我們想使用它,就必須創建一個子類去繼承它。在繼承時我們可以爲 AsyncTask 類指定三個泛型參數,這三個參數的用途如下:

    Params:在執行 AsyncTask 時需要傳入的參數,可用於在後臺任務中使用。

    Progress:後臺任務執行時,如果需要在界面上顯示當前的進度,則使用這裏指定的泛型作爲進度單位。

    Result:當任務執行完畢後,如果需要對結果進行返回,則使用這裏指定的泛型作爲返回值類型。

接着我們還需要重寫 AsyncTask 中的幾個方法才能完成對任務的定製。

    onPreExecute():這個方法會在後臺任務開始執行之前調用,用於進行一些界面上的初始化操作,比如顯示一個進度條對話框等。

  doInBackground(Params...):這個方法中的所有代碼都會在子線程中運行,我們應該在這裏去處理所有的耗時任務。注意,在這個方法中是不可以進行 UI 操作的。

  onProgressUpdate(Progress...):當後臺任務中調用了 publishProgress(Progress...)方法後,這個方法就會很快被調用,方法中攜帶的參數就是在後臺任務中傳遞過來的。在這個方法中可以對 UI 進行操作,利用參數中的數值就可以對界面元素進行相應地更新。

  onPostExecute(Result):當後臺任務執行完畢並通過 return 語句進行返回時,這個方法就很快會被調用。返回的數據會作爲參數傳遞到此方法中,可以利用返回的數據來進行一些 UI 操作,比如提醒任務執行的結果,以及關閉掉進度條對話框等。

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {

    @Override
    protected void onPreExecute() {
        //顯示進度對話框
//        ProgressDialog.show();
    }

    @Override
    protected Boolean doInBackground(Void... params) {
        try {
            while (true) {
                int downloadPercent = 0;
                //這是一個虛構的方法
//                downloadPercent = doDownload;
                publishProgress(downloadPercent);
                if (downloadPercent >= 100) {
                    break;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        //在這裏更新下載進度
//        progressDialog.setMessage("Downloaded " + values[0] + "%");
    }

    @Override
    protected void onPostExecute(Boolean result) {
        //關閉進度對話框
//        progressDialog.dismiss();
        //在這裏提示下載結果
        if (result) {
//             Toast.makeText(context,"Download succeeded",Toast.LENGTH_SHORT).show();
        } else {
//            Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show();
        }
    }
}

6.3.2 啓動任務

        簡單來說,使用 AsyncTask 的訣竅就是,在 doInBackground() 方法中去執行具體的耗時任務,在 onProgressUpdate() 方法中進行 UI 操作,在 onPostExecute()方法中執行一些任務的收尾工作。

  如果想要啓動這個任務,只需編寫以下代碼即可:    

new DownloadTask.execute();

       以上就是 AsyncTask 的基本用法。我們並不需要去考慮異步消息處理機制,也不需要專門使用一個 Handler 來發送和接收消息,只需要調用一下 publishProgress()方法就可以輕鬆地從子線程切換到 UI 線程了。


七 通知

7.1 通知基本用法

7.1.1 創建通知NotificationManager對象

         通過Context類的getSystemService()獲得NotificationManager對象,getSystemService()方法參數用於確定獲取系統哪個服務。

  NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

7.1.2 構造通知Notification對象

Notification notification = new NotificationCompat.Builder(this)
                        .setContentTitle("This is content title")
                        .setContentText("This is content text")
                        .setWhen(System.currentTimeMillis())
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                        .setContentIntent(pi)
                //        .setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg")))
                //        .setVibrate(new long[]{0, 1000, 1000, 1000})
                //        .setLights(Color.GREEN, 1000, 1000)
                        .setDefaults(NotificationCompat.DEFAULT_ALL)
                //        .setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notifications, send and sync data, and use voice actions. Get the official Android IDE and developer tools to build apps for Android."))
                        .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.big_image)))
                        .setPriority(NotificationCompat.PRIORITY_MAX)
                        .build();

7.1.3 顯示通知

         notificationManager表示NotificationManager對象,第一個參數表示通知id(必須唯一,代表通知的唯一標誌),第二個參數代表Notification對象。

notificationManager.notify(1, notification);

7.1.4 通知點擊效果

Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);

        在Notification對象build中添加: .setContentIntent(pi) ;點擊通知之後,將跳到NotificationActivity.class類。

7.1.5 取消通知

         兩種方法,一是顯式調用NotificationManager的cancel();二是NotificationCompat.Builder中設置.setAutoCancel(true)

7.2 通知進階用法

7.2.1 通知發出時播放音頻/震動

       發音頻:在Notification對象build中添加: .setSoound() ;通知發出時,將播放此路徑音頻。

.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg")))

       發震動:在Notification對象build中添加: .setVibrate;參數爲long型數組,index爲0的值表示靜止幾毫秒,index爲1的值帶邊震動幾毫秒,靜止幾毫秒,震動幾毫秒,以此遞推。 

.setVibrate(new long[]{0, 1000, 1000, 1000})//靜止0秒,震動1秒,靜止1秒,震動1秒

7.2.2 通知未被看時(如鎖屏狀態)LED提示

        閃燈:.setLights(),三個參數含義分別爲:燈顏色,燈暗幾毫秒,燈亮幾毫秒;

.setLights(Color.GREEN, 1000, 1000)

7.3通知高級功能

7.3.1 setStyle()方法

          利用NotificationCompat.Builder類中的setStyle()方法,可以構建除了文字和圖標以外的富文本通知內容;

          長文字:一般通知文字內容就兩行,超過部分省略號表示,想要長文本就如下

.setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notifications, send and sync data, and use voice actions. Get the official Android IDE and developer tools to build apps for Android."))

          圖片:一般通知沒有大圖片,想要大圖片就如下

.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.big_image)))

7.3.2 setPriority()方法

         在Notification對象build中添加:.setPriority(NotificationCompat.PRIORITY_MAX);

         參數有五個常量可選:

  1. 默認的重要程度:PRIORITY_DEFAULT;
  2. 最低重要程度PRIORITY_MIN;
  3. 較低重要程度PRIORITY_LOW;
  4. 較高重要程度:PRIORITY_HIGH;
  5. 最高重要程度(會直接頂部提示):PRIORITY_MAX;

八 服務

8.1 基本用法

8.1.1 定義和開關服務

       服務屬四大組件,繼承Service類;

       開關方式:

       Context類中的startService()方法和stopService()方法;

       Service中的stopService方法();

Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 啓動服務
              
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服務

       onStartCommand():在每次服務啓動時調用;

8.2 服務和活動的通信

8.3 服務的生命週期

1). 被啓動的服務的生命週期:如果一個Service被某個Activity 調用 Context.startService 方法啓動,那麼不管是否有Activity使用bindService綁定或unbindService解除綁定到該Service,該Service都在後臺運行。如果一個Service被startService 方法多次啓動,onCreate方法只會調用一次,onStart將會被調用多次(對應調用startService的次數),並且系統只會創建Service的一個實例(因此你應該知道只需要一次stopService調用)。該Service將會一直在後臺運行,而不管對應程序的Activity是否在運行,直到被調用stopService,或自身的stopSelf方法。當然如果系統資源不足,android系統也可能結束服務。

2). 被綁定的服務的生命週期:如果一個Service被某個Activity 調用 Context.bindService 方法綁定啓動,不管調用 bindService 調用幾次,onCreate方法都只會調用一次,同時onStart方法始終不會被調用。當連接建立之後,Service將會一直運行,除非調用Context.unbindService 斷開連接或者之前調用bindService 的 Context 不存在了(如Activity被finish的時候),系統將會自動停止Service,對應onDestroy將被調用。

3). 被啓動又被綁定的服務的生命週期:如果一個Service又被啓動又被綁定,則該Service將會一直在後臺運行。並且不管如何調用,onCreate始終只會調用一次,對應startService調用多少次,Service的onStart便會調用多少次。調用unbindService將不會停止Service,而必須調用 stopService 或 Service的 stopSelf 來停止服務。

4). 當服務被停止時清除服務:當一個Service被終止(1、調用stopService;2、調用stopSelf;3、不再有綁定的連接(沒有被啓動))時,onDestroy方法將會被調用,在這裏你應當做一些清除工作,如停止在Service中創建並運行的線程。

特別注意:

1、你應當知道在調用 bindService 綁定到Service的時候,你就應當保證在某處調用 unbindService 解除綁定(儘管 Activity 被 finish 的時候綁定會自動解除,並且Service會自動停止);

2、你應當注意 使用 startService 啓動服務之後,一定要使用 stopService停止服務,不管你是否使用bindService;

3、同時使用 startService 與 bindService 要注意到,Service 的終止,需要unbindService與stopService同時調用,才能終止 Service,不管 startService 與 bindService 的調用順序,如果先調用 unbindService 此時服務不會自動終止,再調用 stopService 之後服務纔會停止,如果先調用 stopService 此時服務也不會終止,而再調用 unbindService 或者 之前調用 bindService 的 Context 不存在了(如Activity 被 finish 的時候)之後服務纔會自動停止;

4、當在旋轉手機屏幕的時候,當手機屏幕在“橫”“豎”變換時,此時如果你的 Activity 如果會自動旋轉的話,旋轉其實是 Activity 的重新創建,因此旋轉之前的使用 bindService 建立的連接便會斷開(Context 不存在了),對應服務的生命週期與上述相同。

8.4 服務高級技巧之前臺服務

        服務默認後臺運行,優先級較低,內部不足時易被回收;如果需要一直運行,可以採用前臺服務;

        與一般服務區別:狀態欄顯示運行圖標,下拉有詳情。

        前臺服務的創建:在Service的oonCreate中,創建通知,最後顯式通知時不用notificationManager.notify(),而是調用startForeground()方法,即可將service變成一個前臺服務,並且在系統狀態欄顯示出來。

public void onCreate() {
        super.onCreate();
        Log.d("MyService", "onCreate executed");
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        startForeground(1, notification);
    }

8.5 IntentService

        服務默認運行在主線程,耗時操作易ANR,IntentService旨在方便程序員創建一個異步、可以自動停止的服務;它的onHnadleIntent方法是在子線程中跑的:

public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("MyIntentService"); // 調用父類的有參構造函數
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 打印當前線程的id
        Log.d("MyIntentService", "Thread id is " + Thread.currentThread(). getId());
    }

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

}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章