Android中的服務

Android中的服務

四大組件都是運行在主線程

Android中的服務旺坠,是在后臺運行 堂淡〗坦荩可以理解成是在后臺運行并且是沒有界面的Activity逊谋。

  • Foreground process 前臺進程 ,用戶正在交互 土铺,可以理解成相當于Activity執(zhí)行onResume方法
  • Visible process 可視進程胶滋,用戶沒有在交互板鬓,但用戶還一直能看得見頁面。相當于Activity執(zhí)行了onPause方法
  • Service Process 服務進程 究恤,通過startService()開啟了一個服務
  • Background process 后臺進程俭令。當前用戶看不見頁面,相當于Activity執(zhí)行了onStop方法
  • Empty process 空進程

以上五種進程部宿,優(yōu)先級依次降低

服務的簡單例子

定義一個類繼承Service抄腔。

package com.example.servicedemo;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
 
public class MyService extends Service {
 
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("service", "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }
 
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("service", "onCreate");
 
    }
 
    @Override
    public void onDestroy() {
        Log.d("service", "onDestroy");
        super.onDestroy();
    }
}
 

重寫了onStartCommand、onCreate理张、onDestroy赫蛇。

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.servicedemo.MainActivity">
 
    <Button
        android:id="@+id/bt_start_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="開啟服務"/>
    <Button
        android:id="@+id/bt_stop_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="停止服務"/>
 
</LinearLayout>
 

MainActivity

package com.example.servicedemo;
 
import android.content.Context;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
 
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private Context mContext;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
 
        Button btStartService = (Button) findViewById(R.id.bt_start_service);
        Button btStopService = (Button) findViewById(R.id.bt_stop_service);
        btStartService.setOnClickListener(this);
        btStopService.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt_start_service:
                Intent startIntent = new Intent(mContext, MyService.class);
                startService(startIntent);
                break;
            case R.id.bt_stop_service:
                Intent stopIntent = new Intent(mContext, MyService.class);
                stopService(stopIntent);
                break;
            default:
        }
    }
}

就是兩個按鈕,一個用于開啟服務雾叭,另外一個用于停止服務悟耘。

多次點擊開啟服務按鈕,發(fā)現(xiàn)onCreate方法只會在第一次點擊執(zhí)行织狐,而onStartCommand方法每次點擊都會執(zhí)行一次暂幼,在點擊一次停止服務后,調用onDestroy方法停止了服務赚瘦。

startService 方式開啟服務粟誓,服務就會在后臺長期運行奏寨,直到用戶手動停止(設置-Apps里面) 或者調用StopService方法起意,服務才會被銷毀。

電話竊聽案例

注意:這樣的例子僅僅學習的時候可嘗試病瞳。這種竊聽行為是違法的揽咕。

服務部分

package com.example.servicedemo;
 
import android.app.Service;
import android.content.Intent;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
 
import java.io.IOException;
 
public class MyService extends Service {
 
    private MediaRecorder recorder;
 
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("service", "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }
    // 服務第一次開啟時調用
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("service", "onCreate");
        // 1. 獲取電話管理實例
        TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        // 2. 注冊一個電話狀態(tài)的監(jiān)聽
        telephonyManager.listen(new MyPhoneStateListener(), PhoneStateListener.LISTEN_CALL_STATE);
 
    }
 
    private class MyPhoneStateListener extends PhoneStateListener {
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            switch (state) {
                // 空閑狀態(tài)
                case TelephonyManager.CALL_STATE_IDLE:
                    if (recorder != null) {
                        recorder.stop();
                        recorder.reset(); // 使得對象復用,重新開始錄音機到空閑狀態(tài)套菜,從setAudioSource這一步開始
                        recorder.release(); // 釋放相關資源亲善,最好在錄音完成后調用
                        Log.d("recorder", "錄音完成");
 
                    }
                    break;
                // 響鈴狀態(tài)(未接通)
                case TelephonyManager.CALL_STATE_RINGING:
                    Log.d("recorder", "來自"+incomingNumber+"電話...");
                    break;
                // 已接通
                case TelephonyManager.CALL_STATE_OFFHOOK:
                    //[1]實例化對象
                    recorder = new MediaRecorder();
                    //[2]設置音頻的來源,這里是麥克風
                    recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                    //[3]設置音頻的輸出格式,使用小米5s逗柴,默認MPEG-4
                    recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
                    //[4]設置音頻的編碼方式蛹头,默認AMR-NB,這里用AAC編碼
                    recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
                    //[5]保存的文件路徑,為了號召放在了SD卡根目錄下。m4a是MPEG-4的音頻格式
                    recorder.setOutputFile(Environment.getExternalStorageDirectory().getPath()+"/"+System.currentTimeMillis()+"record.m4a");
                    //[5]準備錄音
                    try {
                        recorder.prepare();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    Log.d("recorder", "準備錄音");
                    recorder.start();
                    Log.d("recorder", "開始錄音");
                    break;
                default:
            }
        }
    }
 
