Android中的服务

四大组件都是运行在主线程

Android中的服务,是在后台运行 。可以理解成是在后台运行并且是没有界面的Activity。

  • Foreground process 前台进程 ,用户正在交互 ,可以理解成相当于Activity执行onResume方法
  • Visible process 可视进程,用户没有在交互,但用户还一直能看得见页面。相当于Activity执行了onPause方法
  • Service Process 服务进程 ,通过startService()开启了一个服务
  • Background process 后台进程。当前用户看不见页面,相当于Activity执行了onStop方法
  • Empty process 空进程

以上五种进程,优先级依次降低

服务的简单例子

定义一个类继承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:
        }
    }
}

就是两个按钮,一个用于开启服务,另外一个用于停止服务。

多次点击开启服务按钮,发现onCreate方法只会在第一次点击执行,而onStartCommand方法每次点击都会执行一次,在点击一次停止服务后,调用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. 注册一个电话状态的监听
        telephonyManager.listen(new MyPhoneStateListener(), PhoneStateListener.LISTEN_CALL_STATE);

    }

    private class MyPhoneStateListener extends PhoneStateListener {
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            switch (state) {
                // 空闲状态
                case TelephonyManager.CALL_STATE_IDLE:
                    if (recorder != null) {
                        recorder.stop();
                        recorder.reset(); // 使得对象复用,重新开始录音机到空闲状态,从setAudioSource这一步开始
                        recorder.release(); // 释放相关资源,最好在录音完成后调用
                        Log.d("recorder", "录音完成");

                    }
                    break;
                // 响铃状态(未接通)
                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里面动态申请上面的三个权限

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;
        // 读取电话状态是危险权限
        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:
        }
    }
}

点击按钮开启服务后,每次打电话都会录音到本地了。

在服务中注册广播接收者

操作特别频繁的广播事件,屏幕的关闭和打开 ,电量的变化等广播接收器静态注册无效。

而动态注册的话,活动一旦被销毁,就不能接收到广播了。为此,我们可以在服务中注册广播接收者,让其在服务停止时才注销广播接收者。这样使得广播接收者的生命周期变长。

这个例子就监听屏幕的熄灭和点亮。

首先写一个服务,已自动注册到清单文件。

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出来的,那么它只能被当成一个普通的类来看待。不具备上下文环境, 会报空指针异常。

所以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方式开启服务,服务不能再设置页面里面找到,相当于是一个隐形的服务。
  • 多次点击绑定服务按钮,也只会执行一次。依次执行onCreate,onBind,onServiceConnected。和startService不一样,不会执行onStartCommand方法。
  • unbindService只能解绑一次,多次解绑会报错。
  • 绑定的服务随着活动的销毁而停止(仅仅绑定而没有startService),活动一旦销毁,服务也会停止。可以在活动未销毁时手动调用unbindService一般在活动的onDestroy方法里面执行解绑操作。避免内存泄漏。

启动服务和绑定服务的区别

  • startService : 让服务长期运行在后台,但是无法与服务进行通讯
  • bindServcie : 可以与服务进行通讯,但是无法长期运行在后台

AIDL介绍

  • 远程服务 运行在其他应用里面的服务
  • 本地服务 运行在自己应用里面的服务

如果在本应用里想调用其他应用的服务,比如付款时候调用支付宝。需要实现进行进程间通信, 又称为IPC。

aidl:Android interface Defination Language,Android接口定义语言,专门是用来解决进程间通信的。

实现一个跨程序调用服务方法的例子,生活中常有这样的例子,比如在美团买美食,付款的时候可以使用支付宝,调用的方法自然不是美团的。所以跨程序了,调用了支付宝的支付服务。

步骤

远程端

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同时实现了IPayAidlInterface接口和继承了Binder。

本地端调用pay方法的时候,就会执行远程端PayService里这个已具体实现的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;
    }
    // 根据aidl自动生成的Stub,既继承了Binder又实现了接口,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) {
                // 这部很关键,参数service应该是MyBinder。但是在本程序中自然访问不到它。通过调用从远程端复制过来的aidl的asInterface方法,可获得这个接口。
                // 通过这个接口暴露出来的方法,可以执行myBinder中已实现的方法
                // 和上面直接向下转型MyBinder不一样,这里直接使用的接口而非实现接口后具体的类
                myPay = IPayAidlInterface.Stub.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        };
        // Android5.0中service的intent一定要显式声明,添加服务所在的包名就可将隐式转变为显式
        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

