進(jìn)程間通信(IPC)方式
- 使用Bundle
- 使用文件共享
- 使用Messenger
- 使用AIDL
- 使用COntentProvider
- 使用Socket
- 通過廣播
一费什、使用Bundle
我們都知道Android中三大組件Activity,Service懂更,Receiver都支持在Intent中傳遞Bundle數(shù)據(jù)疙描,而Bundle實(shí)現(xiàn)了Parcelable接口献雅,所以它可以方便的在不同的進(jìn)程間進(jìn)行傳輸轧简。當(dāng)我我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)另外一個(gè)進(jìn)程的Activity膝舅、Service嗡载、Receiver時(shí)窑多,我們就可以在Bundle中附加我們所需要傳輸給遠(yuǎn)程進(jìn)程的信息并通過intent發(fā)送出去仍稀。這里注意,我們傳輸?shù)臄?shù)據(jù)必須能夠被序列化埂息。
下面我們看一下利用Bundle進(jìn)行進(jìn)程間通信的例子
private void startWithIntent(){
Intent intent = new Intent();
//制定要打開的程序的包名(必須寫全包名技潘,不然會(huì)報(bào)錯(cuò))和地址(activity名)
intent.setComponent(new ComponentName("PackageName",
"PackageName.intentIpcActivity"));
//通過budle傳遞數(shù)據(jù),可以攜帶序列化數(shù)據(jù)
Bundle bundle = new Bundle();
bundle.putInt("intextra", 0);
bundle.putString("stringextra", "測(cè)試數(shù)據(jù)");
intent.putExtras(bundle);
try{
startActivity(intent);
}catch(Exception e){
ToastUtils.showMessage("沒有找到對(duì)應(yīng)文件");
}
}
利用Bundle進(jìn)行進(jìn)程間通信是很容易的千康,大家應(yīng)該也注意到享幽,這種方式進(jìn)行進(jìn)程間通信只能是單方向的簡單數(shù)據(jù)傳輸,它的使用有一定的局限性拾弃。
二值桩、使用文件共享
共享文件也是種不錯(cuò)的進(jìn)程間通信的方式,兩個(gè)進(jìn)程通過讀/寫同一個(gè)文件來交換數(shù)據(jù)豪椿,比如A進(jìn)程把數(shù)據(jù)寫入文件FILE奔坟,B進(jìn)程可以通過讀取這個(gè)文件來獲取這個(gè)數(shù)據(jù)。通過這種方式搭盾,除了可以交換簡單的文本信息外咳秉,我們還可以序列化一個(gè)對(duì)象到文件系統(tǒng)中,另一個(gè)進(jìn)程可以通過反序列化恢復(fù)這個(gè)對(duì)象鸯隅。
比如A在進(jìn)程中創(chuàng)建一個(gè)線程進(jìn)行寫數(shù)據(jù)
new Thread(new Runnable(){
@Override
public void run(){
User user = new User(1, "user", false);
File cachedFile = new File(CACHE_FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try{
objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
}catch(IOException e){
e.printStackTrace();
}finally{
objectOutputStream.close();
}
}
}).start();
在B進(jìn)程創(chuàng)建一個(gè)線程進(jìn)行讀取數(shù)據(jù)
new Thread(new Runnable(){
@Override
public void run(){
User user = null;
File cachedFile = new File(CACHE_FILE_PATH);
if (cachedFile.exists()){
ObjectInputStream objectInputStream = null;
try{
objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile));
user = objectInputStream.readObject(user);
} catch(IOException e){
e.printStackTrace();
}finally{
objectInputStream.close();
}
}
try{
objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
}catch (IOException e){
e,printStackTrace();
}finally{
objectOutputStream.close();
}
}
通過文件共享的這種方式來共享數(shù)據(jù)對(duì)文件的格式是沒有具體要求的澜建,比如可以是文本文件、也可以是XML文件蝌以,只要讀寫雙方約定數(shù)據(jù)格式即可炕舵。這種方式進(jìn)行進(jìn)程間通信雖然方便,可是也是有局限性的跟畅,比如并發(fā)讀/寫咽筋,這會(huì)導(dǎo)致比較嚴(yán)重的問題,如讀取的數(shù)據(jù)不完整或者讀取的數(shù)據(jù)不是最新的碍彭。因此通過文件共享的方式適合在對(duì)數(shù)據(jù)同步要求不高的進(jìn)程之間通信晤硕,并且要妥善處理并發(fā)讀/寫問題。
三庇忌、使用Messenger
Messenger是一種輕量級(jí)的IPC方案舞箍,它的底層實(shí)現(xiàn)是AIDL,可以在不同進(jìn)程中傳遞Messenger對(duì)象皆疹,在Messenger中放入我們需要傳遞的數(shù)據(jù)疏橄。它一次只處理一個(gè)請(qǐng)求,在服務(wù)端不需要考慮線程同步的問題,服務(wù)端不存在并發(fā)執(zhí)行的情形捎迫。
Messenger的使用方法也是比較簡單的晃酒,實(shí)現(xiàn)一個(gè)Messenger有如下幾步,分為服務(wù)端和客戶端:
服務(wù)端進(jìn)程:在A進(jìn)程創(chuàng)建一個(gè)Service來處理其他進(jìn)程的連接請(qǐng)求窄绒,同時(shí)創(chuàng)建一個(gè)Handler并通過他來創(chuàng)建一個(gè)Messenger對(duì)象贝次,然后在Service的onBind中返回這個(gè)Messneger對(duì)象底層的Binder即可。
public class MessengerService extends Service {
private static final String TAG = MessengerService.class.getSimpleName();
private class MessengerHandler extends Handler {
/**
* @param msg
*/
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.MSG_FROM_CLIENT:
Log.d(TAG, "receive msg from client: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]");
Toast.makeText(MessengerService.this, "receive msg from client: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();
Messenger client = msg.replyTo;
Message replyMsg = Message.obtain(null, Constants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString(Constants.MSG_KEY, "我已經(jīng)收到你的消息彰导,稍后回復(fù)你蛔翅!");
replyMsg.setData(bundle);
try {
client.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
客戶端進(jìn)程:首先綁定服務(wù)端 Service ,綁定成功之后用服務(wù)端的 IBinder 對(duì)象創(chuàng)建一個(gè) Messenger 位谋,通過這個(gè) Messenger 就可以向服務(wù)端發(fā)送消息了山析,消息類型是 Message 。如果需要服務(wù)端響應(yīng)掏父,則需要?jiǎng)?chuàng)建一個(gè) Handler 并通過它來創(chuàng)建一個(gè) Messenger(和服務(wù)端一樣)笋轨,并通過 Message 的 replyTo 參數(shù)傳遞給服務(wù)端。服務(wù)端通過 Message 的 replyTo 參數(shù)就可以回應(yīng)客戶端了赊淑。
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private Messenger mGetReplyMessenger = new Messenger(new MessageHandler());
private Messenger mService;
//為了收到Service的回復(fù)爵政,客戶端需要?jiǎng)?chuàng)建一個(gè)接收消息的Messenger和Handler
private class MessageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
//消息處理
switch (msg.what) {
case Constants.MSG_FROM_SERVICE:
Log.d(TAG, "received msg form service: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]");
Toast.makeText(MainActivity.this, "received msg form service: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void bindService(View v) {
Intent mIntent = new Intent(this, MessengerService.class);
bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
public void sendMessage(View v) {
Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString(Constants.MSG_KEY, "Hello! This is client.");
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
unbindService(mServiceConnection);
super.onDestroy();
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
/**
* @param name
* @param service
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//根據(jù)得到的IBinder對(duì)象創(chuàng)建Messenger
mService = new Messenger(service);
//通過得到的mService 可以進(jìn)行通信
Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString(Constants.MSG_KEY, "Hello! This is client.");
msg.setData(data);
//
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* @param name
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
這里畫一張Messenger的工作原理圖,以便于更好的理解Messenger:
Messenger內(nèi)部消息處理使用Handler實(shí)現(xiàn)的膏燃,所以他是以串行的方式處理客戶端發(fā)送過來的消息的茂卦,如果有大量的消息發(fā)送給服務(wù)端,服務(wù)端只能一個(gè)一個(gè)處理组哩,如果并發(fā)量大的話用Messenger就不合適了等龙,而且Messenger的主要作用是為了傳遞消息的,很多時(shí)候我們需要跨進(jìn)程調(diào)用服務(wù)端的方法伶贰,這種需求Messenger就無法做到了蛛砰。
注意:客戶端和服務(wù)端是通過拿到對(duì)方的 Messenger 來發(fā)送 Message 的。只不過客戶端通過 bindService onServiceConnected 而服務(wù)端通過 message.replyTo 來獲得對(duì)方的 Messenger 黍衙。Messenger 中有一個(gè) Hanlder 以串行的方式處理隊(duì)列中的消息泥畅。不存在并發(fā)執(zhí)行,因此我們不用考慮線程同步的問題琅翻。
四位仁、使用AIDL
AIDL (Android Interface Definition Language)是一種IDL語言,用于生成可以在Android設(shè)備上兩個(gè)進(jìn)程之間進(jìn)行進(jìn)程間通信(IPC)的代碼方椎。如果在一個(gè)進(jìn)程中(例如Activity)要調(diào)用另一個(gè)進(jìn)程中(例如Service)對(duì)象的操作聂抢,就可以使用AIDL生成可序列化的參數(shù)。
AIDL是IPC的一個(gè)輕量級(jí)實(shí)現(xiàn)棠众,用了對(duì)于Java開發(fā)者來說很熟悉的語法琳疏。Android也提供了一個(gè)工具有决,可以自動(dòng)創(chuàng)建Stub(類構(gòu)架,類骨架)空盼。當(dāng)我們需要在應(yīng)用間通信時(shí)书幕,我們需要按以下幾步走:
定義一個(gè)AIDL接口
為遠(yuǎn)程服務(wù)(Service)實(shí)現(xiàn)對(duì)應(yīng)Stub
將服務(wù)“暴露”給客戶程序使用
官方文檔中對(duì)AIDL有這樣一段介紹:Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.
第一句最重要,“只有當(dāng)你允許來自不同的客戶端訪問你的服務(wù)并且需要處理多線程問題時(shí)你才必須使用AIDL”,其他情況下你都可以選擇其他方法,如使用Messager即纲,也能跨進(jìn)程通訊±撸可見AIDL是處理多線程、多客戶端并發(fā)訪問的奔缠。而Messager是單線程處理。
Messenger 是以串行的方式處理客戶端發(fā)來的消息吼野,如果大量消息同時(shí)發(fā)送到服務(wù)端校哎,服務(wù)端只能一個(gè)一個(gè)處理,所以大量并發(fā)請(qǐng)求就不適合用 Messenger 瞳步,而且 Messenger 只適合傳遞消息闷哆,不能跨進(jìn)程調(diào)用服務(wù)端的方法。AIDL 可以解決并發(fā)和跨進(jìn)程調(diào)用方法的問題单起,要知道 Messenger 本質(zhì)上也是 AIDL 抱怔,只不過系統(tǒng)做了封裝方便上層的調(diào)用而已。
AIDL文件支持的數(shù)據(jù)類型
基本數(shù)據(jù)類型
String 和 CharSequence
ArrayList嘀倒,里面的元素必須能夠被AIDL支持屈留;
HashMap,里面的元素必須能夠被AIDL支持测蘑;
Parcelable灌危,實(shí)現(xiàn)Parcelable接口的對(duì)象;注意:如果 AIDL 文件中用到了自定義的 Parcelable 對(duì)象碳胳,必須新建一個(gè)和它同名的 AIDL 文件勇蝙。
AIDL。AIDL接口本身也可以在AIDL文件中使用挨约。
AIDL很大的好處就是我們直接可以調(diào)用服務(wù)端進(jìn)程所暴露出來的方法味混,下面簡單介紹一下AIDL的使用:
服務(wù)端
服務(wù)端首先要?jiǎng)?chuàng)建一個(gè)Service用來監(jiān)聽客戶端的請(qǐng)求,然后創(chuàng)建一個(gè)AIDL文件诫惭,將暴露給客戶端的接口在這個(gè)AIDL文件中聲明翁锡,最后在Service中實(shí)現(xiàn)這個(gè)AIDL接口即可。
(1)創(chuàng)建AIDL接口文件
AIDL使用簡單的語法來聲明接口贝攒,描述其方法以及方法的參數(shù)和返回值盗誊。這些參數(shù)和返回值可以是任何類型,甚至是其他AIDL生成的接口。重要的是必須導(dǎo)入所有非內(nèi)置類型哈踱,哪怕是這些類型是在與接口相同的包中荒适。
package com.example.android;
interface IRemoteService {
int getPid();
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
(2)向客戶端暴露接口
public class DDService extends Service {
@Override
public void onCreate() {
super.onCreate();
System.out.println("DDService onCreate........" + "Thread: " + Thread.currentThread().getName());
}
@Override
public IBinder onBind(Intent arg0) {
System.out.println("DDService onBind");
return mBinder;
}
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
System.out.println("Thread: " + Thread.currentThread().getName());
System.out.println("DDService getPid ");
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
System.out.println("Thread: " + Thread.currentThread().getName());
System.out.println("basicTypes aDouble: " + aDouble +" anInt: " + anInt+" aBoolean " + aBoolean+" aString " + aString);
}
};
}
這樣我們的服務(wù)端就完成了,把服務(wù)端運(yùn)行到模擬器(或者手機(jī)上)开镣,等一會(huì)可以看一下打印信息刀诬,重點(diǎn)看“線程名”。
客戶端
客戶端所做的事情就要簡單很多了邪财,首先需要綁定服務(wù)端Service陕壹,綁定成功后將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)成AIDL接口所屬的類型,接著就可以調(diào)用AIDL中的方法了树埠。
客戶端調(diào)用遠(yuǎn)程服務(wù)的方法糠馆,被調(diào)用的方法運(yùn)行在服務(wù)端的 Binder 線程池中,同時(shí)客戶端的線程會(huì)被掛起怎憋,如果服務(wù)端方法執(zhí)行比較耗時(shí)又碌,就會(huì)導(dǎo)致客戶端線程長時(shí)間阻塞,導(dǎo)致 ANR 绊袋”显龋客戶端的 onServiceConnected 和 onServiceDisconnected 方法都在 UI 線程中。
public class MainActivity extends Activity {
private IRemoteService remoteService;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
remoteService = IRemoteService.Stub.asInterface(service);
try {
int pid = remoteService.getPid();
int currentPid = Process.myPid();
System.out.println("currentPID: " + currentPid +" remotePID: " + pid);
remoteService.basicTypes(12, 1223, true, 12.2f, 12.3, "我們的愛癌别,我明白");
} catch (RemoteException e) {
e.printStackTrace();
}
System.out.println("bind success! " + remoteService.toString());
}
};
/**
* 監(jiān)聽按鈕點(diǎn)擊
* @param view
*/
public void buttonClick(View view) {
System.out.println("begin bindService");
Intent intent = new Intent("duanqing.test.aidl");
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(conn);
}
}
這樣就實(shí)現(xiàn)了通過AIDL進(jìn)行進(jìn)程間通信了皂岔,是不是也很簡單,不過這個(gè)看似簡單展姐,其實(shí)底層Android為我們做了很多的事情躁垛,核心就是Binder,感興趣的讀者可以學(xué)習(xí)一下Binder原理诞仓。
五缤苫、使用ContentProvider
ContentProvider(內(nèi)容提供者)是Android中的四大組件之一,為了在應(yīng)用程序之間進(jìn)行數(shù)據(jù)交換墅拭,Android提供了ContentProvider活玲,ContentProvider是不同應(yīng)用之間進(jìn)行數(shù)據(jù)交換的API,一旦某個(gè)應(yīng)用程序通過ContentProvider暴露了自己的數(shù)據(jù)操作接口谍婉,那么不管該應(yīng)用程序是否啟動(dòng)舒憾,其他應(yīng)用程序都可以通過接口來操作接口內(nèi)的數(shù)據(jù),包括增穗熬、刪镀迂、改、查等唤蔗。ContentProvider分為系統(tǒng)的和自定義的探遵,系統(tǒng)的也就是例如聯(lián)系人窟赏,圖片等數(shù)據(jù)。
開發(fā)一個(gè)ContentProvider的步驟很簡單:
定義自己的ContentProvider類箱季,該類繼承ContentProvider基類涯穷;
在AndroidManifest.xml中注冊(cè)這個(gè)ContentProvider,類似于Activity注冊(cè)藏雏,注冊(cè)時(shí)要給ContentProvider綁定一個(gè)域名拷况;
當(dāng)我們注冊(cè)好ContentProvider后,其他應(yīng)用就可以訪問ContentProvider暴露出來的數(shù)據(jù)了掘殴。
ContentProvider只是暴露出來可供其他應(yīng)用操作的數(shù)據(jù)赚瘦,其他應(yīng)用則需要通過ContentReslover來操作ContentProvider所暴露出來的數(shù)據(jù)。Context提供了getContentResolver()方法來獲取ContentProvider對(duì)象奏寨,獲取之后皆可以對(duì)暴露出來的數(shù)據(jù)進(jìn)行增起意、刪、改服爷、查操作了杜恰。
使用ContentResolver操作數(shù)據(jù)的步驟也很簡單:
調(diào)用Activity的getContentResolver()獲取ContentResolver對(duì)象
根據(jù)調(diào)用的ContentResolver的insert()、delete()仍源、update()、和query()方法操作數(shù)據(jù)庫即可舔涎。
代碼示例:
BookProvider.java
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
public static final String AUTHORITY = "com.jc.ipc.Book.Provider";
public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
}
private Context mContext;
private SQLiteDatabase mDB;
@Override
public boolean onCreate() {
mContext = getContext();
initProviderData();
return true;
}
private void initProviderData() {
//不建議在 UI 線程中執(zhí)行耗時(shí)操作
mDB = new DBOpenHelper(mContext).getWritableDatabase();
mDB.execSQL("delete from " + DBOpenHelper.BOOK_TABLE_NAME);
mDB.execSQL("delete from " + DBOpenHelper.USER_TABLE_NAME);
mDB.execSQL("insert into book values(3,'Android');");
mDB.execSQL("insert into book values(4,'iOS');");
mDB.execSQL("insert into book values(5,'Html5');");
mDB.execSQL("insert into user values(1,'haohao',1);");
mDB.execSQL("insert into user values(2,'nannan',0);");
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.d(TAG, "query, current thread"+ Thread.currentThread());
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI" + uri);
}
return mDB.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
}
@Nullable
@Override
public String getType(Uri uri) {
Log.d(TAG, "getType");
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.d(TAG, "insert");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI" + uri);
}
mDB.insert(table, null, values);
// 通知外界 ContentProvider 中的數(shù)據(jù)發(fā)生變化
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.d(TAG, "delete");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI" + uri);
}
int count = mDB.delete(table, selection, selectionArgs);
if (count > 0) {
mContext.getContentResolver().notifyChange(uri, null);
}
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.d(TAG, "update");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI" + uri);
}
int row = mDB.update(table, values, selection, selectionArgs);
if (row > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return row;
}
private String getTableName(Uri uri) {
String tableName = null;
switch (sUriMatcher.match(uri)) {
case BOOK_URI_CODE:
tableName = DBOpenHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
tableName = DBOpenHelper.USER_TABLE_NAME;
break;
default:
break;
}
return tableName;
}
}
DBOpenHelper.java
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "book_provider.db";
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME = "user";
private static final int DB_VERSION = 1;
private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "
+ BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";
private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "
+ USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT,"
+ "sex INT)";
public DBOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK_TABLE);
db.execSQL(CREATE_USER_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
ProviderActivity.java
public class ProviderActivity extends AppCompatActivity {
private static final String TAG = ProviderActivity.class.getSimpleName();
private TextView displayTextView;
private Handler mHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
displayTextView = (TextView) findViewById(R.id.displayTextView);
mHandler = new Handler();
getContentResolver().registerContentObserver(BookProvider.BOOK_CONTENT_URI, true, new ContentObserver(mHandler) {
@Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
Toast.makeText(ProviderActivity.this, uri.toString(), Toast.LENGTH_SHORT).show();
super.onChange(selfChange, uri);
}
});
}
public void insert(View v) {
ContentValues values = new ContentValues();
values.put("_id",1123);
values.put("name", "三國演義");
getContentResolver().insert(BookProvider.BOOK_CONTENT_URI, values);
}
public void delete(View v) {
getContentResolver().delete(BookProvider.BOOK_CONTENT_URI, "_id = 4", null);
}
public void update(View v) {
ContentValues values = new ContentValues();
values.put("_id",1123);
values.put("name", "三國演義新版");
getContentResolver().update(BookProvider.BOOK_CONTENT_URI, values , "_id = 1123", null);
}
public void query(View v) {
Cursor bookCursor = getContentResolver().query(BookProvider.BOOK_CONTENT_URI, new String[]{"_id", "name"}, null, null, null);
StringBuilder sb = new StringBuilder();
while (bookCursor.moveToNext()) {
Book book = new Book(bookCursor.getInt(0),bookCursor.getString(1));
sb.append(book.toString()).append("\n");
}
sb.append("--------------------------------").append("\n");
bookCursor.close();
Cursor userCursor = getContentResolver().query(BookProvider.USER_CONTENT_URI, new String[]{"_id", "name", "sex"}, null, null, null);
while (userCursor.moveToNext()) {
sb.append(userCursor.getInt(0))
.append(userCursor.getString(1)).append(" ,")
.append(userCursor.getInt(2)).append(" ,")
.append("\n");
}
sb.append("--------------------------------");
userCursor.close();
displayTextView.setText(sb.toString());
}
}
六笼踩、使用Socket
Socaket也是實(shí)現(xiàn)進(jìn)程間通信的一種方式,Socaket也成為“套接字”亡嫌,是網(wǎng)絡(luò)通信中的概念嚎于,通過Socaket我們可以很方便的進(jìn)行網(wǎng)絡(luò)通信,都可以實(shí)現(xiàn)網(wǎng)絡(luò)通信錄挟冠,那么實(shí)現(xiàn)跨進(jìn)程通信不是也是相同的么于购。
Socket起源于 Unix,而 Unix 基本哲學(xué)之一就是“一切皆文件”知染,都可以用“打開 open –讀寫 write/read –關(guān)閉 close ”模式來操作肋僧。Socket 就是該模式的一個(gè)實(shí)現(xiàn),網(wǎng)絡(luò)的 Socket 數(shù)據(jù)傳輸是一種特殊的 I/O控淡,Socket 也是一種文件描述符嫌吠。Socket 也具有一個(gè)類似于打開文件的函數(shù)調(diào)用: Socket(),該函數(shù)返回一個(gè)整型的Socket 描述符掺炭,隨后的連接建立辫诅、數(shù)據(jù)傳輸?shù)炔僮鞫际峭ㄟ^該 Socket 實(shí)現(xiàn)的。
常用的 Socket 類型有兩種:流式 Socket(SOCK_STREAM)和數(shù)據(jù)報(bào)式 Socket(SOCK_DGRAM)涧狮。流式是一種面向連接的 Socket炕矮,針對(duì)于面向連接的 TCP 服務(wù)應(yīng)用么夫;數(shù)據(jù)報(bào)式 Socket 是一種無連接的 Socket ,對(duì)應(yīng)于無連接的 UDP 服務(wù)應(yīng)用肤视。
Socket 本身可以傳輸任意字節(jié)流档痪。
談到Socket,就必須要說一說 TCP/IP 五層網(wǎng)絡(luò)模型:
應(yīng)用層:規(guī)定應(yīng)用程序的數(shù)據(jù)格式钢颂,主要的協(xié)議 HTTP钞它,F(xiàn)TP,WebSocket殊鞭,POP3 等遭垛;
傳輸層:建立“端口到端口” 的通信,主要的協(xié)議:TCP操灿,UDP锯仪;
網(wǎng)絡(luò)層:建立”主機(jī)到主機(jī)”的通信,主要的協(xié)議:IP趾盐,ARP 庶喜,IP 協(xié)議的主要作用:一個(gè)是為每一臺(tái)計(jì)算機(jī)分配 IP 地址,另一個(gè)是確定哪些地址在同一子網(wǎng)救鲤;
數(shù)據(jù)鏈路層:確定電信號(hào)的分組方式久窟,主要的協(xié)議:以太網(wǎng)協(xié)議;
物理層:負(fù)責(zé)電信號(hào)的傳輸本缠。
Socket 是連接應(yīng)用層與傳輸層之間接口(API)斥扛。
只實(shí)現(xiàn) TCP Socket 。
Client 端代碼:
public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG = "TCPClientActivity";
public static final int MSG_RECEIVED = 0x10;
public static final int MSG_READY = 0x11;
private EditText editText;
private TextView textView;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
private Button sendBtn;
private StringBuilder stringBuilder;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_READY:
sendBtn.setEnabled(true);
break;
case MSG_RECEIVED:
stringBuilder.append(msg.obj).append("\n");
textView.setText(stringBuilder.toString());
break;
default:
super.handleMessage(msg);
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tcp_client_activity);
editText = (EditText) findViewById(R.id.editText);
textView = (TextView) findViewById(R.id.displayTextView);
sendBtn = (Button) findViewById(R.id.sendBtn);
sendBtn.setOnClickListener(this);
sendBtn.setEnabled(false);
stringBuilder = new StringBuilder();
Intent intent = new Intent(TCPClientActivity.this, TCPServerService.class);
startService(intent);
new Thread(){
@Override
public void run() {
connectTcpServer();
}
}.start();
}
private String formatDateTime(long time) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
private void connectTcpServer() {
Socket socket = null;
while (socket == null) {
try {
socket = new Socket("localhost", 8888);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())
), true);
mHandler.sendEmptyMessage(MSG_READY);
} catch (IOException e) {
e.printStackTrace();
}
}
// receive message
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
}
while (!isFinishing()) {
try {
String msg = bufferedReader.readLine();
if (msg != null) {
String time = formatDateTime(System.currentTimeMillis());
String showedMsg = "server " + time + ":" + msg
+ "\n";
mHandler.obtainMessage(MSG_RECEIVED, showedMsg).sendToTarget();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onClick(View v) {
if (mPrintWriter != null) {
String msg = editText.getText().toString();
mPrintWriter.println(msg);
editText.setText("");
String time = formatDateTime(System.currentTimeMillis());
String showedMsg = "self " + time + ":" + msg + "\n";
stringBuilder.append(showedMsg);
}
}
@Override
protected void onDestroy() {
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
}
Server端代碼:
public class TCPServerService extends Service {
private static final String TAG = "TCPServerService";
private boolean isServiceDestroyed = false;
private String[] mMessages = new String[]{
"Hello! Body!",
"用戶不在線丹锹!請(qǐng)稍后再聯(lián)系稀颁!",
"請(qǐng)問你叫什么名字呀?",
"厲害了楣黍,我的哥匾灶!",
"Google 不需要Science上網(wǎng)是真的嗎?",
"扎心了租漂,老鐵=着!窜锯!"
};
@Override
public void onCreate() {
new Thread(new TCPServer()).start();
super.onCreate();
}
@Override
public void onDestroy() {
isServiceDestroyed = true;
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private class TCPServer implements Runnable {
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8888);
} catch (IOException e) {
e.printStackTrace();
return;
}
while (!isServiceDestroyed) {
// receive request from client
try {
final Socket client = serverSocket.accept();
Log.d(TAG, "=============== accept ==================");
new Thread(){
@Override
public void run() {
try {
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) throws IOException {
//receive message
BufferedReader in = new BufferedReader(
new InputStreamReader(client.getInputStream()));
//send message
PrintWriter out = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
client.getOutputStream())),true);
out.println("歡迎來到聊天室张肾!");
while (!isServiceDestroyed) {
String str = in.readLine();
Log.d(TAG, "message from client: " + str);
if (str == null) {
return;
}
Random random = new Random();
int index = random.nextInt(mMessages.length);
String msg = mMessages[index];
out.println(msg);
Log.d(TAG, "send Message: " + msg);
}
out.close();
in.close();
client.close();
}
}