热门搜索 :
考研考公
您的当前位置:首页正文

Android 开发艺术探索笔记(六) 之 Android 使用

来源:东饰资讯网

1. 使用 ContentProvider 进行 IPC

ContentProvider 是 Android 中专门用于不同应用间进行数据共享的方式,它的底层也是 Binder 。

  • 创建一个自定义的 ContentProvider

    我们只需要继承 ContentProvider 并且实现 onCreate(),query(),update(),insert(),delete(),getType() 方法就可以了。这里面除了 onCreate()运行在系统的主线程中,其他 5 个方法都由外界回调并运行在 Binder 线程池中。

    然后在 BookProvider 中创建一个数据库,供外界查询。

show my code

  /**
 *
 * 服务端的 ContentProvider
 *
 * Created by innovator on 2018/1/30.
 */

public class BookProvider extends ContentProvider {

    private static final String TAG = "BookProvider";

    public static final String AUTHORITY = "com.innovator.ipcserver.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);
    }


    /**
     * 根据 Uri 来获取要访问哪个表
     * @param uri
     * @return
     */
    private String getTableName(Uri uri){
      String table = null;
      switch (sUriMatcher.match(uri)){
          case BOOK_URI_CODE:
              table = DbOpenHelper.BOOK_TABLE_NAME;
              break;
          case USER_URI_CODE:
              table = DbOpenHelper.USER_TABLE_NAME;
              break;
          default:
              break;
      }
      return table;
    }

    private Context mContext;
    private SQLiteDatabase mDb;


    @Override
    public boolean onCreate() {
        Log.i("TAG","onCreate,当前线程是:"+Thread.currentThread().getName().toString());

        mContext = getContext();

        //另开线程初始化数据库
        new Thread(new Runnable() {
            @Override
            public void run() {
              initProviderData();
            }
        }).start();
        return true;
    }

    private void initProviderData(){
        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,'Html');");
        mDb.execSQL("insert into user values(1,'jake','1');");
        mDb.execSQL("insert into user values(2,'jasmine','0');");
    }

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

        String tableName = getTableName(uri);
        if(tableName == null){
            throw new IllegalArgumentException("Unsupported URI:"+uri);
        }

        return mDb.query(tableName,projection,selection,selectionArgs,null,null,sortOrder,null);
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        Log.i("TAG","getType,当前线程是:"+Thread.currentThread().getName().toString());
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.i("TAG","insert,当前线程是:"+Thread.currentThread().getName().toString());

        String tableName = getTableName(uri);
        if(tableName == null){
            throw new IllegalArgumentException("Unsupported URI:"+uri);
        }
        mDb.insert(tableName,null,values);
        //通知外界当前的ContentProvider 数据已经发生改变
        mContext.getContentResolver().notifyChange(uri,null);
        return uri;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.i("TAG","delete,当前线程是:"+Thread.currentThread().getName().toString());

        String tableName = getTableName(uri);
        if(tableName == null){
            throw new IllegalArgumentException("Unsupported URI:"+uri);
        }

        int count = mDb.delete(tableName,selection,selectionArgs);
        if(count >0){
            //通知外界当前的ContentProvider 数据已经发生改变
            mContext.getContentResolver().notifyChange(uri,null);
        }
        return count;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.i("TAG","update,当前线程是:"+Thread.currentThread().getName().toString());

        String tableName = getTableName(uri);
        if(tableName == null){
            throw new IllegalArgumentException("Unsupported URI:"+uri);
        }

        int row = mDb.update(tableName,values,selection,selectionArgs);
        if(row >0){
            //通知外界当前的ContentProvider 数据已经发生改变
            mContext.getContentResolver().notifyChange(uri,null);
        }
        return row;
    }
}
  

需要注意的是,query()update()insert()delete() 四大方法是存在多线程并发访问的,要做好线程同步。但是本例中使用 SQLiteDataBase 不需要做线程同步,因为只有一个 DataBase 对象,而 SQLiteDataBase 对数据库的操作就是有同步处理的。

ContentProvider 还要在 ManiFest.xml 里面进行声明:

<provider
            android:authorities="com.innovator.ipcserver.provider"
    android:name="com.innovator.ipcserver.ContentProvider.BookProvider"
            android:permission="com.innovator.ipcserver.PROVIDER"
            android:exported="true">
</provider>
  • 在客户端访问服务端的 ContentProvider

    我们在客户端直接使用 ContentResolver 通过指定的 Uri 来访问服务端的 ContentProvider 获取数据。

