Android學(xué)習(xí)感悟之Service、Broadcast以及ContentProvider的常見用法

本篇包含Service、Broadcast以及ContentProvider的常見用法

簡介

四大組件,包含了Activity、Service、Broadcast以及ContentProvider,而Activity使用最多,所以在感悟的第一篇就介紹了;而當(dāng)我們在項(xiàng)目開發(fā)過程中,就難免會用到其他的組件,這篇文章中就會講道如何使用其他組件,并且有一些個人的理解。

Service

Service生命周期

Service,我們又叫它服務(wù),這個東西在進(jìn)程間通信的文章中,就幫了我們大忙,通過綁定服務(wù),得到Binder對象,就能夠進(jìn)程間的通信了,而這只是啟動服務(wù)的一種方式,還有一種就是通過context來startService。

服務(wù)和Activity一樣,都是有生命周期的,下面就上一張官方Service的生命周期圖:

service_lifecycle.png

可以看到Service的生命周期都是以onCreate開始,onDestory結(jié)束,但是使用不同的啟動方式去啟動服務(wù),生命周期的中間是會有不同的地方。

總的來看Service中的回調(diào)方法有如下幾個:

1、onCreate:只有在第一次創(chuàng)建(不管如何創(chuàng)建)的時候,才會被回調(diào)

2、onStartCommand:只有通過startService啟動的服務(wù)才回回調(diào);

3、onBind:只有第一次通過bindService啟動的服務(wù)才回回調(diào),之后再綁定也不回調(diào)了,因?yàn)樵摲椒ㄆ鋵?shí)就是去返回一個已經(jīng)創(chuàng)建好的Binder;

4、onUnbind:只有最后一個unbindService的時候調(diào)用

5、onDestory:只有在所有啟動都結(jié)束后才會回調(diào),即startService多少次就要stopService多少次,才能被結(jié)束;或者bindService多少次就要unBindService多少次才能被結(jié)束;或者兩者都有,即啟動和結(jié)束要匹配完才能結(jié)束。

注:具體的測試demo的地址會在最后放上

前臺Service

大家都知道Service是沒有界面的,給人的感覺就是后臺運(yùn)行的,正常情況的確如此,但是它卻依舊運(yùn)行在主線程,所以耗時操作都要在子線程中處理,其實(shí)Service還有一種叫做前臺服務(wù),就好比網(wǎng)易云音樂的播放操作欄。

其必要條件是:必須在狀態(tài)欄提供通知,除非服務(wù)停止或從前臺移除,否則不能清除該通知。

而具體的方法就是調(diào)用startForeground()方法,其兩個參數(shù)為唯一標(biāo)識通知的整型數(shù)(不能為0)和狀態(tài)欄的Notification,例如:

NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
builder.setAutoCancel(false);
builder.setOngoing(true);
builder.setShowWhen(true);
builder.setContentTitle("這是一個前臺服務(wù)");
builder.setContentText("你好啊~");
Notification notification = builder.build();
startForeground(NOTIFICATION_DOWNLOAD_PROGRESS_ID, notification);

而如果該通知要做的更好,就需要用到RemoteView的知識,可能后續(xù)的文章中會講道。

在前臺demo中既然能start就肯定會有stopForeground(true),參數(shù)表示,是否移除該通知,而該通知的開和關(guān),這里讓啟動服務(wù)的組件去控制,便用到了Messenger,這是一種比較簡單的進(jìn)程間通信的方法,具體的代碼就不上了,想了解的就看看demo去吧,代碼比較簡單。

Broadcast

廣播,在目前的開發(fā)中用到的不多,用到的地方就如:軟件的安裝情況、極光推送的回調(diào)、短信監(jiān)聽等等;

廣播的回調(diào)方法只有一個onReceive(Context context, Intent intent),且在該方法中,操作不能超過10秒,否則會ANR。參數(shù)的具體含義就不多解釋了。

廣播有兩種注冊方式,一種是靜態(tài)注冊,一種是動態(tài)注冊,下面分辨來看看如何實(shí)現(xiàn)的:

1、靜態(tài)注冊:

需要我們在AndroidManifest.xml文件中,注冊receiver,所以我們就得先實(shí)現(xiàn)一個:

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

/**
 * created by arvin on 17/2/24 00:02
 * email:1035407623@qq.com
 */
public class StaticReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "靜態(tài)注冊的廣播", Toast.LENGTH_SHORT).show();
    }
}

然后在AndroidManifest.xml中聲明:

<receiver android:name=".broadcast.StaticReceiver">
    <intent-filter>
        <action android:name="net.arvin.androidart.broadcast.static" />
    </intent-filter>
</receiver>

這樣就注冊成功了,這里有個細(xì)節(jié),exported沒有設(shè)置時,如果有intent-filter則表示,該廣播是可以被其他進(jìn)程訪問的,反之則不能被其他進(jìn)程方法;如果有exported則以其值為準(zhǔn);

2、動態(tài)注冊:

這種方式其實(shí)也是一樣只是聲明方式不同,首先依然時實(shí)現(xiàn)一個廣播接收者:

private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        showToast("動態(tài)注冊的廣播");
    }
};

然后通過context的registerReceiver方法注冊:

registerReceiver(mReceiver, getFilter());

private IntentFilter getFilter() {
    IntentFilter filter = new IntentFilter();
    filter.addAction(DYNAMICS_ACTION);
    return filter;
}

動態(tài)注冊的廣播,需要在不用的時候調(diào)用unregisterReceiver,例如在onDestroy方法中調(diào)用,避免內(nèi)存泄露。

這樣兩種注冊方式簡單的介紹了一下。

然后發(fā)送廣播,其實(shí)這個也很簡單,發(fā)送的廣播有有序和無序兩種,首先來看無需的。

1、發(fā)送無序廣播:

只需調(diào)用context的sendBroadcast即可,傳入的事intent,這里就涉及到intent-filter的匹配規(guī)則,匹配到則發(fā)送到。具體如何匹配可以查看Android感悟之Intent文章中的講到的匹配規(guī)則。

2、發(fā)送有序廣播:

只需要調(diào)用context的sendOrderedBroadcast即可,而所謂有序廣播則是依次調(diào)用注冊的廣播,排序規(guī)則就是在廣播注冊的時候,設(shè)置的filter.setPriority(int priority);參數(shù)的范圍事[-1000,1000]其中值越大,就越先收到廣播,然后先收到的還能改變intent的值,或者是攔截掉該廣播,例如攔截在黑名單中的電話的短信或來電就是這個原理。攔截的方法也很簡單就是在準(zhǔn)備攔截的廣播中調(diào)用abortBroadcast()方法即可。

ContentProvider用法

ContentProvider它是一種系統(tǒng)提供的重要的進(jìn)程間數(shù)據(jù)交互的方式,底層的實(shí)現(xiàn)也是Binder,具體的實(shí)現(xiàn)這里也就不介紹了,先看看怎么用。交互肯定分為至少兩方,一方是數(shù)據(jù)提供者(實(shí)現(xiàn)ContentProvider),一方則是數(shù)據(jù)訪問者(ContentResolver),而這兩者的交互過程中又有一個最為重要的東西Uri以及UriMatcher,統(tǒng)一資源標(biāo)識符,下面也會挨著介紹。。

Uri

這是一種用于標(biāo)識某一互聯(lián)網(wǎng)資源名稱的字符串,它有三部分,在ContentProvider中大概可以這么分:content://<authority>/<path>

  • "content://":這是第一部分,是協(xié)議;
  • <authority>:這是第二部分,是主機(jī)IP;
  • <path>:這是第三部分,是路徑,方便我們用來判斷怎么操作的部分。

數(shù)據(jù)訪問者

系統(tǒng)提供了ContentResolver類方便用戶通過Uri訪問ContentProvider中的提供的內(nèi)容,而數(shù)據(jù)的操作無外乎增刪查改,剛好這個類也提供了這幾種方法,下面就先簡單的介紹一下,具體如何用請參考demo:

1、query:查,其中參數(shù)有5個比較重要下面挨著說:

  • Uri uri:這個是訪問數(shù)據(jù)的路徑;
  • String[] projection:這個是查詢需要返回的對應(yīng)列的數(shù)據(jù),null則表示所有列;
  • selection:這個是表示sql語句where后邊的條件;
  • String[] selectionArgs:這個是條件中占位符對應(yīng)的參數(shù)值;
  • sortOrder:這個是sql語句中order by后邊的排序方式。

2、insert:增,這個是插入單條數(shù)據(jù),參數(shù):

  • Uri uri:這個是訪問數(shù)據(jù)的路徑;
  • ContentValues values:以map的方式用來存儲插入的數(shù)據(jù),key是列名,value是值。

當(dāng)然也可以插入多條調(diào)用bulkInsert,只需要把第二個參數(shù)變成數(shù)組即可;

3、update:改,修改數(shù)據(jù),參數(shù):

  • Uri uri:這個是訪問數(shù)據(jù)的路徑;
  • ContentValues values:以map的方式用來存儲插入的數(shù)據(jù),key是列名,value是值。
  • String where:這個是表示sql語句where后邊的條件,用于篩選;
  • String[] selectionArgs:這個是條件中占位符對應(yīng)的參數(shù)值;

4、delete:刪,刪除數(shù)據(jù),參數(shù):

  • Uri uri:這個是訪問數(shù)據(jù)的路徑;
  • String where:這個是表示sql語句where后邊的條件,用于篩選;
  • String[] selectionArgs:這個是條件中占位符對應(yīng)的參數(shù)值;

到這里增刪查改四個方法的參數(shù)也介紹的差不多了,具體如何使用請參考demo中的ProviderActivity和AddUserActivity。

ContentProvider

使用ContentProvider,則有兩部,第一繼承ContentProvider,第二在清單文件中配置provider屬性,記得設(shè)置authorities屬性,第二部沒什么好說的。來看第一部:

繼承ContentProvider,需要重寫6個方法:

  • onCreate:在第一次被調(diào)用時創(chuàng)建,一般用于初始化工作
  • getType:這個是用于匹配Intent中的mimeType屬性的,當(dāng)然必須要讓ContentProvider在清單文件的中的過濾器中設(shè)置該屬性才有用;
  • query:查詢;
  • insert:添加;
  • update:修改;
  • delete:刪除。

而增刪查改所操作的數(shù)據(jù)可以是,可以是數(shù)據(jù)庫、文件系統(tǒng)或網(wǎng)絡(luò);而更多使用到的是數(shù)據(jù)庫,所以這篇就以數(shù)據(jù)庫為例;下面就以User表為例,包含三個字?jǐn)啵篿d,name,age;id自增;創(chuàng)建表,我們使用現(xiàn)在比較流行的Greendao,3.0以后使用起來更加方便,使用注解即可,它和ButterKnife的原理一樣,有預(yù)編譯,也不會影響效率,方法如下:

1、在項(xiàng)目級的build.gradle文件中加入:

dependencies {
    classpath 'org.greenrobot:greendao-gradle-plugin:3.2.1'
}

2、在module級的build.gradle文件中加入:

apply plugin: 'org.greenrobot.greendao'

greendao {//這是表示生成的包的目錄以及數(shù)據(jù)庫的版本
    schemaVersion 1
    daoPackage 'net.arvin.androidart.gen'
    targetGenDir 'src/main/java'
}

dependencies {
    //數(shù)據(jù)庫
    compile 'org.greenrobot:greendao:3.2.0'
    compile 'org.greenrobot:greendao-generator:3.2.0'
}

等下載完就配置完成,然后就能直接寫實(shí)體:

@Entity
public class User implements Parcelable {
    @Id(autoincrement = true)
    private Long id;
    private String name;
    private int age;

