上篇博客主要介绍了sharedUserId&&Messenger作为IPC通信的用法,接着这篇博客要介绍到的是ContentProvider和Socket的详细使用方法。
  android IPC通信(上)-sharedUserId&&Messenger
  android IPC通信(下)-AIDL

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="http://schemas.android.com/apk/res/android">    <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使用该权限,这方面的详细介绍可以看http://blog.csdn.net/self_study/article/details/50074781这篇博客,在这就不介绍了。
  再来看该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的数据存储,关于数据库的相关使用方法可以查阅资料(关于数据库封装,我写了一个,大家可以多提提意见https://github.com/zhaozepeng/Android_framework/tree/master/libcore/src/main/java/com/android/libcore/database)。在该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通信了。
  
  项目下载地址:https://github.com/zhaozepeng/IPC-demo/tree/master/ContentProvider

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,用到的相关类为 ServerSocket和 Socket,首先第一步要在manifest中加入访问网络的权限和声明相关activity和service:

<manifest package="com.android.socket" xmlns:android="http://schemas.android.com/apk/res/android">    <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:18com.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地址互相可见,如果需要继续深入可以去查阅相关的资料,在这就不介绍了。
  源码下载:https://github.com/zhaozepeng/IPC-demo/tree/master/Socket

更多相关文章

  1. Android使用腾讯X5内核替换原生webview
  2. android中usb数据通信速率慢问题解决办法
  3. Android——init.rc脚本
  4. Android中ExpandableListView控件基本使用
  5. androidの亮屏,灭屏,解锁广播使用
  6. android开发新浪微博客户端 完整攻略 [新手必读]
  7. 箭头函数的基础使用
  8. NPM 和webpack 的基础使用
  9. Python list sort方法的具体使用

随机推荐

  1. Android(安卓): 输入设备键值从底层到应
  2. 鸿洋,郭霖:2020学会这几样,Android未来属于
  3. Android脚本插件系列(一):安卓国际化多语
  4. 深入探讨 Android(安卓)传感器
  5. Android的消息机制,用Android线程间通信的
  6. Android应用程序窗口(Activity)的运行上下
  7. Android(安卓)init.rc解析
  8. 浅析:为何到现在高端Android集成解决方案
  9. 安卓软硬结合,热点技术实践总结:《Android
  10. Android中WebView加载本地Html,与JavaScri