android IPC通訊(中)-ContentProvider&&Socket

  上篇博客主要介紹了sharedUserId&&Messenger做爲IPC通訊的用法,接着這篇博客要介紹到的是ContentProvider和Socket的詳細使用方法。
  android IPC通訊(上)-sharedUserId&&Messenger
  android IPC通訊(下)-AIDL
html

ContentProvider


   ContentProvider是android中提供的專門用於不一樣應用間數據共享的方式,從這一點來看,它天生就適合進程間通訊。和Messenger同樣,ContentProvider的底層實現一樣是Binder,因而可知,Binder在android系統中是何等的重要。ContentProvider也對Binder進行了封裝,使用起來很方便。
   實現本身的ContentProvider須要實現相關的6個方法,onCreate,query,update,insert,delete和getType。雖然ContentProvider的底層數據看起來很像一個SQLite數據庫,可是ContentProvider對底層的數據存儲方式沒有任何要求,咱們既可使用SQLite數據庫,也可使用普通的文件,甚至能夠採用內存中的一個對象來進行數據的存儲,詳細的ContentProvider能夠去查閱相關資料,這裏就着重介紹一下跨進程的使用。
   先看看manifest文件中,該provider的相關注冊信息
<manifest package="com.android.contentprovider"
xmlns:android="schemas.android.com/apk/res/and…">

<permission android:name="com.android.CONTENTPROVIDER_READPERMISSSION"
android:permissionGroup="android.permission-group.STORAGE"
android:protectionLevel="signature"/>


<uses-permission android:name="com.android.CONTENTPROVIDER_READPERMISSSION"/>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<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:authorities="com.android.StudentProvider"
android:name="com.android.contentprovider.StudentProvider"
android:permission="com.android.CONTENTPROVIDER_READPERMISSSION"
android:process=":provider"/>

</application>

</manifest>
複製代碼

  咱們將ContentProvider置於另外一個進程中,這樣就實現了進程之間的通訊,固然也能夠將ContentProvider置於另外一個應用中,用法是很是相似的。 android:authorities是ContentProvider的惟一標識,經過這個屬性外部應用就能夠訪問咱們的provider,若是須要多個受權,用分號隔開便可,還有一個是必需要保持 android:authorities的惟一性。android:permission標籤用來限制訪問該provider時必需要聲明的權限,固然也能夠細分爲android:writePermission和android:readPermission。在manifest中使用permission標籤聲明一個權限,而且使用uses-permission使用該權限,這方面的詳細介紹能夠看 blog.csdn.net/self_study/…這篇博客,在這就不介紹了。
  再來看該ContentProvider的具體實現代碼:
public class StudentProvider extends ContentProvider {

public static final String AUTHORITY = "com.android.StudentProvider";
public static final Uri STUDENT_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/student");
public static final Uri GRADE_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/grade");

public static final int STUDENT_URI_CODE = 0;
public static final int GRADE_URI_CODE = 1;

private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

static {
sUriMatcher.addURI(AUTHORITY, "student", STUDENT_URI_CODE);
sUriMatcher.addURI(AUTHORITY, "grade", GRADE_URI_CODE);
}

private SQLiteDatabase mDb;

@Override
public boolean onCreate() {
mDb = new DbOpenHelper(getContext()).getWritableDatabase();
return true;
}

@Override
public Cursor query(Uri uri, String[] columns, String selection,
String[] selectionArgs, String sortOrder) {
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
return mDb.query(table, columns, selection, selectionArgs, null, null, sortOrder, null);
}

@Override
public String getType(Uri uri) {
return null;
}

@Override
public Uri insert(Uri uri, ContentValues values) {
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
mDb.insert(table, null, values);
getContext().getContentResolver().notifyChange(uri, null);
return uri;
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int count = mDb.delete(table, selection, selectionArgs);
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}

@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
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 STUDENT_URI_CODE:
tableName = DbOpenHelper.STUDENT_TABLE_NAME;
break;
case GRADE_URI_CODE:
tableName = DbOpenHelper.GRADE_TABLE_NAME;
break;
default:break;
}

return tableName;
}

public class DbOpenHelper extends SQLiteOpenHelper {

private static final String DB_NAME = "student_provider.db";
public static final String STUDENT_TABLE_NAME = "student";
public static final String GRADE_TABLE_NAME = "grade";

public DbOpenHelper(Context context) {
super(context, DB_NAME, null, 1);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.beginTransaction();
String sql;
sql = "create table if not exists "+ STUDENT_TABLE_NAME + " (";
sql += "id integer not null primary key , ";
sql += "name varchar(40) not null default 'unknown', ";
sql += "gender varchar(10) not null default 'male',";
sql += "weight float not null default '60'";
sql += ")";
db.execSQL(sql);
sql = "create table if not exists "+GRADE_TABLE_NAME+ " (";
sql += "id integer not null primary key autoincrement, ";
sql += "chinese float not null default '0', ";
sql += "math float not null default '0', ";
sql += "english float not null default '0'";
sql += ")";
db.execSQL(sql);
db.setTransactionSuccessful();
db.endTransaction();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}複製代碼

