IPC机制系列之三 Android中的IPC方式 (ContentProvider,Socket)

   使用ContentProvider实现进程间通信

  (1)ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,和Messenger一样,ContentProvider的底层实现同样也是Binder,他的使用比较简单主要原因是系统为我们做了封装。系统其实预置了许多的ContentProvider,比如通讯录信息,日程表信息,等等,要跨进程的访问这些,只需要通过ContentProvider的query,Update,insert和delete方法即可。

  (2)自定义一个ContentProvider很简单,只需要继承ContentProvider类并且实现它的六个抽象方法即可。onCreat代表ContentProvider的创建,一般初始化的工作都在这个方法中进行,getType用来返回一个Uri的请求对应的MIME类型(媒体类型),比如图片,视频等,这个类型比较复杂。剩下的都是操作数据表的。依据Binder的原理,这6个方法都在ContentProvider进程中,除了onCreat有系统回调并运行在主线程,其他的均由外界回调并运行在Binder线程池。

  (3)ContentProvider主要以表格的形式来组织数据,并且可以包含多个表,对于每个表格来说,他们都具有行和列的层次性,行往往对应一条数据,而列对应一条记录中的一个字段,这点和数据库类似。ContentProvider还支持文件数据,比如图片、视频等。

  (4)文件数据和表格数据的结构不同,因此处理这类数据时可以在ContentProvider中返回文件的句柄给外界,从而让外界来访问ContentProvider中的文件信息。Android中的MediaStore就是文件类型的ContentProvider。

  (5)ContentProvider对底层的数据存储结构没有具体要求,可以使用SQLite数据库,也可以使用文件,甚至是内存中的一个对象来保存数据。

实例如下:
/**
 * 自定义一个ContentProvider,实现其六个抽象的方法,并添加打印信息
 * 观察其主要回调在那个线程
 */
public class UserProvider extends ContentProvider {
    private static final String TAG="UserProvider";
    @Override
    public boolean onCreate() {
        Log.e(TAG,"onCreate,当前的线程:"+Thread.currentThread().getName());
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.e(TAG,"query,当前的线程:"+Thread.currentThread().getName());
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        Log.e(TAG,"getType");
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.e(TAG,"insert");
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(TAG,"delete");
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(TAG,"update");
        return 0;
    }
}

然后在清单文件中注册

 <provider
        android:name=".provider.UserProvider"
        <!--必须且唯一的值,它是ContentProvider的唯一标识 -->
        android:authorities="com.byd.test.provider"
        <!--自定义权限,要想使用必须声明相应的权限才行-->
        android:permission="com.byd.test.permission.PROVIDER"
        <!--让其运行在独立的进程中-->
        android:process=":provider" />

自定义的权限:

  <permission
        <!--权限的名称-->
        android:name="com.byd.test.permission.PROVIDER"
        <!--权限的解释->
        android:label="@string/provider_string"
        <!--权限的等级-->
        android:protectionLevel="normal" />

注册完之后,我们只需要在外部应用中去访问它了


public class ProviderActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);
          /**
         * 唯一标识了UserProvider,而这个标识正是我们在清单文件中为UserProvider添加的
         *authorities属性所指定的值,
         */
        Uri uri=Uri.parse("content://com.byd.test.provider");
        getContentResolver().query(uri,null,null,null,null);
        getContentResolver().query(uri,null,null,null,null);
        getContentResolver().query(uri,null,null,null,null);
    }
}

我们在另一个应用中使用ContentResolver对象的query方法查询三次了UserProvider中的数据,看Log日志:oncreat在主线程中回调,而query在Binder线程池中回调。

10-17 01:25:07.703 3068-3068/com.byd.fuangde.text:provider E/UserProvider: onCreate,当前的线程:main
10-17 01:25:07.704 3068-3085/com.byd.fuangde.text:provider E/UserProvider: query,当前的线程:Binder_2
10-17 01:25:07.704 3068-3084/com.byd.fuangde.text:provider E/UserProvider: query,当前的线程:Binder_1
10-17 01:25:07.704 3068-3085/com.byd.fuangde.text:provider E/UserProvider: query,当前的线程:Binder_2

完整的流程就是上面的,我们为了能更好的演示一下他的作用,接下来完整一下整个项目
,首先自定义毅哥 数据库来管理用户信息。我们直接使用SQLite。

public class DbOpenHelper extends SQLiteOpenHelper {
    private static final String DB_NAME="user_provider.db";
    public static final String USER_TABLE_NAME="user";
    private static final int VERSION=0;
    //建表语句
    private String CREATE_USER_TABLE="CREATE TABLE IF NOT EXISTS"+USER_TABLE_NAME+"(_id INTEGER PRIMARY KEY,name,sex)";
    public DbOpenHelper(Context context) {
        super(context, DB_NAME, null, VERSION);
    }
    //用于创建数据库表的方法,执行数据库建表语句
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_USER_TABLE);
    }
    //每次都会调用,用于版本更新
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if(oldVersion<newVersion){
            //删除旧表,建立新表
            db.execSQL("drop table if exists user");
            onCreate(db);
        }
    }