show my code

/**
 * 客户端的 ContentResolver
 */
public class ProviderActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);

        //这个 Uri 是由 ContentProvider 声明的 AUTHORITY 参数决定的,在末尾加上要访问的表名就可以了
        Uri uri = Uri.parse("content://com.innovator.ipcserver.provider/book");

        //插入 book 数据
        ContentValues values = new ContentValues();
        values.put("_id",6);
        values.put("name","程序艺术设计");
        getContentResolver().insert(uri,values);

        Cursor bookCursor = getContentResolver().query(uri,new String[]{"_id","name"},null,null,null);
        while (bookCursor != null && bookCursor.moveToNext()){
            Book book = new Book();
            book.setPrice(bookCursor.getInt(0));
            book.setName(bookCursor.getString(1));
            Log.i("TAG","获取到的 Book:"+book.toString());
        }
        bookCursor.close();


        Uri userUri = Uri.parse("content://com.innovator.ipcserver.provider/user");

        Cursor userCursor = getContentResolver().query(userUri,new String[]{"_id","name","sex"},null,null,null);
        while (userCursor != null && userCursor.moveToNext()){
            User user = new User();
            user.setId(userCursor.getInt(0));
            user.setName(userCursor.getString(1));
            user.setSex(userCursor.getInt(2));
            Log.i("TAG","获取到的 User:"+user.toString());
        }
        userCursor  .close();

    }
}

客户端打印的结果:

客户端的 Log

至此我们成功地使用 ContentProvider 进行了跨进程通信,关于 ContentProvider 的更多高级使用,我们以后再探讨,此处只是作为 IPC 的例子进行讲解。

2. 使用 Socket 进行 IPC

Socket 一般是用于网络通信的,支持传输任意字节流,也可以用于跨进程通信。

下面我们用 Socket 实现跨进程的聊天程序。

思路:我们在服务端建立一个 Service ,然后在这个 Service 建立一个 TCP 服务,然后在客户端的主界面中连接 TCP 服务,连接上后客户端就可以和服务端通信了。可以在服务端做能够连接多个客户端的功能。

  • 建立服务端

    在服务端新建一个 Service ,然后通过客户端去启动服务端的 Service,当 Service 启动的时候,会在线程中建立 ServerSocket 以提供 TCP 服务,这里监听 8688 端口,然后等待客户端连接。当有客户端连接的时候,都会生成一个新的 Socket ,通过每次新创建的 Socket 回复客户端,不同的客户端通信了。

show my code

/**
 *
 * Socket 服务端
 * Created by innovator on 2018/2/5.
 */

public class TCPServerService extends Service {

    private boolean mIsServiceDestoyed = false;
    private String[] mDefinedMessages = new String[] {
            "你好啊,哈哈",
            "请问你叫什么名字呀?你成功引起了我的注意",
            "今天真冷,什么时候才能回暖啊",
            "你知道吗?我可是可以和多个人同时聊天的哦",
            "给你讲个笑话吧,据说爱笑的人运气都不会太差,不知道是不是真的"
    };

    @Override
    public void onCreate() {
        Log.i("TCP","正在启动服务端的Service");
        new Thread(new TcpServer()).start();
        super.onCreate();
    }

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

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

    private class TcpServer implements Runnable{

        @SuppressWarnings("resource")
        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                //监听 8688 端口
                serverSocket = new ServerSocket(8688);
            }catch (IOException i){
                Log.i("TCP","establish tcp server failed,port 8688,"+i.getMessage());
                i.printStackTrace();
                return;
            }