    @Override
    public void onDestroy() {
        Log.d("service", "onDestroy");
        super.onDestroy();
    }
}
 

權限申請戏溺,注意這三個都是危險權限

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.servicedemo.MainActivity">
 
    <Button
        android:id="@+id/bt_start_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="開啟服務"/>
    <Button
        android:id="@+id/bt_stop_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="停止服務"/>
 
</LinearLayout>
 

MainActivity里面動態(tài)申請上面的三個權限

package com.example.servicedemo;
 
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
 
import java.util.ArrayList;
import java.util.List;
 
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private Context mContext;
    private List<String> permissions = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        // 讀取電話狀態(tài)是危險權限
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
            permissions.add(Manifest.permission.READ_PHONE_STATE);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            permissions.add(Manifest.permission.RECORD_AUDIO);
        }
        if (permissions.size() > 0) {
            // list.toArray()返回Object[]渣蜗,不能直接轉成String[],只能對里面的每個Object轉成String旷祸,不是很方便耕拷。使用下面帶參方法
            ActivityCompat.requestPermissions(MainActivity.this,  permissions.toArray(new String[permissions.size()]), 1);
        }
 
        Button btStartService = (Button) findViewById(R.id.bt_start_service);
        Button btStopService = (Button) findViewById(R.id.bt_stop_service);
        btStartService.setOnClickListener(this);
        btStopService.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt_start_service:
                Intent startIntent = new Intent(mContext, MyService.class);
                startService(startIntent);
                break;
            case R.id.bt_stop_service:
                Intent stopIntent = new Intent(mContext, MyService.class);
                stopService(stopIntent);
                break;
            default:
        }
    }
}

點擊按鈕開啟服務后,每次打電話都會錄音到本地了托享。

在服務中注冊廣播接收者

操作特別頻繁的廣播事件骚烧,屏幕的關閉和打開 浸赫,電量的變化等廣播接收器靜態(tài)注冊無效。

而動態(tài)注冊的話赃绊,活動一旦被銷毀既峡,就不能接收到廣播了。為此碧查,我們可以在服務中注冊廣播接收者涧狮,讓其在服務停止時才注銷廣播接收者。這樣使得廣播接收者的生命周期變長么夫。

這個例子就監(jiān)聽屏幕的熄滅和點亮者冤。

首先寫一個服務,已自動注冊到清單文件档痪。

package com.example.broadcastinservice;
 
import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
 
public class ScreenService extends Service {
    private ScreenReceiver receiver;
 
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
 
    @Override
    public void onCreate() {
        super.onCreate();
        // 服務一開啟涉枫,就注冊廣播接收者
        receiver = new ScreenReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
        registerReceiver(receiver, intentFilter);
    }
 
    @Override
    public void onDestroy() {
        // 服務終止時候才注銷廣播接收者
          unregisterReceiver(receiver);
        super.onDestroy();
    }
}
 

然后是廣播接收者

package com.example.broadcastinservice;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
 
public class ScreenReceiver extends BroadcastReceiver {
 
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (Intent.ACTION_SCREEN_ON.equals(action)) {
            Log.d("Screen", "屏幕點亮了");
        } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
            Log.d("Screen", "屏幕熄滅了");
        }
    }
}
 

MainActivity

package com.example.broadcastinservice;
 
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
 
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 在活動里開啟服務,在服務里注冊廣播接收者
        Intent intent= new Intent(MainActivity.this, ScreenService.class);
        startService(intent);
    }
}
 

服務和活動進行通信

之前使用startService的方法只是通知了服務開始運行腐螟,之后他們就再也沒有關系了愿汰,活動銷毀也和服務無關。為了讓活動服務的關系更加緊密乐纸,具體來說可以讓活動能指揮服務衬廷。這會用到之前一直沒有用過的onBind方法,該方法返回一個IBinder汽绢,為此我們需要寫個類繼承它吗跋,然后返回其實例。