    @Generated(hash = 1309193360)
    public User(Long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Generated(hash = 586692638)
    public User() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeValue(this.id);
        dest.writeString(this.name);
        dest.writeInt(this.age);
    }

    protected User(Parcel in) {
        this.id = (Long) in.readValue(Long.class.getClassLoader());
        this.name = in.readString();
        this.age = in.readInt();
    }

    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
        @Override
        public User createFromParcel(Parcel source) {
            return new User(source);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
}

寫好實(shí)體后,rebuild一下就生成好了,在net.arvin.androidart.gen包下會多出
DaoMaster、DaoSession、UserDao三個類;

這個實(shí)體其中構(gòu)造方法是greendao生成的,然后序列化是因?yàn)?,在demo中需要傳遞數(shù)據(jù);

他的那些注解這里就不介紹了,有興趣的可以自行Google。

然后我們來看ContentProvider的實(shí)現(xiàn):

這時候我們需要解決一個問題,數(shù)據(jù)訪問者發(fā)來的Uri,正確性我們應(yīng)該如何判斷呢?這里便引入了UriMatcher這個工具類,先來看使用方式:

public static final String MIME_ITEM = "user";

public static final String AUTHORITY = "net.arvin.androidart";
public static final String PATH_SINGLE = MIME_ITEM + "/#";
public static final String PATH_MULTIPLE = MIME_ITEM;

private static final int MULTIPLE_PEOPLE = 1;
private static final int SINGLE_PEOPLE = 2;
private static final UriMatcher uriMatcher;

static {
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI(AUTHORITY, PATH_MULTIPLE, MULTIPLE_PEOPLE);
    uriMatcher.addURI(AUTHORITY, PATH_SINGLE, SINGLE_PEOPLE);
}

在訪問者傳入Uri是,即可通過uriMatcher.match(uri),返回code,表示對應(yīng)的哪一個操作,這里有兩種操作操作單條,操作全部;其中uriMatcher.addUri的參數(shù),分別是:

  • String authority:這個是Uri中的協(xié)議;
  • String path:這個是Uri中的路徑,其中#可表示任意數(shù)字,而這里這個數(shù)字就是下面會用到的id。
  • int code:這個是Uri中路徑對應(yīng)的code,匹配上就返回這個值。

下面就來看看全部的實(shí)現(xiàn)源碼:

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.support.annotation.NonNull;

import net.arvin.androidart.gen.DaoMaster;
import net.arvin.androidart.gen.UserDao;

/**
 * created by arvin on 17/2/25 15:10
 * email:1035407623@qq.com
 */
public class UserProvider extends ContentProvider {
    //mimeType
    public static final String MIME_DIR_PREFIX = "vnd.android.cursor.dir";
    public static final String MIME_ITEM_PREFIX = "vnd.android.cursor.item";
    public static final String MIME_ITEM = "user";

    public static final String MIME_TYPE_SINGLE = MIME_ITEM_PREFIX + "/" + MIME_ITEM;
    public static final String MIME_TYPE_MULTIPLE = MIME_DIR_PREFIX + "/" + MIME_ITEM;

    //有效Uri
    public static final String AUTHORITY = "net.arvin.androidart";
    public static final String PATH_SINGLE = MIME_ITEM + "/#";
    public static final String PATH_MULTIPLE = MIME_ITEM;

