🔥 Intent 传递数据 🔥
Activity
、Service
、BroadcastReceiver
之间的通信载体Intent
来传递数据。而ContentProvider则是共享文件。
Intent可传递的数据类型:
- a. 8种基本数据类型(boolean byte char short int long float double)、String
- b. Intent、Bundle
- c. Serializable和Parcelable序列化对象(将对象转为字节流)及其对应数组、CharSequence 类型
- d. ArrayList,泛型参数类型为:<Integer>、<? Extends Parcelable>、<Charsequence>、<String>
序列化
1.Serializable 方式来进行序列化 //构建一个实现了Serializable接口的类 public class XrData implements Serializable {public String name;public String phone; }//序列化Bundle bundle = new Bundle(); // Bundle主要用于传递数据,它保存的数据,是以key-value(键值对)的形式存在的bundle.putSerializable("DATA", entity); //1fragment.setArguments(bundle);//反序列化mContact = (XrData) getArguments().getSerializable("DATA");mContact.name2.Parcelable 这是安卓专用的序列化方式。Parcelable接口比Serializable接口效率更高。 因为其实现原理是把对象分解为Intent支持的数据类型,并通过Intent进行传输; 其数据可以保存在内存中,相对于Serializable将数据保存在磁盘,效率自然更高。public class CutInfo implements Parcelable {private int offsetX;private int offsetY;public CutInfo() {}// 序列化@Overridepublic void writeToParcel(Parcel dest, int flags) { //3dest.writeInt(this.offsetX);dest.writeInt(this.offsetY);}@Overridepublic int describeContents() { //4return 0;}// 反序列化protected CutInfo(Parcel in) {this.offsetX = in.readInt();this.offsetY = in.readInt();}// 反序列化public static final Creator<CutInfo> CREATOR = new Creator<CutInfo>() { //5@Overridepublic CutInfo createFromParcel(Parcel source) {return new CutInfo(source);}@Overridepublic CutInfo[] newArray(int size) {return new CutInfo[size];}}; }// ActivityA.java 实现序列化 button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent(this, ActivityB.class);intent.putExtra("cut", new CutInfo(8, 8));startActivity(intent);}});// ActivityB.java 获取并反序列化Intent intent = getIntent();CutInfo cut = intent.getParcelableExtra("cut");Log.d("ActivityB", cut.toString()); //会输出: “8, 8”
🔥 ContentProvider 进程间传递数据 🔥
进程间传递数据(底层用的还是binder)
原理:Binder简单的用法是进程A可以通过Binder获得进程B的本地代理,通过本地代理,就可以在进程A里面的调用进程B的方法。在ContentProvider的实现原理中,通过ContentResolver可以查找对应给定Uri的ContentProvider,返回对应的本地代理 BinderProxy,通过这个BinderProxy就可以调用insert、delete接口。
- Content Provider使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。(例如通信录信息)
- ContentProvider使用URI来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider来管理。ContentProvider是以Uri的形式对外提供数据,ContentResolver是根据Uri来访问数据。
🔥 三者关系 🔥
ContentProvider、ContentResolver、ContentObserver 三者关系:
- ContentProvider:内容提供者,主要作用就是管理数据,比如最常见的增删改查操作,同时为这些数据的访问提供了统一的接口,实现进程间的数据传递和共享;
- ContentResolver:内容解析者,ContentResolver可以为不同URI操作不同的ContentProvider中的数据,外部进程可以通过ContentResolver与ContentProvider进行交互。
- ContentObserver:内容观察者,观察ContentProvider中的数据变化,有变化的时候执行特定操作。本人用的最多的是监听Settings数据库的变化。由于ContentObserver的生命周期不同步于Activity和Service等,因此,在不需要时,需要手动的调用unregisterContentObserver()去取消注册。
🔥 在AndroidManifest注册 🔥
在Android文件AndroidManifest.xml注册
authorities:访问表的主机地址;
exported:是否允许对外应用程序访问,true允许,false不允许,默认false;
🔥 继承ContentProvider 🔥
Android系统为我们提供ContentProvider类,我们继承这个类实现onCreate()、getType()、insert()、delete()、update()、query();
onCreate():内容提供者初始化的时候调用,一般这里执行数据库创建、升级操作。
getType():根据给定的Uri返回一个MIME类型的数据。
insert():Uri表示需要操作哪张表,ContentValues需要插入的值,插入成功返回一个Uri;
delete():Uri表示需要操作哪张表,selection和selectionArgs表示筛选的条件,返回受影响的行。
update():Uri表示需要操作哪张表,ContentValues需要插入的值,selection和selectionArgs表示筛选的条件,返回受影响的行。
query():Uri表示需要操作哪张表,projection需要查询表中那些字段,selection和selectionArgs表示筛选的条件,sortOrder对查询查询结果进行排序,将查询结果放到Cursor返回。
public class PersonProvider extends ContentProvider {private static String TAG = PersonProvider.class.getSimpleName();//这里的AUTHORITY就是我们在AndroidManifest.xml中配置的authoritiespublic static final String AUTHORITY = "com.dream.contentprovider";//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码,也就是说如果找不到匹配的类型,返回-1private static final UriMatcher URI_MATCHER;//匹配不同的表的codeprivate static final int ALL_PERSON = 1;private static final int PERSON = 2;//数据库的名字public static final String DB_NAME = "test.db";//数据库版本public static final int DB_VERSION = 1;//数据库表的名称private static final String TABLE_NAME = "person";private MyDBHelper dbHelper;private SQLiteDatabase db;static {//创建一个路径识别器URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);//1.指定一个路径的匹配规则//如果路径满足content://com.dream.contentprovider.provider.PersonProvider/person,返回值就是(ALL_PERSON)=1URI_MATCHER.addURI(AUTHORITY, "/person", ALL_PERSON);//2.#号为通配符//如果路径满足content://com.dream.contentprovider.provider.PersonProvider/person/3,返回值就是(PERSON)=2URI_MATCHER.addURI(AUTHORITY, "/person/#", PERSON);}/*** 一般是对象第一次被创建时调用的方法** @return*/@Overridepublic boolean onCreate() {dbHelper = new MyDBHelper(this.getContext(), DB_NAME, null, DB_VERSION);db = dbHelper.getReadableDatabase();return true;}/*** 让别人去调用返回结果** @param uri 匹配条件* @param projection 选择的列* @param selection 查询条件* @param selectionArgs 查询条件的value* @param sortOrder 排序* @return*/@Nullable@Overridepublic Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {int type = URI_MATCHER.match(uri);switch (type) {case ALL_PERSON:return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);case PERSON://获取具体某一行的数据String id = uri.getPathSegments().get(1);return db.query(TABLE_NAME, projection, "_id = ?", new String[]{id}, null, null, sortOrder);default:break;}return null;}//如果是单条记录应该返回以vnd.android.cursor.item/ 为首的字符串//如果是多条记录,应该返回vnd.android.cursor.dir/ 为首的字符串@Nullable@Overridepublic String getType(@NonNull Uri uri) {int type = URI_MATCHER.match(uri);switch (type) {case ALL_PERSON:return "vnd.android.cursor.dir/vnd.com.dream.contentprovider.provider.person";case PERSON:return "vnd.android.cursor.item/vnd.com.dream.contentprovider.provider.person";default:return null;}}@Nullable@Overridepublic Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {int type = URI_MATCHER.match(uri);switch (type) {case ALL_PERSON:long row = db.insert(TABLE_NAME, null, values);return ContentUris.withAppendedId(uri, row);case PERSON:long row2 = db.insert(TABLE_NAME, null, values);return ContentUris.withAppendedId(uri, row2);default:return null;}}@Overridepublic int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {int type = URI_MATCHER.match(uri);int updateRow = 0;switch (type) {case ALL_PERSON:updateRow = db.delete(TABLE_NAME, selection, selectionArgs);break;case PERSON://获取具体某一行的数据String id = uri.getPathSegments().get(1);updateRow = db.delete(TABLE_NAME, "_id = ?", new String[]{id});break;default:break;}return updateRow;}@Overridepublic int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {int type = URI_MATCHER.match(uri);int updateRow = 0;switch (type) {case ALL_PERSON:updateRow = db.update(TABLE_NAME, values, selection, selectionArgs);break;case PERSON://获取具体某一行的数据String id = uri.getPathSegments().get(1);updateRow = db.update(TABLE_NAME, values, "_id = ?", new String[]{id});break;default:break;}return updateRow;} }
当APP启动的时候,onCreate()方法被执行,此时已经创建了数据库。
🔥 跨进程调用 🔥
private static final Uri STUDENT_ALL_URI = Uri.parse("content://" + PersonProvider.AUTHORITY + "/person");
拼接完整的地址为:"content://com.dream.contentprovider/person"和"content://com.dream.contentprovider/person/id",其中这个id表示表中数据的行数,id=0、1、 2 ...。当和UriMatcher 进行匹配返回匹配类型。不管是进程内还是跨进程之间通信关键是否URL正确,否则会出现如下错误:
private static final Uri STUDENT_ALL_URI = Uri.parse("content://com.dream.contentprovider/person");
插入数据insert()
mUserProvider = getContentResolver();ContentValues values = new ContentValues();values.put("name", "张三");values.put("age", "18");values.put("sex", "男");mUserProvider.insert(STUDENT_ALL_URI, values);
删除数据delete()
mUserProvider = getContentResolver();mUserProvider.delete(STUDENT_ALL_URI, "name = ?", new String[]{"张三"});
查询数据query()
mUserProvider = getContentResolver();String result = "";Cursor cursor = mUserProvider.query(STUDENT_ALL_URI, null, null, null);if (cursor != null) {try {while (cursor.moveToNext()) {String id = cursor.getString(cursor.getColumnIndex("_id"));String name = cursor.getString(cursor.getColumnIndex("NAME"));String age = cursor.getString(cursor.getColumnIndex("AGE"));String sex = cursor.getString(cursor.getColumnIndex("SEX"));result = result + "\n" + "_id=" + id + " name=" + name + " sex=" + sex + " age=" + age;}} finally {cursor.close();cursor = null;mTextView.setText(result);}}
记得关闭游标,防止内存泄漏
🔥 BroadcastReceiver 🔥
BroadcastReceiver(广播接收器),属于 Android 四大组件之一,在 Android 开发中,BroadcastReceiver 的应用场景非常多。今天,我将详细讲解关于BroadcastReceiver的所有广播类型,主要分为5类:
普通广播(Normal Broadcast)
系统广播(System Broadcast)
有序广播(Ordered Broadcast)
粘性广播(Sticky Broadcast)
App应用内广播(Local Broadcast)
🔥 普通广播(Normal Broadcast)🔥
即开发者自身定义intent的广播(最常用)。发送广播使用如下:
Intent intent = new Intent(); //对应BroadcastReceiver中intentFilter的action intent.setAction(BROADCAST_ACTION); //发送广播 sendBroadcast(intent);
若被注册了的广播接收者中注册时 intentFilter 的 action与上述匹配,则会接收此广播(即进行回调 onReceive() )。如下mBroadcastReceiver则会接收上述广播
<receiver //此广播接收者类是mBroadcastReceiverandroid:name=".mBroadcastReceiver" >//用于接收网络状态改变时发出的广播<intent-filter><action android:name="BROADCAST_ACTION" /></intent-filter> </receiver>
若发送广播有相应权限,那么广播接收者也需要相应权限
🔥 系统广播(System Broadcast)🔥
Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播
每个广播都有特定的Intent - Filter(包括具体的action),Android系统广播action如下:
系统操作 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 注:当使用系统广播时,只需要在注册广播接收者时定义相关的action即可,并不需要手动发送广播,当系统有相关操作时会自动进行系统广播
🔥 有序广播(Ordered Broadcast)🔥
定义
发送出去的广播被广播接收者按照先后顺序接收
广播接受者接收广播的顺序规则(同时面向静态和动态注册的广播接受者)
按照Priority属性值从大-小排序;
Priority属性相同者,动态注册的广播优先;
特点
接收广播按顺序接收
先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播;
先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播
具体使用
有序广播的使用过程与普通广播非常类似,差异仅在于广播的发送方式:sendOrderedBroadcast(intent);
🔥 App应用内广播(Local Broadcast)🔥
背景
Android中的广播可以跨App直接通信(exported对于有intent-filter情况下默认值为true)
冲突
可能出现的问题:
其他App针对性发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收广播并处理;
其他App注册与当前App一致的intent-filter用于接收广播,获取广播具体信息;
即会出现安全性 & 效率性的问题。
解决方案
使用App应用内广播(Local Broadcast)1、App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。
2、相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高
具体使用1 - 将全局广播设置成局部广播
注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;
在广播发送和接收时,增设相应权限permission,用于权限验证;
发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
通过**intent.setPackage(packageName)**指定报名
具体使用2 - 使用封装好的LocalBroadcastManager类
使用方式上与全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将参数的context变成了LocalBroadcastManager的单一实例
注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册,不能静态注册
//注册应用内广播接收器 //步骤1:实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver(); IntentFilter intentFilter = new IntentFilter(); //步骤2:实例化LocalBroadcastManager的实例 localBroadcastManager = LocalBroadcastManager.getInstance(this);//步骤3:设置接收广播的类型 intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);//步骤4:调用LocalBroadcastManager单一实例的registerReceiver()方法进行动态注册 localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);//取消注册应用内广播接收器 localBroadcastManager.unregisterReceiver(mBroadcastReceiver);//发送应用内广播 Intent intent = new Intent(); intent.setAction(BROADCAST_ACTION); localBroadcastManager.sendBroadcast(intent);
🔥 粘性广播(Sticky Broadcast)🔥
由于在Android5.0 & API 21中已经失效,所以不建议使用,在这里也不作过多的总结。
🔥 OnReceive 回调函数中Context 含义 🔥
对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:
对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context;
对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context。
对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context;