service的代碼

package com.example.bindservicetest;
 
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
 
public class MyService extends Service {
 
    private MyBinder myBinder = new MyBinder();
 
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("Binder", "onBind: ");
 
        // 這個返回的binder就是活動里onServiceConnected(ComponentName name, IBinder service)里的service
        // 這樣就能在活動里調用服務的方法的
        return myBinder;
    }
 
    class MyBinder extends Binder {
        public void dowload() {
            Toast.makeText(getApplicationContext(), "調用了Binder的download方法", Toast.LENGTH_SHORT).show();
        }
 
        public void cancel() {
            Toast.makeText(getApplicationContext(), "調用了Binder的download方法", Toast.LENGTH_SHORT).show();
        }
    }
 
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("Binder", "onCreate ");
 
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("Binder", "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("Binder", "onDestroy ");
 
    }
}
 

關鍵的地方宁昭,假如上述的downloadcancel方法存在于Service中跌宛,里面用到了Toast方法,這個方法必須要有上下文Context才能用积仗,否則報錯空指針疆拘。雖然Service也是繼承于Context,但是如果這個service是被new出來的寂曹,那么它只能被當成一個普通的類來看待哎迄。不具備上下文環(huán)境, 會報空指針異常隆圆。

所以bindService的方法應運而生漱挚,由此我們就能調用上述方法。

然在活動里匾灶,關鍵是bindService(bindIntent, connection, BIND_AUTO_CREATE);connection需要重寫棱烂,這里就不繼承了,寫成匿名類的形式阶女。

package com.example.bindservicetest;
 
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
 
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
 
    private MyService.MyBinder binder;
 
    private ServiceConnection connection;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
          connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("Binder", "與服務連接成功");
            // 向下轉型
            binder = (MyService.MyBinder) service;
            // 連接到服務后颊糜,可以在活動里面調用服務的方法
            binder.dowload();
            binder.cancel();
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
          // 該方法在正常解綁時候不會被調用哩治,只在異常銷毀時候調用
            Log.d("Binder", "與服務斷開連接");
        }
    };
        Button btBindService = (Button) findViewById(R.id.bt_bind_service);
        btBindService.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt_bind_service:
                Intent bindIntent = new Intent(MainActivity.this, MyService.class);
                bindService(bindIntent, connection, BIND_AUTO_CREATE);
                break;
            default:
        }
    }
 
    @Override
    protected void onDestroy() {
        // 綁定的服務隨著活動銷毀而停止,活動一但銷毀衬鱼,必須解綁业筏,否則會造成內存泄漏
        unbindService(connection);
        super.onDestroy();
    }
}

上面的代碼,只是綁定服務和解綁服務鸟赫。當解綁服務的時候蒜胖,會調用服務的onDestroy方法。但是如果同時調用了startServicebindService抛蚤,則只unbindService是不會停止服務的台谢,即不會調用服務的onDestroy方法,必須調用stopService方法才能停止服務岁经。又因為解綁服務是必須的朋沮,所以如果遇到同時調用了bindServicestartService,則同時調用unbindServicestopService才能終止服務缀壤。

一般的調用順序如下:

  1. 先啟動服務startService
  2. 再將活動與服務綁定bindService
  3. 解綁服務unbindService
  4. 按需求選擇性調用stopService樊拓,比如音樂播放器,下載器最好不停止服務塘慕。

有幾點注意下:

  • 通過bindService方式開啟服務筋夏,服務不能再設置頁面里面找到,相當于是一個隱形的服務图呢。
  • 多次點擊綁定服務按鈕条篷,也只會執(zhí)行一次。依次執(zhí)行onCreate岳瞭,onBind拥娄,onServiceConnected蚊锹。和startService不一樣瞳筏,不會執(zhí)行onStartCommand方法。
  • unbindService只能解綁一次牡昆,多次解綁會報錯姚炕。
  • 綁定的服務隨著活動的銷毀而停止(僅僅綁定而沒有startService),活動一旦銷毀丢烘,服務也會停止柱宦。可以在活動未銷毀時手動調用unbindService播瞳,一般在活動的onDestroy方法里面執(zhí)行解綁操作掸刊。避免內存泄漏。