    private static final int MULTIPLE_PEOPLE = 1;
    private static final int SINGLE_PEOPLE = 2;
    private static final UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, PATH_MULTIPLE, MULTIPLE_PEOPLE);
        uriMatcher.addURI(AUTHORITY, PATH_SINGLE, SINGLE_PEOPLE);
    }

    public static final String CONTENT_URI_STRING = "content://" + AUTHORITY + "/" + PATH_MULTIPLE;
    public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);

    private SQLiteDatabase db;

    @Override
    public boolean onCreate() {
        DaoMaster.DevOpenHelper mHelper = new DaoMaster.DevOpenHelper(getContext(), "art-db", null);
        db = mHelper.getWritableDatabase();
        return db != null;
    }

    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)) {
            case MULTIPLE_PEOPLE:
                return MIME_TYPE_MULTIPLE;
            case SINGLE_PEOPLE:
                return MIME_TYPE_SINGLE;
            default:
                throw new IllegalArgumentException("UnKnow uri:" + uri);
        }
    }

    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        long id = db.insert(UserDao.TABLENAME, null, values);
        if (id > 0) {
            Uri newUri = ContentUris.withAppendedId(CONTENT_URI, id);
            notifyChange(newUri);
            return newUri;
        }
        throw new SQLException("failed to insert row into " + uri);
    }

    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

        qb.setTables(UserDao.TABLENAME);
        qb.appendWhere(getWhere(uri, selection));

        Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);

        notifyChange(uri);
        return cursor;
    }

    @Override
    public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
        int count = db.delete(UserDao.TABLENAME, getWhere(uri, selection), selectionArgs);

        notifyChange(uri);
        return count;
    }

    @Override
    public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        int count = db.update(UserDao.TABLENAME, values, getWhere(uri, selection), selectionArgs);
        notifyChange(uri);
        return count;
    }

    private String getWhere(@NonNull Uri uri, String selection) {
        String where;
        switch (uriMatcher.match(uri)) {
            case SINGLE_PEOPLE:
                where = UserDao.Properties.Id.columnName + "=" + uri.getPathSegments().get(1);
                break;
            case MULTIPLE_PEOPLE:
                where = selection;
                break;
            default:
                throw new IllegalArgumentException("UnKnow URI: " + uri);
        }
        return where;
    }

    private void notifyChange(@NonNull Uri uri) {
        if (getContext() != null) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
    }
}

這里區(qū)分了操作一條還是多條數(shù)據(jù),不同的Uri其實(shí)就是判斷條件不一樣。下面依次描述如何操作數(shù)據(jù)庫:

  • UriMatcher初始化:靜態(tài)初始化,把能匹配的Uri都加入到改類中,方便判斷;
  • onCreate: 通過Grrendao拿到SQLiteDatabase,由于這里邊更多接近原生代碼,所以就使用SQLiteDatabase來操作,就不用Greendao的方法來操作了。
  • getType:根據(jù)Uri來判斷是哪種類型;
  • 增刪改查:完全是調(diào)用db的原生方法,簡單的操作表方法,這里也不再介紹了。
  • notifyChange:這個的目的是在于回調(diào)告訴那些監(jiān)聽了這個ContentProvider的組件,哪個Uri發(fā)生了改變;這里也不再介紹。

到這里自定義ContentProvider就已經(jīng)實(shí)現(xiàn)了,而數(shù)據(jù)訪問者如何使用請查看demo。

Demo源碼

Service Demo

Broadcast Demo

ContentProvider Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.什么是Activity?問的不太多,說點(diǎn)有深度的 四大組件之一,一般的,一個用戶交互界面對應(yīng)一個activit...
    JoonyLee閱讀 5,858評論 2 51
  • 轉(zhuǎn)自 1. 什么是Activity? 四大組件之一,一般的,一個用戶交互界面對應(yīng)一個activity setCon...
    joe1632閱讀 1,465評論 0 7
  • 由于android系統(tǒng)中應(yīng)用程序之間不能共享內(nèi)存。因此,在不同應(yīng)用程序之間交互數(shù)據(jù)(跨進(jìn)程通訊)就稍微麻煩一些。在...
    Ten_Minutes閱讀 8,627評論 1 7
  • 最近,大多數(shù)孩子都喜歡玩王者榮耀,包括高中生和大學(xué)生,大量大學(xué)生甚至奮戰(zhàn)至深夜,確實(shí)達(dá)到了農(nóng)藥的境界,一接...
    馮百堅閱讀 340評論 0 0
  • 不知為何我就是不喜歡第一次,也不喜歡最后一次。所以有些事我并沒有把它當(dāng)做第一次和最后一次,縱使我心知肚明,我...
    羽落y閱讀 175評論 0 0

友情鏈接更多精彩內(nèi)容