Android中的服务的更多相关文章

  1. Android中Service(服务)详解

    http://blog.csdn.net/ryantang03/article/details/7770939 Android中Service(服务)详解 标签: serviceandroidappl ...

  2. Android中查看服务是否开启的工具类

    这个也是昨天学习的,做下总结. 检查服务是否开启要写成一个工具类,方便使用,传服务的名字返回Boolean值,当然,由于须要,还要传一个上下文context. 说一下这个工具类的几个关键点: 1.方法 ...

  3. Android中的Service详解

    今天我们就来介绍一下Android中的四大组件中的服务Service,说到Service, 它分为本地服务和远程服务:区分这两种服务就是看客户端和服务端是否在同一个进程中,本地服务是在同一进程中的,远 ...

  4. Android中后台的劳动者“服务”

    前言 作为四大组件之一的Service,想必不少开发者都是了解的,那具体熟悉吗?是不是对Service中的每个知识点是否了解,它与Activity的关系又是什么样的,我们所理解的后台服务跟Servic ...

  5. 一个Demo学完Android中所有的服务(转)

    说明:这个例子实现了Android中常见的许多服务,下面是实现的截图 接下来,以源代码的方式分析这个例子   1.MainActivity--主界面 这个类主要是实现用户所看到的这个Activity, ...

  6. Android中直播视频技术探究之---视频直播服务端环境搭建(Nginx+RTMP)

    一.前言 前面介绍了Android中视频直播中的一个重要类ByteBuffer,不了解的同学可以 点击查看 到这里开始,我们开始动手开发了,因为我们后续肯定是需要直播视频功能,然后把视频推流到服务端, ...

  7. Android中如何像 360 一样优雅的杀死后台服务而不启动

    Android中,虽然有很多方法(API或者shell命令)杀死后台`service`,但是仍然有很多程序几秒内再次启动,导致无法真正的杀死.这里主要着重介绍如何像 360 一样杀死Android后台 ...

  8. Android中使用HTTP服务

    在Android中,除了使用java.net包下的API访问HTTP服务之外,我们还可以换一种途径去完成工作.Android SDK附带了Apache的HttpClient API.Apache Ht ...

  9. Android中为什么需要服务?

    在解释这个问题之前, 先来看一个Android系统中进程的优先级(从高到低) 前台进程(foreground process ):  一个应用程序启动, 并且可以直接相应用户的点击,触摸事件.那么这样 ...

随机推荐

  1. react native-调用react-native-fs插件时,如果数据的接口是需要验证信息的,在android上运行报错

    调用react-native-fs插件时,如果数据的接口是需要验证信息的,在android上运行报错,而在iOS上运行没问题.原因是因为接口是有验证信息的,而调用这个插件时没有传入,在iOS上会自动加 ...

  2. 给js动态创建的对象绑定事件

    1.使用原生JS动态为动态创建的对象绑定事件 1-1.创建一个function,用来兼容IE8以下浏览器添加事件 function addEvent(el, type, fn) {  if(el.ad ...

  3. [编织消息框架][netty源码分析]8 Channel 实现类NioSocketChannel职责与实现

    Unsafe是托委访问socket,那么Channel是直接提供给开发者使用的 Channel 主要有两个实现 NioServerSocketChannel同NioSocketChannel 致于其它 ...

  4. B. Karen and Coffee

    B. Karen and Coffee time limit per test 2.5 seconds memory limit per test 512 megabytes input standa ...

  5. oracle创建数据库表空间 用户 授权 导入 导出数据库

    windows下可以使用向导一步一步创建数据库,注意编码. windows连接到某一个数据库实例(不然会默认到一个实例下面):set ORACLE_SID=TEST --登录开始创建表空间及可以操作的 ...

  6. sql hibernate查询转换成实体或对应的VO Transformers

    sql查询转换成实体或对应的VO Transformers //addScalar("id") 默认查询出来的id是全部大写的(sql起别名也无效,所以使用.addScalar(& ...

  7. XML 新手入门基础知识(复制,留着自己看)

    如果您是 XML 新手,本文将为您介绍 XML 文档的基础结构,以及创建构造良好的 XML 需要遵循的规则,包括命名约定.正确的标记嵌套.属性规则.声明和实体.您还可以从本文了解到 DTD 和 sch ...

  8. Vijos 1011 清帝之惑之顺治 记忆录式的动态规划(记忆化搜索)

    背景 顺治帝福临,是清朝入关后的第一位皇帝.他是皇太极的第九子,生于崇德三年(1638)崇德八年八月二ten+six日在沈阳即位,改元顺治,在位18年.卒于顺治十八年(1661),终24岁. 顺治即位 ...

  9. javascript精度问题与调整

    一个经典的问题: 0.1+0.2==0.3 答案是:false 因为:0.1+0.2=0.30000000000000004 第一次看到这个结果就是无比惊讶,下巴碰到地上,得深入了解下问题出在哪里,该 ...

  10. (转载)Oracle10g 数据泵导出命令 expdp 使用总结(二)

    原文链接:http://hi.baidu.com/edeed/item/2c454cff5c559f773d198b94 Oracle10g 数据泵导出命令 expdp 使用总结(一) 1.1.2 e ...