进程的概念
进程可以看成一个Android 手机上的一个APP应用,也可以看成是window操作系统上面的一个软件。。
进程中可以包含多个线程,线程是cpu调度的最小单位。。
所谓进程间通信IPC,inter process communication,就是两个进程之间进行数据交换的过程。。不同的操作系统 ,进程间的通信方式也不同
Windows系统通过剪切板、管道等来进行进程间通信;
Linux系统通过命名管道、共享内存、信号量等来进行进程间通信
虽然Android系统的底层实现是Linux内核,但是关于进程间通信的这块不一样的实现方式。。。一种基于Linux内核的移动操作系统
为什么要使用进程
- App为了加大一个应用可使用的内存 所以需要多进程来获取更多份的内存空间。Android对单个应用的内存做了最大限制,不同设备有不同的大小。
2.两个APP之间进行数据交换,使用 ContentProvider
为啥要进程间通信
静态数据失效
单例模式失效
sp 的安全度降低
创建多个AppLication
同步机制完全失效
一个进程其实就相当于一个应用,一个应用对应一个虚拟机,一个虚拟机对应一块内存地址,多个进程就意味着多个虚拟机,多个内存地址,所以静态变量,单例模式肯定失效了。。。
SP 不支持多个进程同时进行写数据,因为它的底部实现是对xml文件进行读写,如果同时写,肯定会有一个数据失效。。。
重启一个进程,就相当于重启一个应用,重启应用,肯定创建新的Application
都不在一块内存地址,A进程锁自己对应的内存空间的数据对象,B进程用B内存空间内的数据,没有加锁,同步机制肯定失效了
综合以上的问题,所以需要进程间通信。。。
开启多进程的方式
在配置文件AndroidManifest中对四大组件设置Process属性。。。process内容有两种方式
:test,生成的进程名为当前应用包名:test--->com.android.top:test,这样形式生成的进程是当前应用的私有进程,外部应用不能访问
com.android.shadow.test 这种形式是一种完整的命名进程的方式,这样生成的进程是全局进程,可以和其他APP应用进行数据交换。。
进程通信基础知识点:序列化 ,binder
Serializable和Parcelable接口可以完成对象的序列化过程,比如通过Intent和Binder传输数据对象的时候,就需要对此对象进行序列化。。
Serializable接口
Serializable是Java提供的,只需要数据类实现该接口,不需要实现任何的方法
使用简单 开销很大,序列化和反序列化都需要大量的I/O操作,将对象序列化到纯属设备中或者将对象序列化后通过网络传输建议使用serializable。。。可以设置serialVersionUID,使序列化和反序列化更加安全。。
Parcelable接口
需要实现方法,序列化,反序列化,描述
/**
* 反序列化实现
*
* @param in
*/
protected Book(Parcel in) {
bookId = in.readLong();
bookName = in.readString();
}
/**
* 反序列化
*/
public static final Creator CREATOR = new Creator() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
/**
* 描述信息
*
* @return
*/
@Override
public int describeContents() {
return 0;
}
/**
* 序列化
*
* @param dest
* @param flags
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(bookId);
dest.writeString(bookName);
}
Parcel 内部包装了可序列化的数据,可以在Binder中自由传输
Parcelable是Android提供的序列化方式,使用麻烦 但是效率很好。主要用在内存序列化
Binder
Binder是Android的一个类,实现了IBinder接口
IPC角度:是Android中的一种跨进程通信方式
Android frameWork角度:是ServiceManager连接各种Manager和相应ManagerService的桥梁
应用层角度:客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据
多进程的通信方式
多进程通信可以是同一个APP内进行数据通信,也可以是不同的APP之间进行数据通信
Intent 传递数据
当我们在一个进程中启动一个进程activity service 和receiver ,就可以在bundle中附加需要传输给远程进程的信息,并通过intent发送出去,传送的数据必须能被序列化,比如基本类型,实现了parcelable 和serializable接口的对象
共享文件
通过文件共享来共享数据对文件格式没有任何具体要求,可以是文本,也可以是xml,只有读写双方约定好数据格式就好。。最大的局限性就是并发读书的问题
sharedPreference
messenger
可以在不同进程中传递message对象,在message中放入我们需要传递的数据,就可以轻松实现数据的进程间传递
底层实现是AIDL,一次只能处理一个请求,因此在服务端也不用考虑线程同步的问题。。
Messenger 主要就是用来传递信息的,不能互相调用方法。。。利用handle+message,通过Messenger sendMessage
服务端实现:
class MessageService : Service() {
//通过handle创建Messenger
private var mMessenger = Messenger(MessengerHandle())
inner class MessengerHandle : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
1 -> { //接收客户端传来的message
ToastUtil.showMsg(msg.data.getString("hh"))
val clientMessenger = msg.replyTo //客户端传来的messenger
val message = Message()
message.what = 1;
val bundle = Bundle();
//给客户端发送消息
bundle.putString("hh", "hello shadow,我是服务端,我看到你的消息啦,我给你回消息了,这是我的进程名字${getProcessName(this@MessageService)
}")
message.data = bundle
clientMessenger.send(message)
}
}
}
}
override fun onBind(intent: Intent): IBinder? {
return mMessenger.binder //返回Messenger的底层 binder
}
//获取当前 service 进程名
private fun getProcessName(cxt: Context): String? {
val pid = android.os.Process.myPid()
val am = cxt.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningApps = am.runningAppProcesses ?: return null
for (procInfo in runningApps) {
if (procInfo.pid == pid) {
return procInfo.processName
}
}
return null
}
}
客户端实现
tvMessenger.setOnClickListener {
val intent = Intent(this, MessageService::class.java)
//绑定服务
bindService(intent, conn, Context.BIND_AUTO_CREATE)
}
//收到服务端返回的信息
class ClientHandle : Handler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
if (msg == null)
return
when (msg.what) {
1 -> {
tvServiceReturnContent.text = msg.data.getString("hh")
}
}
}
}
//构建客户端的Messenger 用来传递给服务端,让服务端发送消息
val messenger = Messenger(ClientHandle())
//绑定服务实现
private val conn = object : ServiceConnection {
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
if (p1 == null)
return
val messageService = Messenger(p1) //收到服务端返回的binder,构建messenger,发送消息
val message = Message()
message.what = 1
val bundle = Bundle()
bundle.putString("hh", "hello shadow,我是客户端,我给你发消息了")
message.data = bundle
message.replyTo = messenger //把客户端的messenger传递给服务端,让服务端能够给客户端发送消息
messageService.send(message)//发送消息
}
//发送消息失败
override fun onServiceDisconnected(p0: ComponentName?) {
}
}
//获取客户端进程的名字
private fun getProcessName(cxt: Context): String? {
val pid = android.os.Process.myPid()
val am = cxt.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningApps = am.runningAppProcesses ?: return null
for (procInfo in runningApps) {
if (procInfo.pid == pid) {
return procInfo.processName
}
}
return null
}
}
AIDL
Android Interface Defind Language ,Android 接口定义语言。。messenger 主要就是传递信息,但是更多时候进程间通信,需要互相调用方法,messenger就不太能用了,这个时候就可以用AIDL来完成进程间通信。。。
AIDL的实现 稍微比较复杂,总结描述一下
1. 创建要传输数据的序列化实体类对应的aidl文件,不需要有内容
====Book.aidl====
package com.example.administrator.shadowapplication.aidl;
// Declare any non-default types here with import statements
parcelable Book;
2. 创建服务端需要实现方法的 AIDL接口,这个方法主要是为了给客户端调用的。。
==== BookRemoteInterface .aidl=====
package com.example.administrator.shadowapplication.aidl;
// Declare any non-default types here with import statements
//相应的类 需要手动导入import
import com.example.administrator.shadowapplication.aidl.Book;
import com.example.administrator.shadowapplication.IOnNewBookArrivedListener;
interface BookRemoteInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
//获取 列表
List getBookList();
// 添加图书
void addBook(in Book book);
//注册监听
void registerListener(IOnNewBookArrivedListener listener);
//取消注册
void unregisterListener(IOnNewBookArrivedListener listener);
}
=====IOnNewBookArrivedListener .aidl====
package com.example.administrator.shadowapplication;
// Declare any non-default types here with import statements
//手动导入相应的类
import com.example.administrator.shadowapplication.aidl.Book;
interface IOnNewBookArrivedListener {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
//客户端 方法监听 通知,观察者模式
void onNewBookArried(in Book book);
}
3. rebuild project,自动生成 aidl接口文件的interface 文件
public interface BookRemoteInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.administrator.shadowapplication.aidl.BookRemoteInterface
{
private static final java.lang.String DESCRIPTOR = "com.example.administrator.shadowapplication.aidl.BookRemoteInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}.........
4. 实现BookRemoteInterface 内部类Stub ,Stub 继承 Binder
public static abstract class Stub extends android.os.Binder implements com.example.administrator.shadowapplication.aidl.BookRemoteInterface
//实现AIDL 接口的方法
public class BookRemoteManagerImpl extends BookRemoteInterface.Stub {实现BookRemoteInterface 内部类Stub
// CopyOnWriteArrayList 支持同步读写
//RemoteCallbackList 进程间通信 保持不同进程间传递对象一致的类
private CopyOnWriteArrayList bookList ;
private RemoteCallbackList mListenerList ;
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
public BookRemoteManagerImpl(CopyOnWriteArrayList bookList,RemoteCallbackList mListenerList) {
this.bookList = bookList;
this.mListenerList = mListenerList;
}
/**
* 服务端的方法本身就运行在服务端Binder线程池中,所以服务端方法可以执行大量耗时操作
* 这个时候不要在服务端开启子线程执行异步任务
* @return
* @throws RemoteException
*/
@Override
public List getBookList() throws RemoteException {
return bookList;
}
@Override
public void addBook(Book book) throws RemoteException {
bookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
/* if (!mListenerList.contains(listener)){
mListenerList.add(listener);
}else {
Log.d("hh","already exists");
}
Log.d("hh","registerListener mListenerList.size = "+mListenerList.size());*/
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
/* if (mListenerList.contains(listener)){
mListenerList.remove(listener);
}else {
Log.d("hh","not found ,not can unregister");
}
Log.d("hh","unregisterListener mListenerList.size = "+mListenerList.size());*/
mListenerList.unregister(listener);
}
}
5. 创建服务类,实现 Service类,在 onBind 方法中返回 Stub的 实现类对象,并在配置文件中声明Service,设置process属性
public class BookManagerService extends Service {
private CopyOnWriteArrayList bookList = new CopyOnWriteArrayList<>();
//使用CopyOnWriteArrayList 存储listener对象不是惟一的,对象经过序列化会在客户端生成新的对象,
// 所以客户端和服务端的对象listener不是同一个
//private CopyOnWriteArrayList mListenerList =new CopyOnWriteArrayList<>();
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
//系统提供的,专门用于删除跨进程listener的接口,RemoteCallbackList并不是list
//当客户端进程终止的时候,能自动移除客户端注册的listener
//内部实现了线程同步功能,所以使用他进行注册和解除注册时候,不需要做额外的线程同步功能
private RemoteCallbackList mListenerList = new RemoteCallbackList<>();
@Override
public void onCreate() {
super.onCreate();
for (int i=0;i<3;i++){
Book book = new Book(i+1,"小王子"+i,10.5f) ;
bookList.add(book);
}
new Thread(new ServiceWork()).start();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new BookRemoteManagerImpl(bookList,mListenerList);
}
@Override
public void onDestroy() {
mIsServiceDestoryed.set(true);
super.onDestroy();
}
/**
* 每五秒添加一本新书
*/
public class ServiceWork implements Runnable{
@Override
public void run() {
while (!mIsServiceDestoryed.get()){
try {
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
int bookId = bookList.size()+1;
Book book = new Book(bookId,"小王子"+bookId,10.5f) ;
onNewBookArrived(book);
}
}
}
/**
* 添加新书
* 远程服务端调用客户端的listener中的方法时,被调用的方法也运行在Binder线程池,客户端的Binder线程池
* 不能在服务端,调用客户端比较耗时的方法,比如 listener.onNewBookArried(book);
* 如果onNewBookArried比较耗时,就需要开启新的线程
* @param book
*/
private void onNewBookArrived(Book book) {
bookList.add(book);
/*for (int i = 0; i< mListenerList.size();i++){
IOnNewBookArrivedListener listener = mListenerList.get(i);
try {
listener.onNewBookArried(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}*/
//beginBroadcast 和finishBroadcast 配对出现
final int listenerSize = mListenerList.beginBroadcast();
for (int i = 0; i< listenerSize ; i++){
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
if (listener != null){
try {
listener.onNewBookArried(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast();
}
}
6. 客户端的简单实现
public class AidlBindServiceActivity extends AppCompatActivity {
private Button bindService;
private TextView contentText;
private BookRemoteInterface bookRemoteInterface;
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MESSAGE_NEW_BOOK_ARRIVED :
Log.d("hh","receive new book:"+msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl_bind_service);
bindService = findViewById(R.id.bindService);
contentText = findViewById(R.id.contentText);
bindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(AidlBindServiceActivity.this,BookManagerService.class);
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
}
});
}
/**
* 客户端调用远程服务端的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程被挂起,
* 这个时候如果服务端的方法比较耗时,导致客户端线程长时间阻塞在这里,如果客户端是在UI线程调用方法,客户端就会出现ANR错误
* onServiceConnected,onServiceDisconnected都是运行在UI线程的
* 所以如果知道要调用的方法是比较耗时的,就要开启新的线程调用服务端的方法
*/
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bookRemoteInterface = BookRemoteInterface.Stub.asInterface(service);
try {
//客户端访问服务端的方法,如果服务端方法比较耗时,可以放在新的线程中调用
/* new Thread(new Runnable() {
@Override
public void run() {
List books = bookRemoteInterface.getBookList();
}
}).start();*/
if (bookRemoteInterface.getBookList() != null){
List books = bookRemoteInterface.getBookList();
StringBuilder builder = new StringBuilder();
for (Book book:books) {
builder.append(book.getBookId()+"--"+book.getBookName()+"---"+book.getBookPrice());
builder.append("\n\r");
}
contentText.setText(builder.toString());
}
bookRemoteInterface.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
bookRemoteInterface = null;
}
};
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener() {
@Override
public void onNewBookArried(Book book) throws RemoteException {
//此方法运行在客户端的Binder线程池中,所以需要handle 切换到UI线程
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,book).sendToTarget();
}
@Override
public IBinder asBinder() {
return null;
}
};
@Override
protected void onDestroy() {
if (bookRemoteInterface != null && bookRemoteInterface.asBinder().isBinderAlive()){
try {
bookRemoteInterface.unregisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(serviceConnection);
super.onDestroy();
}
}
7. 一些注意事项
服务端的方法 工作在Binder线程池中,如果在此方法中需要进行一些耗时操作,需要客户在新线程中调用,否则会出现ANR
服务端观察者 回调用客户端的方法的时候,客户端的回调方法执行在客户端的Binder线程池中,如果需要更新UI,需要切换到主线程;如果在此方法中执行耗时操作,需要服务端开启新的线程调用。。
ContentProvider
是Android提供的专门用于不同应用间进行数据共享的方式
具体实现方式: extends ContentProvider ,实现增删改查方法,数据载体可以是数据库,集合,文件等
onCreate 在主线程中,所以不能进行耗时操作
query,delete等方法执行在binder线程池中,所以会有线程同步的问题需要解决(如果数据载体是集合或者文件等,因为数据库本身就做了线程同步的问题,但是也仅限于一个数据的内容操作)。。
配置文件声明Provide,设置多进程:
Provider实现类,实现相应的方法,数据载体用的DataBase
/**
* @author 付影影
* @desc 内容提供者
* @date 2019/8/2
*/
public class BookContentProvider extends ContentProvider {
private static final String AUTHORITY = "com.android.shadow.book.provider";
private static final String BOOK_PATH = "book";
private static final String USER_PATH = "user";
public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + BOOK_PATH);
public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + USER_PATH);
public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
private static final UriMatcher mUrimatcher = new UriMatcher(UriMatcher.NO_MATCH);
/**
ContentProvider通过外界传来的Uri来区分外界要访问的数据集体
* 把 uri 和 uri_code 绑定起来
*/
static {
mUrimatcher.addURI(AUTHORITY, BOOK_PATH, BOOK_URI_CODE);
mUrimatcher.addURI(AUTHORITY, USER_PATH, USER_URI_CODE);
}
/**
* 根据传递过来的uri,匹配uri_code,获取表格名称
*
* @param uri
* @return
*/
private String getTableName(Uri uri) {
String tableName = null;
switch (mUrimatcher.match(uri)) {
case USER_URI_CODE:
tableName = BookDataBase.USER_TABLE_NAME;
break;
case BOOK_URI_CODE:
tableName = BookDataBase.BOOK_TABLE_NAME;
break;
}
return tableName;
}
private SQLiteDatabase mDatabase;
private Context mContext;
/**
* onCreate 运行在主进程 不要做耗时操作
*
* @return
*/
@Override
public boolean onCreate() {
Log.d("hh", "onCreate current thread:" + Thread.currentThread().getName());
mContext = getContext();
mDatabase = new BookDataBase(mContext).getWritableDatabase();
return true;
}
/**
* 以及其他的几个方法 都运行在binder 线程中,可鞥会出现线程同步的问题
* 但是因为用的是一个database,而database 内部实现了线程同步,所以在此处不需要处理
* 但是如果用别的数据存储形式实现provide,比如list,文件等就需要考虑同步问题
*/
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
Log.d("hh", "query current thread:" + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("unSupport URI:" + uri);
}
return mDatabase.query(tableName, strings, s, strings1, null, null, s, null);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.d("hh", "getType current thread:" + Thread.currentThread().getName());
return null;
}
/**
* 插入数据
*
* @param uri
* @param contentValues
* @return
*/
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
Log.d("hh", "insert current thread:" + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("unSupport URI:" + uri);
}
mDatabase.insert(tableName, null, contentValues);
//insert update delete 等方法会引起数据源的改变,所以需要通过contentResolve 的notifyChange方法
//通知外界当前contentProvider的数据已经发生改变
//如果需要观察一个ContentProvider中的数据改变情况,可以通过contentResolve的registerContentObserver
//方法来注册观察者,通过unRegisterContentObserver方法来解除观察者
Log.d("hh", "insert success ");
mContext.getContentResolver().notifyChange(uri, null);
return null;
}
/**
* 删除数据
*
* @param uri
* @param s
* @param strings
* @return
*/
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
Log.d("hh", "delete current thread:" + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("unSupport URI:" + uri);
}
int column = mDatabase.delete(tableName, s, strings);
if (column > 0) {
Log.d("hh", "delete success " + column);
mContext.getContentResolver().notifyChange(uri, null);
}
return 0;
}
/**
* 更新数据
*
* @param uri
* @param contentValues
* @param s
* @param strings
* @return
*/
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
Log.d("hh", "update current thread:" + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("unSupport URI:" + uri);
}
int column = mDatabase.update(tableName, contentValues, s, strings);
if (column > 0) {
Log.d("hh", "update success " + column);
mContext.getContentResolver().notifyChange(uri, null);
}
return 0;
}
}
数据库的实现
/**
* @author 付影影
* @desc
* @date 2019/8/2
*/
public class BookDataBase extends SQLiteOpenHelper {
private static final String DB_NAME = "book.db";
private static final int DB_VERSION = 1;
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME = "user";
private final String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY,name TEXT)";
private final String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY,name TEXT,sex INT)";
public BookDataBase(@Nullable Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
* 创建
*
* @param sqLiteDatabase
*/
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_BOOK_TABLE);
sqLiteDatabase.execSQL(CREATE_USER_TABLE);
}
/**
* 更新
*
* @param sqLiteDatabase
* @param i
* @param i1
*/
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
客户端调用实现
class ContentProviderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val userUri = BookContentProvider.USER_CONTENT_URI
val bookUri = BookContentProvider.BOOK_CONTENT_URI
setContentView(R.layout.activity_content_provider)
insert.setOnClickListener {
val contentValue = ContentValues()
contentValue.put("_id", 1)
contentValue.put("name", "android 开发艺术探索")
contentResolver.insert(bookUri, contentValue)
val userValue = ContentValues()
userValue.put("_id", 1)
userValue.put("name", "shadow")
userValue.put("sex", 0)
contentResolver.insert(userUri, userValue)
}
delete.setOnClickListener {
contentResolver.delete(userUri, null, null)
}
update.setOnClickListener {
val userValue = ContentValues()
userValue.put("_id", 1)
userValue.put("name", "shadow update")
userValue.put("sex", 1)
contentResolver.update(userUri, userValue, "_id = ?", arrayOf("1"))
}
query.setOnClickListener {
val cursor = contentResolver.query(userUri, null, null, null, null)
while (cursor.moveToNext()) {
val id = cursor.getInt(0)
val name = cursor.getString(1)
val sex = cursor.getInt(2)
Log.d("hh", "id = $id,name = $name,sex = $sex")
}
}
}
}