目录
写在前面
Android有五种主要存储方式的用法,包括
下面,我们将要讲解一下Application与内容提供器ContentProvider
4. 应用Application基础
在App运行过程中有且仅有一个Application对象
贯穿整个生命周期。
4.1 Application的生命周期
onCreate
:App启动时调用onTerminate
:App退出时调用- 该方法是
供模拟环境调用
,在真机上不会被调用
- 该方法是
onLowMemory
:低内存时调用onConfigurationChanged
:配置改变时调用,例如从竖屏变为横屏
4.2 利用Application操作全局变量
4.2.1 适用场景
适合在Application中保存的全局变量主要有下面3类数据:
- 频繁读取的信息,如用户名、手机号等
- 从网络上获取的临时数据,为减少用户等待时间,暂时放在内存中供下次使用,如商品图片等Iogo
- 容易因频繁分配内存而导致内存泄漏的对象,如Handler对象等
4.2.2 实现全局内存的读写
要想通过Application实现全局内存的读写,得完成以下3项工作:
写一个继承自Application的类MainApplication
。该类要采用单例模式
,内部声明自身类的一个静态成员对象,在创建App时把自身赋值给这个静态对象,然后提供该静态对象的获取方法getlnstance.
package com.example.storage;
import java.util.HashMap;
import android.app.Application;
import android.graphics.Bitmap;
import android.util.Log;
public class MainApplication extends Application {
private final static String TAG = "MainApplication";
// 声明一个当前应用的静态实例
private static MainApplication mApp;
// 声明一个公共的信息映射对象,可当作全局变量使用
public HashMap<String, String> mInfoMap = new HashMap<String, String>();
// 声明一个公共的图标映射对象,
public HashMap<Long, Bitmap> mIconMap = new HashMap<Long, Bitmap>();
// 利用单例模式获取当前应用的唯一实例
public static MainApplication getInstance() {
return mApp;
}
@Override
public void onCreate() {
super.onCreate();
// 在打开应用时对静态的应用实例赋值
mApp = this;
Log.d(TAG, "onCreate");
}
@Override
public void onTerminate() {
Log.d(TAG, "onTerminate");
super.onTerminate();
}
}
在AndroidManifest.xml中注册新定义的Application类名
,即在application节点中增加android:name属性,值为.MainApplication
;表示application的入口代码
是MainApplication.java.
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:name=".MainApplication">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name=".provider.UserInfoProvider"
android:authorities="com.example.storage.provider.UserInfoProvider"
android:enabled="true"
android:exported="true" />
</application>
- 在Activity中调用MainApplication的getlnstance方法,获得MainApplication的一个静态对象,通过该对象访问MainApplication的公共变量和公共方法。
package com.example.storage;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;
import com.example.storage.util.DateUtil;
public class AppWriteActivity extends AppCompatActivity implements OnClickListener {
private EditText et_name;
private EditText et_age;
private EditText et_height;
private EditText et_weight;
private boolean bMarried = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_app_write);
et_name = findViewById(R.id.et_name);
et_age = findViewById(R.id.et_age);
et_height = findViewById(R.id.et_height);
et_weight = findViewById(R.id.et_weight);
findViewById(R.id.btn_save).setOnClickListener(this);
initTypeSpinner();
}
// 初始化婚姻状况的下拉框
private void initTypeSpinner() {
ArrayAdapter<String> typeAdapter = new ArrayAdapter<String>(this,
R.layout.item_select, typeArray);
typeAdapter.setDropDownViewResource(R.layout.item_dropdown);
Spinner sp_married = findViewById(R.id.sp_married);
sp_married.setPrompt("请选择婚姻状况");
sp_married.setAdapter(typeAdapter);
sp_married.setSelection(0);
sp_married.setOnItemSelectedListener(new TypeSelectedListener());
}
private String[] typeArray = {"未婚", "已婚"};
class TypeSelectedListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
bMarried = (arg2 == 0) ? false : true;
}
public void onNothingSelected(AdapterView<?> arg0) {
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_save) {
String name = et_name.getText().toString();
String age = et_age.getText().toString();
String height = et_height.getText().toString();
String weight = et_weight.getText().toString();
if (TextUtils.isEmpty(name)) {
showToast("请先填写姓名");
return;
} else if (TextUtils.isEmpty(age)) {
showToast("请先填写年龄");
return;
} else if (TextUtils.isEmpty(height)) {
showToast("请先填写身高");
return;
} else if (TextUtils.isEmpty(weight)) {
showToast("请先填写体重");
return;
}
// 获取当前应用的Application实例
MainApplication app = MainApplication.getInstance();
// 以下直接修改Application实例中保存的映射全局变量
app.mInfoMap.put("name", name);
app.mInfoMap.put("age", age);
app.mInfoMap.put("height", height);
app.mInfoMap.put("weight", weight);
app.mInfoMap.put("married", typeArray[!bMarried ? 0 : 1]);
app.mInfoMap.put("update_time", DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss"));
showToast("数据已写入全局内存");
}
}
private void showToast(String desc) {
Toast.makeText(this, desc, Toast.LENGTH_SHORT).show();
}
}
package com.example.storage;
import java.util.Map;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class AppReadActivity extends AppCompatActivity {
private TextView tv_app;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_app_read);
tv_app = findViewById(R.id.tv_app);
readAppMemory();
}
// 读取全局内存中保存的变量信息
private void readAppMemory() {
String desc = "全局内存中保存的信息如下:";
// 获取当前应用的Application实例
MainApplication app = MainApplication.getInstance();
// 获取Application实例中保存的映射全局变量
Map<String, String> mapParam = app.mInfoMap;
// 遍历映射全局变量内部的键值对信息
for (Map.Entry<String, String> item_map : mapParam.entrySet()) {
desc = String.format("%s\n %s的取值为%s",
desc, item_map.getKey(), item_map.getValue());
}
if (mapParam.size() <= 0) {
desc = "全局内存中保存的信息为空";
}
tv_app.setText(desc);
}
}
5. 内容提供与处理
Android号称提供了4大组件,分别是:
页面Activity
广播Broadcast
服务Service
内容提供器ContentProvider
- 跟数据存取有关的组件,完整的内容组件由
内容提供器 ContentProvider
、内容解析器 ContentResolver
、内容观察器 ContentObserver
这三部分组成
- 跟数据存取有关的组件,完整的内容组件由
5.1 内容提供器 ContentProvider
在实际编码中,ContentProvider只是一个服务端的数据存取接口
,开发者需要在其基础上实现一个具体类,并重写以下相关数据库管理方法
。
onCreate
:创建数据库并获得数据库连接query
:查询数据insert
:插入数据update
:更新数据delete
:删除数据getType
:获取数据类型
5.1.1 实践案例
既然内容提供器是四大组件之一,就得在AndroidManifest.xml中注册它的定义,并开放外部访问权限,注册代码如下:
<provider
android:name=".provider.UserInfoProvider"
android:authorities="com.example.storage.provider.UserInfoProvider"
android:enabled="true"
android:exported="true" />
注册完毕后就完成了服务端App的封装工作
,接下来可由其他App进行数据存取
。
- UserInfo类相关代码
package com.example.storage.bean;
public class UserInfo {
public long rowid;
public int xuhao;
public String name;
public int age;
public long height;
public float weight;
public boolean married;
public String update_time;
public String phone;
public String password;
public UserInfo() {
rowid = 0L;
xuhao = 0;
name = "";
age = 0;
height = 0L;
weight = 0.0f;
married = false;
update_time = "";
phone = "";
password = "";
}
}
- UserDBHelper 类:数据库操作
package com.example.storage.database;
import java.util.ArrayList;
import com.example.storage.bean.UserInfo;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
@SuppressLint("DefaultLocale")
public class UserDBHelper extends SQLiteOpenHelper {
private static final String TAG = "UserDBHelper";
private static final String DB_NAME = "user.db"; // 数据库的名称
private static final int DB_VERSION = 1; // 数据库的版本号
private static UserDBHelper mHelper = null; // 数据库帮助器的实例
private SQLiteDatabase mDB = null; // 数据库的实例
public static final String TABLE_NAME = "user_info"; // 表的名称
private UserDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
private UserDBHelper(Context context, int version) {
super(context, DB_NAME, null, version);
}
// 利用单例模式获取数据库帮助器的唯一实例
public static UserDBHelper getInstance(Context context, int version) {
if (version > 0 && mHelper == null) {
mHelper = new UserDBHelper(context, version);
} else if (mHelper == null) {
mHelper = new UserDBHelper(context);
}
return mHelper;
}
// 打开数据库的读连接
public SQLiteDatabase openReadLink() {
if (mDB == null || !mDB.isOpen()) {
mDB = mHelper.getReadableDatabase();
}
return mDB;
}
// 打开数据库的写连接
public SQLiteDatabase openWriteLink() {
if (mDB == null || !mDB.isOpen()) {
mDB = mHelper.getWritableDatabase();
}
return mDB;
}
// 关闭数据库连接
public void closeLink() {
if (mDB != null && mDB.isOpen()) {
mDB.close();
mDB = null;
}
}
// 创建数据库,执行建表语句
public void onCreate(SQLiteDatabase db) {
Log.d(TAG, "onCreate");
String drop_sql = "DROP TABLE IF EXISTS " + TABLE_NAME + ";";
Log.d(TAG, "drop_sql:" + drop_sql);
db.execSQL(drop_sql);
String create_sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
+ "name VARCHAR NOT NULL," + "age INTEGER NOT NULL,"
+ "height LONG NOT NULL," + "weight FLOAT NOT NULL,"
+ "married INTEGER NOT NULL," + "update_time VARCHAR NOT NULL"
//演示数据库升级时要先把下面这行注释
+ ",phone VARCHAR" + ",password VARCHAR"
+ ");";
Log.d(TAG, "create_sql:" + create_sql);
db.execSQL(create_sql);
}
// 修改数据库,执行表结构变更语句
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d(TAG, "onUpgrade oldVersion=" + oldVersion + ", newVersion=" + newVersion);
if (newVersion > 1) {
//Android的ALTER命令不支持一次添加多列,只能分多次添加
String alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "phone VARCHAR;";
Log.d(TAG, "alter_sql:" + alter_sql);
db.execSQL(alter_sql);
alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "password VARCHAR;";
Log.d(TAG, "alter_sql:" + alter_sql);
db.execSQL(alter_sql);
}
}
// 根据指定条件删除表记录
public int delete(String condition) {
// 执行删除记录动作,该语句返回删除记录的数目
return mDB.delete(TABLE_NAME, condition, null);
}
// 删除该表的所有记录
public int deleteAll() {
// 执行删除记录动作,该语句返回删除记录的数目
return mDB.delete(TABLE_NAME, "1=1", null);
}
// 往该表添加一条记录
public long insert(UserInfo info) {
ArrayList<UserInfo> infoArray = new ArrayList<UserInfo>();
infoArray.add(info);
return insert(infoArray);
}
// 往该表添加多条记录
public long insert(ArrayList<UserInfo> infoArray) {
long result = -1;
for (int i = 0; i < infoArray.size(); i++) {
UserInfo info = infoArray.get(i);
ArrayList<UserInfo> tempArray = new ArrayList<UserInfo>();
// 如果存在同名记录,则更新记录
// 注意条件语句的等号后面要用单引号括起来
if (info.name != null && info.name.length() > 0) {
String condition = String.format("name='%s'", info.name);
tempArray = query(condition);
if (tempArray.size() > 0) {
update(info, condition);
result = tempArray.get(0).rowid;
continue;
}
}
// 如果存在同样的手机号码,则更新记录
if (info.phone != null && info.phone.length() > 0) {
String condition = String.format("phone='%s'", info.phone);
tempArray = query(condition);
if (tempArray.size() > 0) {
update(info, condition);
result = tempArray.get(0).rowid;
continue;
}
}
// 不存在唯一性重复的记录,则插入新记录
ContentValues cv = new ContentValues();
cv.put("name", info.name);
cv.put("age", info.age);
cv.put("height", info.height);
cv.put("weight", info.weight);
cv.put("married", info.married);
cv.put("update_time", info.update_time);
cv.put("phone", info.phone);
cv.put("password", info.password);
// 执行插入记录动作,该语句返回插入记录的行号
result = mDB.insert(TABLE_NAME, "", cv);
// 添加成功后返回行号,失败后返回-1
if (result == -1) {
return result;
}
}
return result;
}
// 根据条件更新指定的表记录
public int update(UserInfo info, String condition) {
ContentValues cv = new ContentValues();
cv.put("name", info.name);
cv.put("age", info.age);
cv.put("height", info.height);
cv.put("weight", info.weight);
cv.put("married", info.married);
cv.put("update_time", info.update_time);
cv.put("phone", info.phone);
cv.put("password", info.password);
// 执行更新记录动作,该语句返回记录更新的数目
return mDB.update(TABLE_NAME, cv, condition, null);
}
public int update(UserInfo info) {
// 执行更新记录动作,该语句返回记录更新的数目
return update(info, "rowid=" + info.rowid);
}
// 根据指定条件查询记录,并返回结果数据队列
public ArrayList<UserInfo> query(String condition) {
String sql = String.format("select rowid,_id,name,age,height,weight,married,update_time," +
"phone,password from %s where %s;", TABLE_NAME, condition);
Log.d(TAG, "query sql: " + sql);
ArrayList<UserInfo> infoArray = new ArrayList<UserInfo>();
// 执行记录查询动作,该语句返回结果集的游标
Cursor cursor = mDB.rawQuery(sql, null);
// 循环取出游标指向的每条记录
while (cursor.moveToNext()) {
UserInfo info = new UserInfo();
info.rowid = cursor.getLong(0); // 取出长整型数
info.xuhao = cursor.getInt(1); // 取出整型数
info.name = cursor.getString(2); // 取出字符串
info.age = cursor.getInt(3);
info.height = cursor.getLong(4);
info.weight = cursor.getFloat(5); // 取出浮点数
//SQLite没有布尔型,用0表示false,用1表示true
info.married = (cursor.getInt(6) == 0) ? false : true;
info.update_time = cursor.getString(7);
info.phone = cursor.getString(8);
info.password = cursor.getString(9);
infoArray.add(info);
}
cursor.close(); // 查询完毕,关闭游标
return infoArray;
}
// 根据手机号码查询指定记录
public UserInfo queryByPhone(String phone) {
UserInfo info = null;
ArrayList<UserInfo> infoArray = query(String.format("phone='%s'", phone));
if (infoArray.size() > 0) {
info = infoArray.get(0);
}
return info;
}
}
- UserInfoProvider类:UserInfo内容提供者
package com.example.storage.provider;
import com.example.storage.database.UserDBHelper;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
public class UserInfoProvider extends ContentProvider {
private final static String TAG = "UserInfoProvider";
private UserDBHelper userDB; // 声明一个用户数据库的帮助器对象
public static final int USER_INFO = 1; // Uri匹配时的代号
public static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static { // 往Uri匹配器中添加指定的数据路径
uriMatcher.addURI(UserInfoContent.AUTHORITIES, "/user", USER_INFO);
}
// 根据指定条件删除数据
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
if (uriMatcher.match(uri) == USER_INFO) {
// 获取SQLite数据库的写连接
SQLiteDatabase db = userDB.getWritableDatabase();
// 执行SQLite的删除操作,返回删除记录的数目
count = db.delete(UserInfoContent.TABLE_NAME, selection, selectionArgs);
db.close(); // 关闭SQLite数据库连接
}
return count;
}
// 插入数据
@Override
public Uri insert(Uri uri, ContentValues values) {
Uri newUri = uri;
if (uriMatcher.match(uri) == USER_INFO) {
// 获取SQLite数据库的写连接
SQLiteDatabase db = userDB.getWritableDatabase();
// 向指定的表插入数据,返回记录的行号
long rowId = db.insert(UserInfoContent.TABLE_NAME, null, values);
if (rowId > 0) { // 判断插入是否执行成功
// 如果添加成功,就利用新记录的行号生成新的地址
newUri = ContentUris.withAppendedId(UserInfoContent.CONTENT_URI, rowId);
// 通知监听器,数据已经改变
getContext().getContentResolver().notifyChange(newUri, null);
}
db.close(); // 关闭SQLite数据库连接
}
return uri;
}
// 创建ContentProvider时调用,可在此获取具体的数据库帮助器实例
@Override
public boolean onCreate() {
userDB = UserDBHelper.getInstance(getContext(), 1);
return true;
}
// 根据指定条件查询数据库
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Cursor cursor = null;
if (uriMatcher.match(uri) == USER_INFO) {
// 获取SQLite数据库的读连接
SQLiteDatabase db = userDB.getReadableDatabase();
// 执行SQLite的查询操作
cursor = db.query(UserInfoContent.TABLE_NAME,
projection, selection, selectionArgs, null, null, sortOrder);
// 设置内容解析器的监听
cursor.setNotificationUri(getContext().getContentResolver(), uri);
}
return cursor;
}
// 获取Uri数据的访问类型,暂未实现
@Override
public String getType(Uri uri) {
throw new UnsupportedOperationException("Not yet implemented");
}
// 更新数据,暂未实现
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
- 页面Activity: ContentProviderActivity
package com.example.storage;
import java.util.ArrayList;
import com.example.storage.bean.UserInfo;
import com.example.storage.provider.UserInfoContent;
import com.example.storage.util.DateUtil;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.TextView;
@SuppressLint("DefaultLocale")
public class ContentProviderActivity extends AppCompatActivity implements OnClickListener {
private static final String TAG = "ContentProviderActivity";
private EditText et_name;
private EditText et_age;
private EditText et_height;
private EditText et_weight;
private TextView tv_read_user;
private String mUserCount = "";
private String mUserResult = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content_provider);
et_name = findViewById(R.id.et_name);
et_age = findViewById(R.id.et_age);
et_height = findViewById(R.id.et_height);
et_weight = findViewById(R.id.et_weight);
tv_read_user = findViewById(R.id.tv_read_user);
findViewById(R.id.btn_add_user).setOnClickListener(this);
tv_read_user.setOnClickListener(this);
showUserInfo();
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_add_user) {
UserInfo user = new UserInfo();
user.name = et_name.getText().toString().trim();
user.age = Integer.parseInt(et_age.getText().toString().trim());
user.height = Integer.parseInt(et_height.getText().toString().trim());
user.weight = Float.parseFloat(et_weight.getText().toString().trim());
addUser(getContentResolver(), user);
showUserInfo();
} else if (v.getId() == R.id.tv_read_user) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(mUserCount);
builder.setMessage(mUserResult);
builder.setPositiveButton("确定", null);
builder.create().show();
}
}
// 显示所有用户信息
private void showUserInfo() {
mUserResult = readAllUser(getContentResolver());
String[] split = mUserResult.split("\n");
int count = (!mUserResult.contains("\n")) ? 0 : split.length;
mUserCount = String.format("当前共找到%d位用户信息", count);
tv_read_user.setText(mUserCount);
}
// 添加一条用户记录
private void addUser(ContentResolver resolver, UserInfo user) {
ContentValues name = new ContentValues();
name.put("name", user.name);
name.put("age", user.age);
name.put("height", user.height);
name.put("weight", user.weight);
name.put("married", false);
name.put("update_time", DateUtil.getNowDateTime(""));
// 通过内容解析器往指定Uri中添加用户信息
resolver.insert(UserInfoContent.CONTENT_URI, name);
}
// 读取所有的用户记录
private String readAllUser(ContentResolver resolver) {
ArrayList<UserInfo> userArray = new ArrayList<UserInfo>();
// 通过内容解析器从指定Uri中获取用户记录的游标
Cursor cursor = resolver.query(UserInfoContent.CONTENT_URI, null, null, null, null);
// 循环取出游标指向的每条用户记录
while (cursor.moveToNext()) {
UserInfo user = new UserInfo();
user.name = cursor.getString(cursor.getColumnIndex(UserInfoContent.USER_NAME));
user.age = cursor.getInt(cursor.getColumnIndex(UserInfoContent.USER_AGE));
user.height = cursor.getInt(cursor.getColumnIndex(UserInfoContent.USER_HEIGHT));
user.weight = cursor.getFloat(cursor.getColumnIndex(UserInfoContent.USER_WEIGHT));
userArray.add(user); // 添加到用户信息队列
}
cursor.close(); // 关闭数据库游标
String result = "";
for (UserInfo user : userArray) {
// 遍历用户信息队列,逐个拼接到结果字符串
result = String.format("%s%s 年龄%d 身高%d 体重%f\n", result,
user.name, user.age, user.height, user.weight);
}
Log.d(TAG, "result=" + result);
return result;
}
}
5.2 内容解析器 ContentResolver
前面利用ContentProvider实现服务端App的数据封装。
如果客户端App想访问对方的内部数据
,就要通过内容解析器ContentResolver访问
。
- 内容解析器是
客户端App操作服务端数据的工具
,相对应的内容提供器是服务端的数据接口。 - 要获取ContentResolver对象,在Activity代码中调用
getContentResolver方法
即可 - ContentResolver提供的方法与ContentProvider是对应的
5.2.1 相关实例
- 在实际开发中,普通App很少会开放数据接口给其他应用访问,作为服务端接口的
ContentProvider基本用不到
。 - 内容组件能够派上用场的情况往往是
App想要访问系统应用的通信数据
,比如查看联系人、短信、通话记录,以及对这些通信信息进行增、删、改、查。
package com.example.storage.util;
import java.util.ArrayList;
import com.example.storage.bean.CallRecord;
import com.example.storage.bean.Contact;
import com.example.storage.bean.SmsContent;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.CallLog;
import android.provider.ContactsContract;
import android.provider.Telephony;
import android.util.Log;
@SuppressLint("DefaultLocale")
public class CommunicationUtil {
private final static String TAG = "CommunicationUtil";
private static Uri mContactUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
private static String[] mContactColumn = new String[]{
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME};
// 读取手机保存的联系人数量
public static int readPhoneContacts(ContentResolver resolver) {
ArrayList<Contact> contactArray = new ArrayList<Contact>();
Cursor cursor = resolver.query(mContactUri, mContactColumn, null, null, null);
while (cursor.moveToNext()) {
Contact contact = new Contact();
contact.phone = cursor.getString(0).replace("+86", "").replace(" ", "");
contact.name = cursor.getString(1);
Log.d(TAG, contact.name + " " + contact.phone);
contactArray.add(contact);
}
cursor.close();
return contactArray.size();
}
// 读取sim卡保存的联系人数量
public static int readSimContacts(ContentResolver resolver) {
Uri simUri = Uri.parse("content://icc/adn");
ArrayList<Contact> contactArray = new ArrayList<Contact>();
Cursor cursor = resolver.query(simUri, mContactColumn, null, null, null);
while (cursor.moveToNext()) {
Contact contact = new Contact();
contact.phone = cursor.getString(0).replace("+86", "").replace(" ", "");
contact.name = cursor.getString(1);
Log.d(TAG, contact.name + " " + contact.phone);
contactArray.add(contact);
}
cursor.close();
return contactArray.size();
}
// 往手机中添加一个联系人信息(包括姓名、电话号码、电子邮箱)
public static void addContacts(ContentResolver resolver, Contact contact) {
// 构建一个指向系统联系人提供器的Uri对象
Uri raw_uri = Uri.parse("content://com.android.contacts/raw_contacts");
ContentValues values = new ContentValues(); // 创建新的配对
// 往 raw_contacts 添加联系人记录,并获取添加后的联系人编号
long contactId = ContentUris.parseId(resolver.insert(raw_uri, values));
// 构建一个指向系统联系人数据的Uri对象
Uri uri = Uri.parse("content://com.android.contacts/data");
ContentValues name = new ContentValues(); // 创建新的配对
name.put("raw_contact_id", contactId); // 往配对添加联系人编号
// 往配对添加“姓名”的数据类型
name.put("mimetype", "vnd.android.cursor.item/name");
name.put("data2", contact.name); // 往配对添加联系人的姓名
resolver.insert(uri, name); // 往提供器添加联系人的姓名记录
ContentValues phone = new ContentValues(); // 创建新的配对
phone.put("raw_contact_id", contactId); // 往配对添加联系人编号
// 往配对添加“电话号码”的数据类型
phone.put("mimetype", "vnd.android.cursor.item/phone_v2");
phone.put("data1", contact.phone); // 往配对添加联系人的电话号码
phone.put("data2", "2"); // 联系类型。1表示家庭,2表示工作
resolver.insert(uri, phone); // 往提供器添加联系人的号码记录
ContentValues email = new ContentValues(); // 创建新的配对
email.put("raw_contact_id", contactId); // 往配对添加联系人编号
// 往配对添加“电子邮箱”的数据类型
email.put("mimetype", "vnd.android.cursor.item/email_v2");
email.put("data1", contact.email); // 往配对添加联系人的电子邮箱
email.put("data2", "2"); // 联系类型。1表示家庭,2表示工作
resolver.insert(uri, email); // 往提供器添加联系人的邮箱记录
}
// 往手机中一次性添加一个联系人信息(包括主记录、姓名、电话号码、电子邮箱)
public static void addFullContacts(ContentResolver resolver, Contact contact) {
// 构建一个指向系统联系人提供器的Uri对象
Uri raw_uri = Uri.parse("content://com.android.contacts/raw_contacts");
// 构建一个指向系统联系人数据的Uri对象
Uri uri = Uri.parse("content://com.android.contacts/data");
// 创建一个插入联系人主记录的内容操作器
ContentProviderOperation op_main = ContentProviderOperation
.newInsert(raw_uri).withValue("account_name", null).build();
// 创建一个插入联系人姓名记录的内容操作器
ContentProviderOperation op_name = ContentProviderOperation
.newInsert(uri).withValueBackReference("raw_contact_id", 0)
.withValue("mimetype", "vnd.android.cursor.item/name")
.withValue("data2", contact.name).build();
// 创建一个插入联系人电话号码记录的内容操作器
ContentProviderOperation op_phone = ContentProviderOperation
.newInsert(uri).withValueBackReference("raw_contact_id", 0)
.withValue("mimetype", "vnd.android.cursor.item/phone_v2")
.withValue("data2", "2").withValue("data1", contact.phone).build();
// 创建一个插入联系人电子邮箱记录的内容操作器
ContentProviderOperation op_email = ContentProviderOperation
.newInsert(uri).withValueBackReference("raw_contact_id", 0)
.withValue("mimetype", "vnd.android.cursor.item/email_v2")
.withValue("data2", "2").withValue("data1", contact.email).build();
// 声明一个内容操作器的队列,并将上面四个操作器添加到该队列中
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
operations.add(op_main);
operations.add(op_name);
operations.add(op_phone);
operations.add(op_email);
try {
// 批量提交四个内容操作器所做的修改
resolver.applyBatch("com.android.contacts", operations);
} catch (Exception e) {
e.printStackTrace();
}
}
//private static Uri mSmsUri = Uri.parse("content://sms"); //该地址表示所有短信,包括收件箱和发件箱
//private static Uri mSmsUri = Uri.parse("content://sms/inbox"); //该地址为收件箱
private static Uri mSmsUri;
private static String[] mSmsColumn;
// 读取指定号码发来的短信记录
@TargetApi(Build.VERSION_CODES.KITKAT)
public static int readSms(ContentResolver resolver, String phone, int gaps) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mSmsUri = Telephony.Sms.Inbox.CONTENT_URI;
} else {
mSmsUri = Uri.parse("content://sms/inbox");
}
mSmsColumn = new String[]{
Telephony.Sms.ADDRESS, Telephony.Sms.PERSON,
Telephony.Sms.BODY, Telephony.Sms.DATE,
Telephony.Sms.TYPE};
ArrayList<SmsContent> smsArray = new ArrayList<SmsContent>();
String selection = "";
if (phone != null && phone.length() > 0) {
selection = String.format("address='%s'", phone);
}
if (gaps > 0) {
selection = String.format("%s%sdate>%d", selection,
(selection.length() > 0) ? " and " : "", System.currentTimeMillis() - gaps * 1000);
}
Cursor cursor = resolver.query(mSmsUri, mSmsColumn, selection, null, "date desc");
while (cursor.moveToNext()) {
SmsContent sms = new SmsContent();
sms.address = cursor.getString(0);
sms.person = cursor.getString(1);
sms.body = cursor.getString(2);
sms.date = DateUtil.formatDate(cursor.getLong(3));
sms.type = cursor.getInt(4); //type=1表示收到的短信,type=2表示发送的短信
Log.d(TAG, sms.address + " " + sms.person + " " + sms.date + " " + sms.type + " " + sms.body);
smsArray.add(sms);
}
cursor.close();
return smsArray.size();
}
private static Uri mRecordUri = CallLog.Calls.CONTENT_URI;
private static String[] mRecordColumn = new String[]{
CallLog.Calls.CACHED_NAME, CallLog.Calls.NUMBER, CallLog.Calls.TYPE,
CallLog.Calls.DATE, CallLog.Calls.DURATION, CallLog.Calls.NEW};
// 读取规定时间内的通话记录
public static int readCallRecord(ContentResolver resolver, int gaps) {
ArrayList<CallRecord> recordArray = new ArrayList<CallRecord>();
String selection = String.format("date>%d", System.currentTimeMillis() - gaps * 1000);
Cursor cursor = resolver.query(mRecordUri, mRecordColumn, selection, null, "date desc");
while (cursor.moveToNext()) {
CallRecord record = new CallRecord();
record.name = cursor.getString(0);
record.phone = cursor.getString(1);
record.type = cursor.getInt(2); //type=1表示接听,2表示拨出,3表示未接
record.date = DateUtil.formatDate(cursor.getLong(3));
record.duration = cursor.getLong(4);
record._new = cursor.getInt(5);
Log.d(TAG, record.name + " " + record.phone + " " + record.type + " " + record.date + " " + record.duration);
recordArray.add(record);
}
cursor.close();
return recordArray.size();
}
// 读取所有的联系人信息
public static String readAllContacts(ContentResolver resolver) {
ArrayList<Contact> contactArray = new ArrayList<Contact>();
Cursor cursor = resolver.query(
ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
int contactIdIndex = 0;
int nameIndex = 0;
if (cursor.getCount() > 0) {
contactIdIndex = cursor.getColumnIndex(ContactsContract.Contacts._ID);
nameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
}
while (cursor.moveToNext()) {
Contact contact = new Contact();
contact.contactId = cursor.getString(contactIdIndex);
contact.name = cursor.getString(nameIndex);
contactArray.add(contact);
}
cursor.close();
for (int i = 0; i < contactArray.size(); i++) {
Contact contact = contactArray.get(i);
contact.phone = getColumn(resolver, contact.contactId,
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.CommonDataKinds.Phone.NUMBER);
contact.email = getColumn(resolver, contact.contactId,
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
ContactsContract.CommonDataKinds.Email.CONTACT_ID,
ContactsContract.CommonDataKinds.Email.DATA);
contactArray.set(i, contact);
Log.d(TAG, contact.contactId + " " + contact.name + " " + contact.phone + " " + contact.email);
}
String result = "";
for (Contact contact : contactArray) {
result = String.format("%s%s %s %s\n", result, contact.name, contact.phone, contact.email);
}
return result;
}
private static String getColumn(ContentResolver resolver, String contactId,
Uri uri, String selection, String column) {
Cursor cursor = resolver.query(uri, null, selection + "=" + contactId, null, null);
int index = 0;
if (cursor.getCount() > 0) {
index = cursor.getColumnIndex(column);
}
String value = "";
while (cursor.moveToNext()) {
value = cursor.getString(index);
}
cursor.close();
return value;
}
}
5.3 内容观察器 ContentObserver
5.3.1 ContentObserver使用场景
ContentResolver
获取数据采用的是主动查询方式
,有查询就有数据,没查询就没数据。
有时我们不但要获取以往的数据,还要实时获取新增的数据,最常见的业务场景是短信验证码。电商App经常在用户注册或付款时发送验证码短信,为了给用户省事,App通常会监控手机刚收到的验证码数字,并自动填入验证码输入框
。
这时就用到了内容观察器ContentObserver,给目标内容注册一个观察器,目标内容的数据一旦发生变化,观察器规定好的动作马上触发,从而执行开发者预先定义的代码。
5.3.2 ContentObserver相关方法
内容观察器的用法与内容提供器类似
,也要从ContentObserver派生一个观察器类,然后通过ContentResolver对象
调用相应的方法注册或注销
观察器。
相关方法如下:
- registerContentObserver:注册内容观察器
- unregisterContentObserver:注销内容观察器。
- notifyChange:通知内容观察器发生了数据变化。
5.3.3 ContentObserver相关实例
- 手机App监控10086发送的短信内容,自动获取手机号码的月流量额度,实现流量校准
package com.example.storage;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.telephony.SmsManager;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.Toast;
/**
* Created by ouyangshen on 2017/12/4.
*/
@SuppressLint("DefaultLocale")
public class ContentObserverActivity extends AppCompatActivity implements OnClickListener {
private static final String TAG = "ContentObserverActivity";
private static TextView tv_check_flow;
private static String mCheckResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content_observer);
tv_check_flow = findViewById(R.id.tv_check_flow);
tv_check_flow.setOnClickListener(this);
findViewById(R.id.btn_check_flow).setOnClickListener(this);
initSmsObserver();
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_check_flow) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "请先给当前APP开启短信权限", Toast.LENGTH_LONG).show();
} else {
//查询数据流量,移动号码的查询方式为发送短信内容“18”给“10086”
//电信和联通号码的短信查询方式请咨询当地运营商客服热线
//跳到系统的短信发送页面,由用户手工发短信
//sendSmsManual("10086", "18");
//无需用户操作,自动发送短信
sendSmsAuto("10086", "18");
}
} else if (v.getId() == R.id.tv_check_flow) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("收到流量校准短信");
builder.setMessage(mCheckResult);
builder.setPositiveButton("确定", null);
builder.create().show();
}
}
// 跳到系统的短信发送页面,由用户手工编辑与发送短信
public void sendSmsManual(String phoneNumber, String message) {
Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:" + phoneNumber));
intent.putExtra("sms_body", message);
startActivity(intent);
}
// 短信发送事件
private String SENT_SMS_ACTION = "com.example.storage.SENT_SMS_ACTION";
// 短信接收事件
private String DELIVERED_SMS_ACTION = "com.example.storage.DELIVERED_SMS_ACTION";
// 无需用户操作,由App自动发送短信
public void sendSmsAuto(String phoneNumber, String message) {
// 以下指定短信发送事件的详细信息
Intent sentIntent = new Intent(SENT_SMS_ACTION);
sentIntent.putExtra("phone", phoneNumber);
sentIntent.putExtra("message", message);
PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, sentIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// 以下指定短信接收事件的详细信息
Intent deliverIntent = new Intent(DELIVERED_SMS_ACTION);
deliverIntent.putExtra("phone", phoneNumber);
deliverIntent.putExtra("message", message);
PendingIntent deliverPI = PendingIntent.getBroadcast(this, 1, deliverIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// 获取默认的短信管理器
SmsManager smsManager = SmsManager.getDefault();
// 开始发送短信内容。要确保打开发送短信的完全权限,不是那种还需提示的不完整权限
smsManager.sendTextMessage(phoneNumber, null, message, sentPI, deliverPI);
}
private Handler mHandler = new Handler(); // 声明一个处理器对象
private SmsGetObserver mObserver; // 声明一个短信获取的观察器对象
private static Uri mSmsUri; // 声明一个系统短信提供器的Uri对象
private static String[] mSmsColumn; // 声明一个短信记录的字段数组
// 初始化短信观察器
private void initSmsObserver() {
//mSmsUri = Uri.parse("content://sms/inbox");
//Android5.0之后似乎无法单独观察某个信箱,只能监控整个短信
mSmsUri = Uri.parse("content://sms");
mSmsColumn = new String[]{"address", "body", "date"};
// 创建一个短信观察器对象
mObserver = new SmsGetObserver(this, mHandler);
// 给指定Uri注册内容观察器,一旦Uri内部发生数据变化,就触发观察器的onChange方法
getContentResolver().registerContentObserver(mSmsUri, true, mObserver);
}
// 在页面销毁时触发
protected void onDestroy() {
// 注销内容观察器
getContentResolver().unregisterContentObserver(mObserver);
super.onDestroy();
}
// 定义一个短信获取的观察器
private static class SmsGetObserver extends ContentObserver {
private Context mContext; // 声明一个上下文对象
public SmsGetObserver(Context context, Handler handler) {
super(handler);
mContext = context;
}
// 观察到短信的内容提供器发生变化时触发
public void onChange(boolean selfChange) {
String sender = "", content = "";
// 构建一个查询短信的条件语句,这里使用移动号码测试,故而查找10086发来的短信
String selection = String.format("address='10086' and date>%d", System.currentTimeMillis() - 1000 * 60 * 60);
// 通过内容解析器获取符合条件的结果集游标
Cursor cursor = mContext.getContentResolver().query(
mSmsUri, mSmsColumn, selection, null, " date desc");
// 循环取出游标所指向的所有短信记录
while (cursor.moveToNext()) {
sender = cursor.getString(0);
content = cursor.getString(1);
break;
}
cursor.close(); // 关闭数据库游标
mCheckResult = String.format("发送号码:%s\n短信内容:%s", sender, content);
// 依次解析流量校准短信里面的各项流量数值,并拼接流量校准的结果字符串
String flow = String.format("流量校准结果如下:\n\t总流量为:%s\n\t已使用:%s" +
"\n\t剩余:%s", findFlow(content, "总流量为", ","),
findFlow(content, "已使用", "B"), findFlow(content, "剩余", "B"));
if (tv_check_flow != null) { // 离开该页面时就不再显示流量信息
// 把流量校准结果显示到文本视图tv_check_flow上面
tv_check_flow.setText(flow);
}
super.onChange(selfChange);
}
}
// 解析流量校准短信里面的流量数值
private static String findFlow(String sms, String begin, String end) {
int begin_pos = sms.indexOf(begin);
if (begin_pos < 0) {
return "未获取";
}
String sub_sms = sms.substring(begin_pos);
int end_pos = sub_sms.indexOf(end);
if (end_pos < 0) {
return "未获取";
}
if (end.equals(",")) {
return sub_sms.substring(begin.length(), end_pos);
} else {
return sub_sms.substring(begin.length(), end_pos + end.length());
}
}
}