IPCは呼ばれるプロセス間通信手段、プロセス間通信の2つのプロセス間のデータ交換の過程を指します。
方法1:バンドルは、Androidの4つの主要コンポーネントの直接プロセス間通信を実装します
アプリケーション1アクティビティ
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.one_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent=new Intent();
Bundle bundle=new Bundle();
bundle.putString("sendMessage","嘿!我正在给你发消息!");
ComponentName componentName=new ComponentName("com.example.ipcdemoapplication","com.example.ipcdemoapplication.MainActivity");
intent.setComponent(componentName);
intent.putExtras(bundle);
startActivity(intent);
}
});
}
}
アプリケーション2コード
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView =(TextView)findViewById(R.id.text);
Bundle bundle=getIntent().getExtras();
if(bundle!=null){
textView.setText(bundle.getString("sendMessage"));
}
}
}
アプリ1のボタンをクリックした後、アプリ2にジャンプして、「Hey!メッセージを送信します!」と出力します。
方法2:ファイル共有を使用する
たとえば、ローカルディレクトリにファイルがある場合、このファイルを使用してデータや特定の操作を共有できます(https://www.jianshu.com/p/55eae30d133c)
SharedPreferencesを介してxmlを共有することでローカルファイル共有を実現する別の方法がありますが、マルチプロセスにはまだいくつかの問題があります。(https://www.jianshu.com/p/4984f66f9a4b)を参照してください。
方法3:メッセンジャーを使用する
Messengerは、さまざまなプロセスでメッセージオブジェクトを転送できます。メッセージに転送するデータを追加して、プロセス間でデータを転送します。Messengerは軽量のIPCソリューションであり、AIDLをカプセル化します。実装は比較的簡単です。まず、サーバー(MessengerServce.java)を作成し、onBindメソッドでMessengerを作成し、メッセージ受信ハンドラーをgetBinderに関連付けて取得します。 Binderオブジェクトは、handleMessageメソッドでクライアントから送信された情報を受け入れます。
public class MessengerService extends Service {
private static final String TAG = "WANZIKAIFA";
public static final int MSG_FORMCLIENT = 1000;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case MSG_FORMCLIENT:
Log.e(TAG,"收到客户端信息----"+msg.getData().get("msg"));
//得到客户端传来的Messenger对象
Messenger mMessenger = msg.replyTo;
Message message = Message.obtain(null,MessengerService.MSG_FORMCLIENT);
Bundle mBundle = new Bundle();
mBundle.putString("rep","这里是服务端,我们收到信息了");
message.setData(mBundle);
try {
mMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new Messenger(mHandler).getBinder();
}
}
サービスの登録時に別のプロセスを開始します
<service android:name=".MessengerService" android:process=":remoute"/>
次に、クライアント(MainActivity.java)を作成し、別のプロセスサービスをバインドします。バインドに成功したら、サーバーから返されたBinderオブジェクトに基づいてメッセンジャーを作成し、メッセンジャーを使用してサーバーに情報を送信します。
public class MainActivity extends AppCompatActivity {
private Messenger mMessenger;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.one_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("WANZIKAIFA", "点击按钮 ");
Intent intent = new Intent(MainActivity.this,MessengerService.class);
bindService(intent,mServiceConnon, Context.BIND_AUTO_CREATE);
}
});
}
private ServiceConnection mServiceConnon = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.d("WANZIKAIFA", "这里是客户端,服务端收到了吗 ");
mMessenger = new Messenger(iBinder);
Message mMessage = Message.obtain(null,MessengerService.MSG_FORMCLIENT);
Bundle mBundle = new Bundle();
mBundle.putString("msg","这里是客户端,服务端收到了吗");
mMessage.setData(mBundle);
//将Messenger传递给服务端
mMessage.replyTo = new Messenger(mHandler);
try {
mMessenger.send(mMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
private Handler mHandler= new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case MessengerService.MSG_FORMCLIENT:
Log.d("WANZIKAIFA", "收到服务端信息: "+msg.getData().get("rep"));
break;
}
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mServiceConnon);
}
}
handleMessageコールバックでクライアント情報を受信するときは、Message.replyToを呼び出して、クライアントから渡されたMessengerオブジェクトを取得し、メッセージを作成して、Messengerを介してクライアントに送信します。次に、クライアントはサーバーから情報を受信するためのハンドラーを作成する必要があります。サービスがバインドされると、定義されたハンドラーを関連付ける必要があります。
印刷されたログ
01-01 03:02:51.601 12156 12156 D WANZIKAIFA: 点击按钮
01-01 03:02:52.011 12156 12156 D WANZIKAIFA: 这里是客户端,服务端收到了吗
01-01 03:02:52.012 12565 12565 E WANZIKAIFA: 收到客户端信息----这里是客户端,服务端收到了吗
01-01 03:02:52.013 12156 12156 D WANZIKAIFA: 收到服务端信息: 这里是服务端,我们收到信息了
方法4:AIDLを介したIPC通信
以前のAIDL通信(https://blog.csdn.net/qq_27647919/article/details/107204389)
モード5:ContentProviderモードは、別のアプリケーションプロセスへのオープンプロバイダーデータのクエリを実現します
最初のステップは、データベースを作成し、「game_provider.db」という名前のテーブルを作成することです。名前と説明の2つのフィールドがあります。
public class DBOpenHelper extends SQLiteOpenHelper {
public static final String DB_NAME = "game_provider.db";
public static final String GAME_TABLE_NAME = "game";
public static final int DB_VERSION = 1;
public String CREATE_GAME_TABLE = "create table if not exists " + GAME_TABLE_NAME
+"(_id integer primary key ,"+"name TEXT ,"+"describe TEXT)";
public DBOpenHelper(@Nullable Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_GAME_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
2番目のステップは、データインベントリにデータを入力することです
public class GameProvider extends ContentProvider {
public static final String AUTHORITY = "com.example.myapplication.GameProvider";
public static final Uri GAME_CONTENT_URL = Uri.parse("content://"+AUTHORITY+"/game");
private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private SQLiteDatabase mDB;
private Context mContext;
private String table;
static {
mUriMatcher.addURI(AUTHORITY,"game",0);
}
@Override
public boolean onCreate() {
table = DBOpenHelper.GAME_TABLE_NAME;
mContext = getContext();
initProvider();
return false;
}
private void initProvider() {
Log.d("WANZIKAIFA", "insert into game: ");
mDB = new DBOpenHelper(mContext).getWritableDatabase();
new Thread(new Runnable() {
@Override
public void run() {
mDB.execSQL("delete from "+DBOpenHelper.GAME_TABLE_NAME);
mDB.execSQL("insert into game values(1,'插入第一条','我是第一条');");
}
}).start();
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
String table = DBOpenHelper.GAME_TABLE_NAME;
Cursor mCursor = mDB.query(table,
projection,
selection,
selectionArgs,
null,
sortOrder,
null);
return mCursor;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
mDB.insert(table,null,contentValues);
mContext.getContentResolver().notifyChange(uri,null);
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
return 0;
}
}
プロセス間の通信をテストするには、プロバイダーを別のプロセスに書き込みます
<provider
android:authorities="com.example.myapplication.GameProvider"
android:name=".GameProvider"
android:process=":provider"/>
アクティビティに別のデータを挿入します
public class ContentProviderActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.contentprovier_activity);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Uri uri = Uri.parse("content://com.example.myapplication.GameProvider");
ContentValues mContentValues = new ContentValues();
mContentValues.put("_id",2);
mContentValues.put("name","插入第二条");
mContentValues.put("describe","我是第二条");
getContentResolver().insert(uri,mContentValues);
Cursor gameCursor = getContentResolver().query(uri,new String[]{"name","describe"},null,null,null);
while (gameCursor.moveToNext()){
Game mGame = new Game(gameCursor.getString(0),gameCursor.getString(1));
Log.d("WANZIKAIFA",mGame.gameName+"---"+mGame.gameDescribe);
}
}
});
}
}
game.java
public class Game implements Parcelable {
public String gameName;
public String gameDescribe;
public Game(String gameName,String gameDescribe){
this.gameName = gameName;
this.gameDescribe = gameDescribe;
}
protected Game(Parcel in) {
gameName = in.readString();
gameDescribe=in.readString();
}
public static final Creator<Game> CREATOR = new Creator<Game>() {
@Override
public Game createFromParcel(Parcel in) {
return new Game(in);
}
@Override
public Game[] newArray(int size) {
return new Game[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(gameName);
parcel.writeString(gameDescribe);
}
}
ボタンをクリックすると、出力ログは次のようになります。
01-01 00:10:46.764 8779 8779 D WANZIKAIFA: insert into game:
01-01 00:10:46.842 8736 8736 D WANZIKAIFA: 插入第一条---我是第一条
01-01 00:10:46.842 8736 8736 D WANZIKAIFA: 插入第二条---我是第二条
ログから、プロセス番号pid8779pid8736が2番目のクロスプロセスに正常に挿入されていることがわかります。
方法6ソケットを使用してクロスプロセスチャットプログラムを実現する
Socketは、アプリケーション層とトランスポート層の間に位置する抽象化層であり、TCP / IP層の複雑な操作をいくつかの単純なインターフェースに抽象化し、ネットワークでプロセス通信を実現するためにアプリケーション層によって呼び出されます。ソケットは、ネットワーク伝送制御層のTCPプロトコルとUDPプロトコルにそれぞれ対応するストリームソケットとデータパケットソケットに分けられます。TCPプロトコルは、接続指向で信頼性の高いバイトストリームベースのトランスポートレイヤー通信プロトコルです。3ウェイハンドシェイクプロトコルを使用して接続を確立し、安定性の高いタイムアウト再送信メカニズムを提供します。UDPプロトコルは接続のないプロトコルであり、送信されるデータパケットの信頼性を保証するものではありません。一度に少量のデータを送信するのに適しています。UDP送信の信頼性は、アプリケーション層の責任です。ネットワーク品質が非常に不十分な環境では、UDPプロトコルパケットの損失はより深刻になります。ただし、UDPプロトコルの特性上、接続タイプのプロトコルではないため、リソース消費量が少なく、処理速度が速いというメリットがあります。そのため、通常、音声、動画、通常のデータは、UDPプロトコルを使用して送信されます。
権限を追加するための最初のステップ
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
2番目のステップは、サーバーを実装することです
実際のプロセスは、スレッドでTCPサービスを作成し、ポート8688を監視し、クライアントが接続するのを待ち、クライアントが接続したときにソケットを生成することです。毎回作成されるソケットを介して、さまざまなクライアントと通信できます。クライアントが切断すると、サーバーはソケットも閉じて呼び出しスレッドを終了します。サーバーは最初にクライアントに「こんにちは、私はサーバーです」というメッセージを送信し、クライアントから送信されたメッセージを受け入れ、受信したメッセージを処理してクライアントに返します。
public class SocketServerService extends Service {
private boolean isServiceDestoryed = false;
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private class TcpServer implements Runnable {
@Override
public void run() {
ServerSocket serverSocket = null;
try {
//监听8688窗口
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
e.printStackTrace();
}
while (!isServiceDestoryed){
try {
//接受客户端请求,并且阻塞直接接受消息
final Socket client = serverSocket.accept();
new Thread(){
@Override
public void run() {
responseClient(client);
}
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) {
//用户接受客户端消息
try {
BufferedReader in = new BufferedReader((new InputStreamReader(client.getInputStream())));
//用于向客户端发送消息
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);
out.println("您好呀!我是服务端!");
while (!isServiceDestoryed){
String str = in.readLine();
Log.d("WANZIKAIFA", "收到客户端的信息: "+str);
if (TextUtils.isEmpty(str)){
//客户端断开了连接
Log.d("WANZIKAIFA","客户端断开连接");
break;
}
String message = "收到了客户端的信息为"+ str;
//从客户端收到的消息加工再发给客户端
out.println(message);
}
out.close();
in.close();
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
isServiceDestoryed = true;
super.onDestroy();
}
}
3番目のステップは、クライアントを実装することです
プロセス:クライアントはサービスを開始し、サーバーソケットに接続するためのスレッドを開始します。
public class SocketClientActivity extends Activity {
private EditText et_receiver;
private Button bt_send;
private TextView tv_message;
private PrintWriter mPrintWriter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_socket);
initView();
Intent intent = new Intent(this,SocketServerService.class);
startService(intent);
new Thread(){
@Override
public void run() {
connectSocketServer();
}
}.start();
}
private void initView() {
et_receiver = (EditText)findViewById(R.id.et_receiver);
bt_send =(Button)findViewById(R.id.bt_send);
tv_message = (TextView)findViewById(R.id.tv_message);
bt_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final String msg = et_receiver.getText().toString();
//向服务端发送信息
if (!TextUtils.isEmpty(msg)&&null!=mPrintWriter){
new Thread(new Runnable() {
@Override
public void run() {
mPrintWriter.println(msg);
}
}).start();
tv_message.setText(tv_message.getText()+"\n"+"客户端:"+msg);
Log.d("WANZIKAIFA", tv_message.getText()+"\n"+"客户端:"+msg);
et_receiver.setText("");
}
}
});
}
private void connectSocketServer() {
Socket socket = null;
while (socket==null){
//选择和服务器相同的端口8688
try {
socket = new Socket("localhost",8688);
mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
} catch (IOException e) {
e.printStackTrace();
}
}
//接受服务器端的消息
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (!isFinishing()){
final String msg = br.readLine();
if (msg!=null){
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_message.setText(tv_message.getText()+"\n"+"服务端:"+msg);
Log.d("WANZIKAIFA", tv_message.getText()+"\n"+"服务端:"+msg);
}
});
}
}
mPrintWriter.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ログを次のように印刷します
01-01 02:08:54.459 9771 9771 D WANZIKAIFA: 服务端:您好呀!我是服务端!
01-01 02:09:28.140 9790 9814 D WANZIKAIFA: 收到客户端的信息: 收到
01-01 02:09:28.142 9771 9771 D WANZIKAIFA: 客户端:收到
01-01 02:09:28.190 9771 9771 D WANZIKAIFA: 服务端:收到了客户端的信息为收到
ウェイセブン放送
たとえば、放送を開始します
public class BootUpReceiver extends BroadcastReceiver {
private static final String cTag = "BootUpReceiver";
@Override
public void onReceive( final Context context, final Intent intent ) {
LogTool.d(cTag, " BootUpReceiver");
}
}
登録リスト:
<receiver android:name=".BootUpReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</receiver>
最後に、これらのIPC通信方法の長所と短所を分析します。(https://www.jianshu.com/p/71480c680a65)から要約します。
1.バンドル:
シンプルで使いやすいが、バンドルでサポートされているオブジェクトのみを送信できます。多くの場合、4つの主要コンポーネント間のプロセス間通信に使用されます
。2。ファイル共有:
使いやすいですが、高い同時実行性には適さず、ファイルの読み取りに時間がかかり、すぐに通信できません。これは、並行性の程度が高くなく、リアルタイム要件が高くない状況でよく使用され
ます。3。AIDL:
強力で、1対多の同時通信をサポートし、インスタントメッセージングをサポートしますが、使用がより複雑で、マルチスレッド同期の問題に対処する必要があります。多くの場合、1対多の通信で使用されます。 RPC要件がある
場合(サーバーとクライアントの通信)4。メッセンジャー:この
関数は通常、1対多のシリアル通信とリアルタイム通信をサポートしますが、高い同時実行性の条件を適切に処理できません。バンドルでサポートされているタイプのみを送信できます。多くの場合、低い同時実行性で使用されます。 RPCには1
対多の機会が必要です。5。ContentProvider:
強力なデータソースアクセス、1対多の同時操作のサポート、拡張可能な呼び出し方法はAIDLの制約付きバージョンとして理解でき、1対多のデータ共有によく使用されるCRUD操作とカスタム関数を提供します。機会
6.ソケット:
強力で、ネットワークを介してバイトストリームを送信でき、1対多の同時操作をサポートしますが、実装が面倒で、ネットワークデータ交換によく使用される直接RPCをサポートしません。
総括する
クロスプロセスの4つの主要コンポーネント間でデータを転送するだけの場合は、Bundle
を使用するのが簡単で便利です。アプリケーションの内部データを共有する場合は、ContentProviderを使用する方が便利です。
並行性の程度が高くない場合、つまり、プロセス間でアクセスされる場合があります。メッセンジャーは通信に使用できます
。ソケットはネットワークデータ共有を設計するときに使用されます。
要件が複雑で高い同時実行性が必要で、リアルタイム通信が必要でRPC要件が必要な場合は、AIDLを使用する必要があります。
ファイル共有方法は、キャッシュ共有などの一部の機能に使用されます。