归纳下广播和内容提供器

广播

what

android内部封装的一种通信方式,如其名,广播,向注册的接收者广播信息

  • 标准广播:异步执行,所有接受器几乎同时收到,无法被拦截
  • 有序广播:同步执行,有先后顺序,按照优先级,可以被拦截

接受系统广播

在android系统内部会有巨多的广播,来向向应用扩散重要信息。比如开机,网络,sd卡等等。

想要接受系统广播首先需要定义广播接收器

  • 静态注册:在Manifest中声明关心的广播
  • 动态注册:通过代码声明

定义广播接收器

继承BroadcastReceiver重写onReceive即可判断广播

1
2
3
4
5
6
7
8
9
10
11
12
class NetworkChangeRecevier extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isAvailable()) {
Toast.makeText(MainActivity.this,"网络可用",Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this,"网络不可用",Toast.LENGTH_SHORT).show();
}
}
}

静态注册

直接在Manifest中书写receiver标签,同如activity和service,写好intent-filter

1
2
3
4
5
6
7
8
9
10
11
12
13
//1、新建一个BroadcastReceiver
//2、在应用配置文件,设置好权限,以及监听的广播
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
//enabled表示是否启用
//exproted表示是否接受应用以外的广播

动态注册

1
2
3
4
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
recevier = new NetworkChangeRecevier();
registerReceiver(recevier,intentFilter);

只有注册完之后才能监听广播。

记得在对应的生命周期取消注册。

1
unregisterReceiver(recevier);

自定义广播

需要自行传达某些消息

发送标准广播

8.0后自定义广播接受不到解决方案:

  • 使用动态注册
  • 静态注册,发送广播时携带ComponentName
1
2
3
4
5
6
7
8
//1、新建一个自定义的广播接受器
//2、静态注册接受自定义的广播
//3、发送广播
Intent intent = new Intent("com.example.tt.MY_BROADCAST");
Log.d("button", "onClick: ");
intent.setComponent(new ComponentName("com.example.hujie2.broadcasttest",
"com.example.hujie2.broadcasttest.MyBroadcastReceiver"));
sendBroadcast(intent);

发送有序广播

1
2
3
setOrderBroadcast(intent);
Manifest通过设置android:priority设置优先级
使用 abortBroadcast() 拦截广播

粘性广播

需要额外申请权限在Android系统粘性广播一般用来确保重要的状态改变后的信息被持久保存,并且能随时广播给新的广播接收器使用,使用isInitialStickyBroadcast来判断是否是一个粘性的初始广播值,比如我遇见的耳机插拔的时候就出现的粘性广播。

声明权限

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

发送

1
sendStickyBroadcast(intent);//好像API写了废弃。。

判断

1
isInitialStickyBroadcast()

本地广播

只在本地应用实行的广播,如果不需要向外部广播,那就用本地广播咯

发送和注册方式略有不同,通过获得LocalBroadcastManager实例来管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
localBroadcastManager = LocalBroadcastManager.getInstance(this);//本地广播管理器
Button button = findViewById(R.id.button2);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("MY.LocalBroadcast");//意图
localBroadcastManager.sendBroadcast(intent);//发送本地广播
}
});
intentFilter = new IntentFilter();//意图过滤
intentFilter.addAction("MY.LocalBroadcast");//设置监听广播行为
localReceiver = new LocalReceiver();//自定义广播接收器
localBroadcastManager.registerReceiver(localReceiver,intentFilter);//注册广播接收器
localBroadcastManager.unregisterReceiver(localReceiver);//注销广播接受器

后续补充

内容提供器

what

在andorid往往需要访问其它应用的数据共同协作。通过内容提供器的方式安全的提供给其他应用访问修改数据。比如读取通讯录,读取MediaCenter的数据,他们都是暴露除了内容提供器来供其它应用访问数据。其它应用通过内容解析器访问uri,如同访问数据库的方式一样访问修改数据。

FileContentProvider

从android 7.0开始直接使用file://Uri这样暴力的传达文件路径进行分享操作将不可用,会报FileUriExposedException异常,必须得用content://Uri这样得形式来解析分享文件的路径

声明FileContentProvider

Manifest中配置

1
2
3
4
5
6
7
8
9
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="app的包名.fileProvider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

路径配置文件

在xml资源文件夹下创键file_paths.xml

1
2
3
4
5
6
7
8
9
10
11
12
//每个节点都支持两个属性:name+path
//path:需要临时授权访问的路径(.代表所有路径)
//name:就是你给这个访问路径起个名字
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="" /> //代表设备的根目录new File("/");
<files-path name="files" path="" /> //context.getFilesDir()
<cache-path name="cache" path="" /> //context.getCacheDir()
<external-path name="external" path="" /> //Environment.getExternalStorageDirectory()
<external-files-path name="name" path="path" /> //context.getExternalFilesDirs()
<external-cache-path name="name" path="path" /> //getExternalCacheDirs()
</paths>

使用

1
2
3
4
5
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri uri = FileProvider.getUriForFile(CameraActivity.this, "app的包名.fileProvider", photoFile);
} else {
Uri uri = Uri.fromFile(photoFile);
}

经过这样的包装形成uri:content://app的包名/files/path/文件 就可以匹配到具体实际路径了

访问应用数据

android 的许多数据都可以获得,但是要申请权限

比如:通讯录,短信,各种类别的文件

准备uri

api的类一般都会暴露uri,比如:MediaStore.Files.getContentUri("external")

解析
1
2
3
4
context.getContentResolver().query();
context.getContentResolver().insert();
context.getContentResolver().delete();
context.getContentResolver().update();

就和操作数据库一样,因为内容提供器本身就是相当于包装了一层数据库访问操作。

自定义内容提供器

自然还是要Manifest声明

1
2
3
4
5
<provider
android:name=".provider.MyContentProvider"
android:authorities="com.my.demo.provider"
android:enabled="true"
android:exported="true" />

继承实现

感觉是固定代码,内部访问数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package com.example.hujie2.filepersistencetest;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

public class DatabaseContentProvider 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.hujie2.filepersistencetest.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper dbHelper;

static {
//uri匹配
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);
}

public DatabaseContentProvider() {

}

@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);
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) {
String vndDir = "vnd.android.cursor.dir/";
String vndItem = "vnd.android.cursor.item/";
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return vndDir + "vnd." + AUTHORITY + ".book";
case BOOK_ITEM:
return vndItem + "vnd." + AUTHORITY + ".book";
case CATEGORY_DIR:
return vndDir + "vnd." + AUTHORITY + ".category";
case CATEGORY_ITEM:
return vndItem + "vnd." + AUTHORITY + ".category";
default:
break;
}
return null;
}
}