  咱們使用SQLite實現該ContentProvider的數據存儲,關於數據庫的相關使用方法能夠查閱資料(關於數據庫封裝,我寫了一個,你們能夠多提提意見 github.com/zhaozepeng/…)。在該student_provider.db中建了student和grade兩個表用來存儲相關信息。
  ContentProvider使用uri來區別外界要訪問的數據集合,爲了知道外界要訪問的是哪一個表,咱們須要爲他們定義單獨的uri和uri code,而且使用 UriMathcer類將uri和uri code關聯,這樣根據外界的uri就可以知道它想要訪問哪張表了。
  query,update,insert和delete方法的具體實現很類似也很簡單,具體看代碼就明白了,惟一一點不一樣就是後面三個方法會引發數據源的改變,這時候咱們須要經過ContentResolver的notifyChange方法來通知外界當前ContentProvider中的數據已經發生改變。還有一點須要說明的是這四個方法是存在多線程併發訪問的,所以方法內部要作好線程同步。上面代碼因爲只有一個SQLiteDatabase對象,因此可以正確應對多線程問題,可是若是經過多個SQLiteDatabase對象訪問該數據庫,就會出現線程同步問題,這點須要注意。
  接下來就是要經過外部訪問該ContentProvider了:
public class MainActivity extends BaseActivity implements View.OnClickListener{
Uri studentUri;
Uri gradeUri;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_mainactivity);
findViewById(R.id.btn_query).setOnClickListener(this);
studentUri = StudentProvider.STUDENT_CONTENT_URI;
gradeUri = StudentProvider.GRADE_CONTENT_URI;

ContentValues studentValues = new ContentValues();
studentValues.put("id", 1);
studentValues.put("name", "zhao");
studentValues.put("gender", "male");
studentValues.put("weight", 68.5);
getContentResolver().insert(studentUri, studentValues);

ContentValues gradeValues = new ContentValues();
gradeValues.put("id", 1);
gradeValues.put("chinese", 90.5);
gradeValues.put("math", 80.5);
gradeValues.put("english", 91.5);
getContentResolver().insert(gradeUri, gradeValues);
}

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_query:
StringBuilder stringBuilder = new StringBuilder();
Cursor cursor= getContentResolver().query(studentUri, null, null, null, null);
stringBuilder.append("STUDENT\n");
while (cursor.moveToNext()){
stringBuilder.append("id:").append(cursor.getString(0)).append("\n");
stringBuilder.append("name:").append(cursor.getString(1)).append("\n");
stringBuilder.append("gender:").append(cursor.getString(2)).append("\n");
stringBuilder.append("weight:").append(cursor.getString(3)).append("\n");
}
cursor.close();
cursor = getContentResolver().query(gradeUri, null, null, null, null);
stringBuilder.append("GRADE\n");
while (cursor.moveToNext()){
stringBuilder.append("id:").append(cursor.getString(0)).append("\n");
stringBuilder.append("chinese:").append(cursor.getString(1)).append("\n");
stringBuilder.append("math:").append(cursor.getString(2)).append("\n");
stringBuilder.append("english:").append(cursor.getString(3)).append("\n");
}
cursor.close();
((TextView)findViewById(R.id.tv_result)).setText(stringBuilder);
break;
}
}
}複製代碼

  插入查詢操做都能成功,也就實現了ContentProvider的IPC通訊了。
   這裏寫圖片描述
  項目下載地址: github.com/zhaozepeng/…


Socket


  Socket稱爲「套接字」,是網絡通訊中的概念,他分爲流式套接字和用戶數據報套接字兩種,分別對應於網絡傳輸控制層中的TCP和UDP協議。TCP協議是面向鏈接的協議,提供穩定的雙向通訊功能;而UDP是無鏈接的,提供不穩定的單向通訊功能。在性能上,UDP具備更好的效率,缺點是不能保證數據必定可以正確傳輸,尤爲是在網絡擁塞的狀況下。具體TCP和UDP的區別能夠查閱相關資料。

  1. 基於TCP的socket編程

  2. • java.net.ServerSocket是用來建立服務器端的套接字socket。
    • java.net.Socket是用來建立客戶端的套接字socket。
  3. 基於UDP的socket編程
  4. • java.net.DatagramSocket(數據電報套接字)。
    • java.net.DatagramPacket(數據電報包,裏面包含了發送的信息)。
  咱們這使用TCP來寫demo,用到的相關類爲 ServerSocketSocket,首先第一步要在manifest中加入訪問網絡的權限和聲明相關activity和service:


<manifest package="com.android.socket"
xmlns:android="schemas.android.com/apk/res/and…">


<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity android:name=".SocketClient">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

<service android:name=".SocketServer"
android:process=":socket"/>

</application>

</manifest>複製代碼

  其次還須要注意的是在4.0以後,是不可以在主線程中執行網絡訪問的,要否則會拋出NetworkOnMainThreadException。接着先來看看服務器端的代碼:
public class SocketServer extends Service{
private boolean mIsServiceDestroyed = false;
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}

@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onDestroy() {
mIsServiceDestroyed = true;
super.onDestroy();
}

private class TcpServer implements Runnable {
@Override
public void run() {
ServerSocket serverSocket;
try {
//監聽8688端口
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
L.e(e);
return;
}
while (!mIsServiceDestroyed) {
try {
// 接受客戶端請求,而且阻塞直到接收到消息
final Socket client = serverSocket.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 {
// 用於接收客戶端消息
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
// 用於向客戶端發送消息
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
while (!mIsServiceDestroyed) {
String str = in.readLine();
if (str == null) {
break;
}
L.i("server has received '" + str +"'");
String message = "server has received your message";
out.println(message);
}
out.close();
in.close();
client.close();
}
}複製代碼


  必定要注意的是Service也是運行在主線程,因此不能直接在onCreate函數中去創建TCP鏈接,也須要另開一個線程去處理。這裏咱們使用8688端口去創建和客戶端的鏈接,接着循環去接收該端口的消息數據,serverSocket.accept()函數會阻塞直到有消息的到來。接收到消息以後會返回一個客戶端Socket對象,使用getInputStream()來讀取該消息內容;使用getOutputStream()而且將其裝飾成PrintWriter來向客戶端發送消息。最後記得將相關對象close便可。
  再來看看客戶端代碼:
public class SocketClient extends BaseActivity implements OnClickListener{
private Button mSendButton;
private EditText mMessageEditText;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tcpclient);
mSendButton = (Button) findViewById(R.id.send);
mSendButton.setOnClickListener(this);
mMessageEditText = (EditText) findViewById(R.id.msg);
Intent service = new Intent(this, SocketServer.class);
startService(service);
new Thread() {
@Override
public void run() {
connectTCPServer();
}
}.start();
}

private void connectTCPServer() {
Socket socket = null;
while (socket == null) {
try {
//選擇和服務器相同的端口8688
socket = new Socket("localhost", 8688);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
} catch (IOException e) {
SystemClock.sleep(1000);
}
}
try {
// 接收服務器端的消息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (!isFinishing()) {
String msg = br.readLine();
if (msg != null) {
String time = formatDateTime(System.currentTimeMillis());
L.i("client has received '" + msg + "' at " + time);
}
}
mPrintWriter.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
protected void onDestroy() {
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}

@Override
public void onClick(View v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
//像服務器發送信息
L.i("client has send '" + msg + "' at " + formatDateTime(System.currentTimeMillis()));
mPrintWriter.println(msg);
mMessageEditText.setText("");
}
}
}

private String formatDateTime(long time) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(time));
}複製代碼


  客戶端用來發送消息至服務器,一樣的TCP鏈接不可以放在onCreate主線程中執行,新建一個線程去創建TCP鏈接。該線程中每隔1000ms就會去鏈接服務端Socket直到鏈接成功,鏈接成功以後,一樣使用getOutputStream函數,而且裝飾成PrintWriter對象用來進行消息的發送,最後創建循環去讀取8688端口的消息而且打印出來。看看最後的結果:
com.android.socket I/[PID:20730]: [TID:1] SocketClient.onClick(line:99): client has send 'hello i am client' at 2015-12-14 15:59:18
com.android.socket:socket I/[PID:20784]: [TID:5918] SocketServer.responseClient(line:90): server has received 'hello i am client'
com.android.socket I/[PID:20730]: [TID:5902] SocketClient.connectTCPServer(line:69): client has received 'server has received your message' at 2015-12-14 15:59:18複製代碼


  經過日誌能夠清楚地看到SocketServer是在20784進程中,SocketClient是在20730進程中,很明顯的跨進程之間的通訊。
  固然Socket除了使用TCP套接字以外,還可以使用UDP套接字。另外經過Socket不只僅可以實現進程之間的通訊,還能夠實現設備間的通訊,前提是這些設備之間的IP地址互相可見,若是須要繼續深刻能夠去查閱相關的資料,在這就不介紹了。
  源碼下載:github.com/zhaozepeng/…java

相關文章
相關標籤/搜索