            //死循环来读客户端的消息
            while (!mIsServiceDestoyed){
                try{
                  //接收客户端请求
                    final Socket client  = serverSocket.accept();
                    Log.i("TCP","accept");

                    //新建一个线程,因此可以和多个客户端连接
                    new Thread(){
                        @Override
                        public void run() {
                            try {
                               responseClient(client);
                            }catch (IOException o){
                                o.printStackTrace();
                            }
                        }
                    }.start();
                }catch (IOException e){
                    e.printStackTrace();

                }
            }
        }
    }


    /**
     * 服务端回应客户端
     * @param client
     * @throws IOException
     */
    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("欢迎来到聊天室!");

        while (!mIsServiceDestoyed){
            String str = in.readLine();
            if (str == null){
                //客户端断开连接
                break;
            }

            Log.i("TCP","正在读取客户端发送过来的消息: "+str);
            int i = new Random().nextInt(mDefinedMessages.length);
            String msg = mDefinedMessages[i];
            //回复客户端,进行聊天
            out.println(msg);
            Log.i("TCP","回复客户端: "+msg);

        }

        //客户端断开连接后需要关闭流
        Log.i("TCP","客户端断开连接");

        try {
            if (null != out) {
                out.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            if (null != in) {
                in.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意: 由于 String str = in.readLine(); 是阻塞的,所以在客户端和服务端互相发送信息的时候,必须使用 out.println(msg); ,因为 println 自带换行符,所以在读取 Socket 的输入流的时候不会阻塞。

  • 建立客户端

    在客户端的 Activity 中启动服务端的 Service ,然后使用 Socket 监听 8688 端口,连接服务器,然后和服务器通信。

show my code

/**
 * Socket 客户端
 */
public class SocketActivity extends AppCompatActivity implements View.OnClickListener{

    private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
    private static final int MESSAGE_SOCKET_CONNECTED = 2;

    private Button mSendButton;
    private TextView mMessagetextView;
    private TextView mMessagetEditText;

    private PrintWriter mPrintWriter;
    private Socket mClientSocket;

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MESSAGE_RECEIVE_NEW_MSG:
                    //显示服务器发送的消息
                    mMessagetextView.setText(mMessagetextView.getText()+(String)msg.obj);
                    break;
                case MESSAGE_SOCKET_CONNECTED:
                    mSendButton.setEnabled(true);
                    break;

                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_socket);
        mMessagetextView = findViewById(R.id.msg_container);
        mSendButton = findViewById(R.id.send);
        mSendButton.setOnClickListener(this);
        mMessagetEditText = findViewById(R.id.msg);
        Intent i  = new Intent();
        i.setAction("com.innovator.socket");
        i.setPackage("com.innovator.ipcserver");
        startService(i);
        new Thread(){
            @Override
            public void run() {
                //连接服务器
                connectTCPServer();
            }
        }.start();
    }

    @Override
    protected void onDestroy() {
        if(mClientSocket != null){
            //关闭 Socket
            try {
              mClientSocket.shutdownInput();
              mClientSocket.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        super.onDestroy();
    }

    @Override
    public void onClick(View v) {
        if(v == mSendButton){
            String msg = mMessagetEditText.getText().toString();
            if(!TextUtils.isEmpty(msg) && mPrintWriter != null){
                Log.i("TCP","sending message to the server");
                mPrintWriter.println(msg);
                mMessagetEditText.setText("");
                String time = formateDateTime(System.currentTimeMillis());
                String showMsg = "self"+time+":"+msg+"\n";
                //显示发送的消息
                mMessagetextView.setText(mMessagetextView.getText() + showMsg);
            }
        }
    }

    private String formateDateTime(long time){
        return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
    }

    /**
     * 连接服务端的 Socket
     */
    private void connectTCPServer(){
        Socket socket = null;
        while (null == socket){
            try{
                socket = new Socket("localhost",8688);
                mClientSocket = socket;
                mPrintWriter = new PrintWriter(new BufferedWriter(
                        new OutputStreamWriter(socket.getOutputStream())),true);
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
                mPrintWriter.println("我是客户端");
                Log.i("TCP","连接到了服务端的Socket");
            }catch (IOException i){
                SystemClock.sleep(1000);
                i.printStackTrace();
                Log.i("TCP","连接服务端的Socket失败,正在重连..."+i.getMessage());
            }
        }

            try{
                //接收服务端发送过来的信息
                BufferedReader br = new BufferedReader(new InputStreamReader(
                        socket.getInputStream()));
                while (!SocketActivity.this.isFinishing()){
                    String msg = br.readLine();
                    if(msg != null){
                        Log.i("TCP","服务端发送的消息:"+msg);
                        String time = formateDateTime(System.currentTimeMillis());
                        String showMsg = "server"+time+":"+msg+"\n";
                        //显示接收的消息
                        mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG,showMsg).sendToTarget();
                    }
                }

                Log.i("TCP","quit...");

                try {
                    if (null != mPrintWriter) {
                        mPrintWriter.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

                try {
                    if (null != br) {
                        br.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

                socket.close();
            }catch (Exception e){
                e.printStackTrace();
            }

    }
}

注意:当客户端销毁的时候记得释放资源,断开 Socket ,断开和服务器的连接。

  • 结果:


    使用 Socket 进行 IPC

3. 总结

到这里我们就已经尝过了所有的 IPC 方式,下一篇我们好好对比总结一下,下一篇再见👋👋👋

Top