Android中遠端Service和Activity互動
阿新 • • 發佈:2019-01-27
Android四大元件之二: Activity和Service, 兩者在很多情況下, 不是分裂的,那麼,它們如何聯絡起來呢?通常使用IBinder兩者建立關聯, 方法如下:
為了描述兩者的通訊方式, 我們需要建立兩個App, 一個為server, 另一個為client。 一個App內包含server和client的這種本地service我們就不描述, 沒有什麼挑戰。
1. 使用Android studio來建立一個名為AIDLServer的Project
2. 為AIDLServer增加一個AIDL檔案。 裡面的內容為:
// IMyAidlInterface.aidl package com.zltz.www.aidlserver; // Declare any non-default types here with import statements interface IMyAidlInterface { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); int getPid(); }
其實我們就是增加了一個獲取pid的方法getPid()
3. 新建一個MyService.java的檔案, 用於描述Service, 內容如下:
package com.zltz.www.aidlserver; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.Process; import android.util.Log; public class MyService extends Service { public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return (IBinder)mBinder; } private IMyAidlInterface mBinder = new IMyAidlInterface.Stub() { public int getPid() { return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) {} }; }
裡面建立了Binder, 實現了getPid()方法, 這個就是一般的寫法。 到這裡server部分的程式碼就寫完了。 4. 然後我們需要配置manifest檔案了:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.zltz.www.aidlserver"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <service android:name="com.zltz.www.aidlserver.MyService" android:enabled="true" android:exported="true" android:process=":remote" > <!-- 加:和不加:還是有區別。加:表示會建立一個私有的程序; 直接小寫字母開頭表示全域性程序 --> <intent-filter> <action android:name="com.zltz.www.aidlserver.IMyAidlInterface" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service> </application> </manifest>
這裡因為我們是兩個不同的程序, 所以使用了android:process來配置成遠端程序, 注意:remote前面的冒號的描述, 這是一個知識點。 5. 到這裡, 我們的Server端就完成了。 下面要開始client段的編寫了。我們需要使用兩個按鈕和一個textview,一個按鈕用於繫結遠端的service, 一個用於執行遠端service的方法, textview則是用於顯示獲取到的pid。 layout檔案見下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.zltz.www.aidlclient.MainActivity">
<TextView
android:id="@+id/tv_pid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bind_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/bind_name"
android:onClick="BindService"/>
<Button
app:layout_constraintLeft_toRightOf="@+id/bind_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_name"
android:onClick="onGetPid"/>
</android.support.constraint.ConstraintLayout>
裡面使用到來了兩個字串, 需要在values檔案下的strings.xml檔案中新增。 見下:
<resources>
<string name="app_name">AIDLClient</string>
<string name="bind_name">Bind Service</string>
<string name="btn_name">Get Pid</string>
</resources>
6. 要使用遠端service, 那麼得知道遠端的aidl的定義呀。 所以需要定義一個和遠端service的aidl一樣的aidl, 你可以重新寫一邊或者直接從server中拷貝過來。 記得包名要和server中的一致, 否則會出現錯誤。 見下:
// IMyAidlInterface.aidl
package com.zltz.www.aidlserver;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
int getPid();
}
7. 現在, 就差client中的繫結service, 執行service方法的操作了。 見下:
package com.zltz.www.aidlclient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.zltz.www.aidlserver.IMyAidlInterface;
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface mBinder;
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView)findViewById(R.id.tv_pid);
}
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mBinder = IMyAidlInterface.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
public void BindService(View view)
{
/* 如果為android 5.0前的程式碼, 那麼使用
Intent intent = new Intent("com.zltz.www.aidlserver.IMyAidlInterface");
即可, 但是5.0及後面的程式碼, 就需要修改了, 對intent設定Package即可
// 來自 http://blog.csdn.net/guolin_blog/article/details/9797169的評論部分
Google從Android 5.0之後就不再允許使用隱式的方式去啟動和繫結一個Service了,如果採用隱式的方式就會丟擲安全異常。
如果你看5.0之前的4.4的原始碼你就會發現validateServiceIntent這個方法裡面有幾句程式碼是註釋掉的,註釋掉的這部分程式碼
在5.0的時候被放開了,這部分程式碼就是禁止隱式啟動Service的。可以看出其實Google早就想這麼幹了。
private void validateServiceIntent(Intent service)
{
if (service.getComponent() == null && service.getPackage() == null)
{
if (true || getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.KITKAT)
{
Log.w(TAG, "Implicit intents with startService are not safe: " + service + " " + Debug.getCallers(2, 3));
//IllegalArgumentException ex = new IllegalArgumentException( // "Service Intent must be explicit: " + service);
//Log.e(TAG, "This will become an error", ex); //throw ex;
}
}
}
*/
Intent intent = new Intent("com.zltz.www.aidlserver.IMyAidlInterface");
intent.setPackage("com.zltz.www.aidlserver");
//Intent intent = new Intent(IMyAidlInterface.class.getName());
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
public void onGetPid(View view)
{
try {
tv.setText(String.valueOf("Remote Pid:" + mBinder.getPid()) + ", Cur Pid:" + Process.myPid());
}catch(RemoteException ex)
{
Log.e("Err", ex.getMessage());
}
}
}
這裡需要主要bindService的操作, 我在這裡耽誤了一個多小時,原來是android5.0後修改了繫結的處理, 網上程式碼大部分是android4.4或以前的, 直接使用就會出現異常。 8. 至此,我們就完成了一個遠端service和Activity的互動的例子了。 9. 簡單總結一下: 新建一個AIDL描述檔案, 一個Service,在Service中生產Binder, 等待繫結 在需要使用Service的App中新建一個和Service一樣的AIDL描述檔案。 然後在App中啟動繫結, 獲取到Binder後,就可以根據AIDL裡面給出的方法來操作了。 主要點有幾個, 一個是BindService的方法使用, 需要制定Package名, 這個是android5.0後的變化; 一個是Server和Client中的AIDL描述要一致;另外一個就是server中的service要使用android:process來指定為remote, 否則就為本地service(沒有跨程序這個說法了)