//    此时,并没有数据库,只有heper在调用:
//    getWritableDatabase();            //两个方法得到的db都可以读写操作,当磁盘满了
//    getReadableDatabase();            //第二种不会继续向内存中写,第一种会尝试去写
//    时才会创建

}

完善后的UserProvider:

public class UserProvider extends ContentProvider {
    private static final String TAG="UserProvider";
    private static final String AUTHORITY="com.byd.test.provider";
    private static final Uri USER_CONTENT_URI=Uri.parse("content://"+AUTHORITY+"/user");
    private static final int USER_URI_CODE=0;
    private static UriMatcher matcher = null;
    private Context context;
    private SQLiteDatabase mDb;
    static {
        matcher=new UriMatcher(UriMatcher.NO_MATCH);
        matcher.addURI(AUTHORITY,"user",USER_URI_CODE);
    }
    private String getTableName(Uri uri){
        String tabName=null;
        switch(matcher.match(uri)){
            case USER_URI_CODE:
                tabName= DbOpenHelper.USER_TABLE_NAME;
                break;
            default:
                break;
        }
        return tabName;
    }
    @Override
    public boolean onCreate() {
        Log.e(TAG,"onCreate,当前的线程:"+Thread.currentThread().getName());
        context=getContext();
        //初始化数据库
        mDb=new DbOpenHelper(context).getWritableDatabase();
        new Thread(new Runnable() {
            @Override
            public void run() {
                mDb.execSQL("insert into user values(4,'jack','男');");
            }
        }).start();
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.e(TAG,"query,当前的线程:"+Thread.currentThread().getName());
        String table=getTableName(uri);
        Cursor cursor=null;
        if(table == null){
            throw new IllegalArgumentException("Unsupported URI:"+uri);
        }
        cursor=mDb.query(table,projection,selection,selectionArgs,null,null,sortOrder);
        return cursor;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        Log.e(TAG,"getType");
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.e(TAG,"insert");
        String table=getTableName(uri);
        if(table == null){
            throw new IllegalArgumentException("Unsupported URI:"+uri);
        }
        long id = mDb.insert(table, null, values);
        return ContentUris.withAppendedId(uri,id);
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(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){
            getContext().getContentResolver().notifyChange(uri,null);
        }
        return count;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(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;
    }
}

我们在外部访问一下:

public class ProviderActivity extends AppCompatActivity {
    private static final String TAG="ProviderActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);
        /**
         * 唯一标识了UserProvider,而这个标识正是我们在清单文件中为UserProvider添加的
         *authorities属性所指定的值,
         */
        Uri uri=Uri.parse("content://com.byd.test.provider/user");
        Cursor cursor = getContentResolver().query(uri, new String[]{"_id", "name", "sex"}, null, null, null);
        while(cursor.moveToNext())
        {
            User user=new User();
            user.userId=cursor.getInt(0);
            user.userName=cursor.getString(1);
            Log.e(TAG,user.toString());
        }
       cursor.close();

    }
}

log日志:

10-17 04:00:16.950 8688-8688/? E/UserProvider: onCreate,当前的线程:main
10-17 04:00:16.952 8688-8696/? I/art: Debugger is no longer active
10-17 04:00:16.955 8688-8705/? E/UserProvider: query,当前的线程:Binder_2
10-17 04:00:16.967 8670-8670/com.byd.fuangde.text E/ProviderActivity: User{userId='1', usereName='tom'}
10-17 04:00:16.967 8670-8670/com.byd.fuangde.text E/ProviderActivity: User{userId='2', usereName='rose'}
10-17 04:00:16.967 8670-8670/com.byd.fuangde.text E/ProviderActivity: User{userId='3', usereName='jack'}
10-17 04:00:16.967 8670-8670/com.byd.fuangde.text E/ProviderActivity: User{userId='4', usereName='jack'}

可以看到已经可以正常处理请求了,由于ProviderActivity和Provider运行在不同的进程中,因此也就是实现了进程间通信。

  使用Socket实现进程间通信

  Socket也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字,分别对应网络传输层协议中的tcp和udp协议,前者是面向连接的协议,连接的建立需要通过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,具有很高的稳定性。后者是无连接的,提供不稳定的单向通信功能,但是效率较高。

  接下来编写一个小型简易的聊天室来展示使用Socket实现进程间通信,首先设计服务端,当Service启动时,会在线程中建立TCP服务,这里监听一个客户端连接请求的端口,当有客户端连进来是,会生成一个新的Socket,通过每次新创建的Socket就可以分别和不同的客户端通信了。服务端每收到一个客户端的消息就会回复客户端一个信息,当客户端断开连接后服务端这边也会 相应的关闭Socket并结束通话线程。

  服务端的代码:

public class TCPServerService extends Service {
    //定义一个布尔类型的值来记录Server是否被销毁 false:存活 true:被销毁
    private boolean mIsServiceDestory=false;
    private String [] mDefineMessages=new String[]{"你好啊。爱猴","你是哪里人","你是干啥的啊","你知道吗你可牛逼了"};
    @Override
    public void onCreate() {
        new Thread(new TcpServer()).start();
        super.onCreate();
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        mIsServiceDestory=true;
        super.onDestroy();
    }
    public class TcpServer implements Runnable{

        @SuppressWarnings("resource")
        @Override
        public void run() {
            ServerSocket serverSocket=null;
            try {
                //这里监听本地的8688端口
                serverSocket=new ServerSocket(8688);
            } catch (IOException e) {
                System.err.println("establish tcp server failed,port:8688");
                e.printStackTrace();
                return;
            }
            while(!mIsServiceDestory){
                //接收客户端请求
                try {
                    final Socket clientSocket=serverSocket.accept();
                    System.out.println("accept");
                    new Thread(){
                        @Override
                        public void run() {
                            try {
                                responseClient(clientSocket);
                            } 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);
            out.println("欢迎来到byd的聊天室");
            while(!mIsServiceDestory)
            {
                String str=in.readLine();
                System.out.println("msg from client:"+str);
                if (str== null){
                    //客户端断开连接
                    break;
                }
                int i=new Random().nextInt(mDefineMessages.length);
                String msg=mDefineMessages[i];
                out.println(msg);
                System.out.println("send:"+msg);
                System.out.println("client quit.");
                //关闭流
                out.close();
                in.close();
                client.close();
            }
        }
    }
}

客户端

public class TCPClientActivity extends AppCompatActivity {
    //收到新消息
    private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
    //确定socket是否连接
    private static final int MESSAGE_SOCKET_CONNECTED = 2;
    @BindView(R.id.show_msg)
    TextView showMsg;
    @BindView(R.id.input_msg_et)
    EditText inputMsgEt;
    @BindView(R.id.send_msg_btn)
    Button sendMsgBtn;

    private Socket mClientSocket;
    private PrintWriter mWriter;

    @SuppressLint("HandlerLeak")
    private Handler mHandler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what)
            {
                case MESSAGE_RECEIVE_NEW_MSG:
                    showMsg.setText(showMsg.getText()+(String) msg.obj);
                    break;
                case MESSAGE_SOCKET_CONNECTED:
                    sendMsgBtn.setEnabled(true);
                    break;
                default:
                 break;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tcpclient);
        ButterKnife.bind(this);
        //开启后端service
        Intent intent = new Intent(this, TCPServerService.class);
        startService(intent);
        new Thread() {
            @Override
            public void run() {
                connectTCPServer();
            }
        }.start();
    }

    /**
     * 当听关闭客户端时,关闭socket
     */
    @Override
    protected void onDestroy() {
        if (mClientSocket != null) {
            try {
                mClientSocket.shutdownInput();
                mClientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        super.onDestroy();
    }

    @OnClick(R.id.send_msg_btn)
    public void onViewClicked() {
        final String msg = inputMsgEt.getText().toString().trim();
        if (!TextUtils.isEmpty(msg)) {
            mWriter.println(msg);
            inputMsgEt.setText("");
            String time = new SimpleDateFormat("HH:MM:ss").format(new Date(System.currentTimeMillis()));
            final String showMessage = "self" + time + ":" + msg + "\n";
            showMsg.setText(showMsg.getText() + showMessage);
        }
    }


    //连接Server
    private void connectTCPServer() {
        Socket socket = null;
        while (socket == null) {
            try {
                socket = new Socket("localhost", 8688);
                mClientSocket=socket;
                mWriter=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
                //通过Handler通知已经连上了socket
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
                System.out.println("connect server success");
            } catch (IOException e) {
                SystemClock.sleep(1000);
                System.out.println("connect tcp server failed,retry...");
            }
        }
        try {
            //接收服务端的消息
            BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //当客户端还未退出时
            while (!TCPClientActivity.this.isFinishing())
            {
                String msg = br.readLine();
                System.out.println("receive:"+msg);
                if(msg!=null)
                {
                    String time = new SimpleDateFormat("HH:MM:ss").format(new Date(System.currentTimeMillis()));
                    final String showMessage = "server" + time + ":" + msg + "\n";
                    mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG,showMessage).sendToTarget();
                }
            }
            System.out.println("quit...");
            mWriter.close();
            br.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <TextView
        android:id="@+id/show_msg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="left"
        android:padding="@dimen/activity_horizontal_margin"
        android:text="@string/app_name"
        android:textSize="16sp"/>
    </ScrollView>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="50dp"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true">
        <EditText
            android:id="@+id/input_msg_et"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="6"
            android:textSize="16sp"
            />
        <Button
            android:id="@+id/send_msg_btn"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:text="发送"
            android:textSize="16sp"
            android:enabled="false"
            android:background="@color/colorPrimary"
            android:textColor="#ffffff"/>
    </LinearLayout>
</RelativeLayout>

项目已经放到github上,大家可以clone下来自己实现看一下。附一下地址:项目在git上面的地址

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值