啟動服務和綁定服務的區(qū)別

  • startService : 讓服務長期運行在后臺赢乓,但是無法與服務進行通訊
  • bindServcie : 可以與服務進行通訊忧侧,但是無法長期運行在后臺

AIDL介紹

  • 遠程服務 運行在其他應用里面的服務
  • 本地服務 運行在自己應用里面的服務

如果在本應用里想調用其他應用的服務石窑,比如付款時候調用支付寶。需要實現(xiàn)進行進程間通信蚓炬, 又稱為IPC松逊。

aidl:Android interface Defination Language,Android接口定義語言肯夏,專門是用來解決進程間通信的经宏。

實現(xiàn)一個跨程序調用服務方法的例子,生活中常有這樣的例子驯击,比如在美團買美食烁兰,付款的時候可以使用支付寶,調用的方法自然不是美團的徊都。所以跨程序了缚柏,調用了支付寶的支付服務。

步驟

遠程端

Android Studio下碟贾,在main下币喧,file -> New里找到AIDL,新建袱耽∩辈停可以看到,與MainActivity實在同一個包(文件夾)下的朱巨。寫法和接口類似史翘,要注意的是不要加諸如public的修飾符

// IPayAidlInterface.aidl
package com.example.remoteservice;
 
// Declare any non-default types here with import statements
 
interface IPayAidlInterface {
    boolean pay(String username, String password, int money);
}
 

再寫一個支付服務冀续,需要注意的是MyBinder繼承了IPayAidlInterface.Stub琼讽,這個Stub是Android Studio幫我們自動生成的,不要修改洪唐。IPayAidlInterface.Stub同時實現(xiàn)了IPayAidlInterface接口和繼承了Binder钻蹬。

本地端調用pay方法的時候,就會執(zhí)行遠程端PayService里這個已具體實現(xiàn)的pay方法凭需。

package com.example.remoteservice;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
 
public class PayService extends Service {
    private MyBinder myBinder = new MyBinder();
    @Override
    public IBinder onBind(Intent intent) {
        return myBinder;
    }
    // 根據(jù)aidl自動生成的Stub问欠,既繼承了Binder又實現(xiàn)了接口,extends android.os.Binder implements com.example.remoteservice.IPayAidlInterface
    private class MyBinder extends IPayAidlInterface.Stub {
 
        @Override
        public boolean pay(String username, String password, int money) throws RemoteException {
            if ("admin".equals(username) && "123456".equals(password)) {
                Log.d("Remote", "支付X已收到您的付款粒蜈,共"+money+"元顺献!");
                return true;
            } else {
                return false;
            }
        }
    }
}
 

在服務里最好不要寫Toast,否則報錯``Can't create handler inside thread that has not called Looper.prepare()`,原因好像是子線程是沒有消息隊列的

另外枯怖,由于跨程序注整,不能通過顯式Intent通過制定PayService.class來開啟服務,必須在AndroidManifest.xml里制定action

<service
   android:name=".PayService"
   android:enabled="true"
   android:exported="true">
  <intent-filter>
    <action android:name="com.example.remoteservice.ACTION_PAY" />
  </intent-filter>
</service>

遠程端這樣就算寫好了,接下來寫本地端肿轨。

本地端

首先將遠程端的main目錄下的aidl文件夾直接復制到本地端借浊,沒錯,直接復制萝招,啥都不要改蚂斤,這樣可保證兩個aidl的包名一樣。

界面就是輸入賬號和密碼槐沼,點擊付款曙蒸。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.localservice.MainActivity">
    <EditText
        android:id="@+id/et_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="輸入賬戶名"/>
 
    <EditText
        android:id="@+id/et_password"
        android:inputType="textPassword"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="輸入密碼"
        />
 
    <Button
        android:id="@+id/bt_pay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Pay"/>
 
</LinearLayout>
 

MainActivity,尤其注意一點岗钩,Android 5.0之后纽窟,開啟服務的意圖(Intent)必須明確,否則會報錯 -> Service Intent must be explicit兼吓。添加一句 intent.setPackage("com.example.remoteservice");里面填上要調用的遠程服務的包名臂港。

package com.example.localservice;
 
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
 
import com.example.remoteservice.IPayAidlInterface;
 
public class MainActivity extends AppCompatActivity {
 
    private ServiceConnection connection;
    private IPayAidlInterface myPay;
    private EditText etUser;
    private EditText etPassword;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // 這部很關鍵,參數(shù)service應該是MyBinder视搏。但是在本程序中自然訪問不到它审孽。通過調用從遠程端復制過來的aidl的asInterface方法,可獲得這個接口浑娜。
                // 通過這個接口暴露出來的方法佑力,可以執(zhí)行myBinder中已實現(xiàn)的方法
                // 和上面直接向下轉型MyBinder不一樣,這里直接使用的接口而非實現(xiàn)接口后具體的類
                myPay = IPayAidlInterface.Stub.asInterface(service);
            }
 
            @Override
            public void onServiceDisconnected(ComponentName name) {
 
            }
        };
        // Android5.0中service的intent一定要顯式聲明,添加服務所在的包名就可將隱式轉變?yōu)轱@式
        Intent intent = new Intent();
        intent.setPackage("com.example.remoteservice");
        intent.setAction("com.example.remoteservice.ACTION_PAY");
        bindService(intent, connection, BIND_AUTO_CREATE);
 
        Button btPay = (Button) findViewById(R.id.bt_pay);
        etUser = (EditText) findViewById(R.id.et_username);
        etPassword = (EditText) findViewById(R.id.et_password);
 
        btPay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    String username = etUser.getText().toString().trim();
                    String password = etPassword.getText().toString().trim();
                  // 賬號和密碼非空校驗
                    if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
                        Toast.makeText(MainActivity.this, "用戶名和密碼不能為空筋遭!", Toast.LENGTH_SHORT).show();
                    } else {
                      // 賬號和密碼和遠程端的一致才能支付成功
                        boolean hasPayed = myPay.pay(username, password, 1000);
                        if (hasPayed) {
                            Toast.makeText(MainActivity.this, "支付成功打颤!", Toast.LENGTH_SHORT).show();
                        } else {
                          // 密碼或者賬號輸入錯誤,支付失敗
                            Toast.makeText(MainActivity.this, "賬號不存在或者密碼有誤漓滔!", Toast.LENGTH_SHORT).show();
                        }
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
 
    @Override
    protected void onDestroy() {
      // 記得解綁服務
        unbindService(connection);
        super.onDestroy();
    }
}
 

輸入賬號admin编饺,密碼123456,和遠程端一致响驴,就能付款成功了透且!

而且在遠程端會在控制臺打印:支付X已收到您的付款踏施,共1000元石蔗!這個方法確實是跨程序調用了遠程端的。

如果賬號密碼錯誤就是下圖了畅形,不能購買。


by @sunhaiyu

2017.5.26

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末诉探,一起剝皮案震驚了整個濱河市日熬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肾胯,老刑警劉巖竖席,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耘纱,死亡現(xiàn)場離奇詭異,居然都是意外死亡毕荐,警方通過查閱死者的電腦和手機束析,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來憎亚,“玉大人员寇,你說我怎么就攤上這事〉诿溃” “怎么了蝶锋?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵,是天一觀的道長什往。 經(jīng)常有香客問我扳缕,道長,這世上最難降的妖魔是什么别威? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任躯舔,我火速辦了婚禮,結果婚禮上省古,老公的妹妹穿的比我還像新娘庸毫。我一直安慰自己,他們只是感情好衫樊,可當我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布飒赃。 她就那樣靜靜地躺著,像睡著了一般科侈。 火紅的嫁衣襯著肌膚如雪载佳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天臀栈,我揣著相機與錄音蔫慧,去河邊找鬼。 笑死权薯,一個胖子當著我的面吹牛姑躲,可吹牛的內容都是我干的。 我是一名探鬼主播盟蚣,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼黍析,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了屎开?” 一聲冷哼從身側響起阐枣,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蔼两,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體甩鳄,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年额划,在試婚紗的時候發(fā)現(xiàn)自己被綠了妙啃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡俊戳,死狀恐怖揖赴,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情品抽,我是刑警寧澤储笑,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站圆恤,受9級特大地震影響突倍,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望稽犁。 院中可真熱鬧,春花似錦秕磷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瘟芝,卻和暖如春易桃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锌俱。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工晤郑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贸宏。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓造寝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吭练。 傳聞我的和親對象是個殘疾皇子诫龙,可洞房花燭夜當晚...
    茶點故事閱讀 45,937評論 2 361

推薦閱讀更多精彩內容