四大组件之Service

 

1、Service的用途

它主要用于在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务。必要的时候我们甚至可以在程序退出的情况下,让Service在后台继续保持运行状态。

2、Service的创建

可以在上图那样的选项中创建自己想要的Service,创建好的Service会自动注册在AndroidManifest文件中,不用手动注册。

创建完成后重写里面的几个方法;

 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("TAG","onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i("TAG","onStartCommand");
        super.onDestroy();
    }

    @Override
    public void onCreate() {
        Log.i("TAG","onStartCommand");
        super.onCreate();
    }

以下是如何启动和停止Service

        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                startService(intent);
            }
        });
        findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                stopService(intent);
            }
        });

调用startService()会触发onCreat()和onStartCommand()这两个方法,如果启动后再去尝试调用startService()只会触发onStartCommand方法,不会触发onCreat()方法,除非通过stopService()来调用onDestroy()后再去启动;

以下是连续点击开始按钮的Log信息

3、Service与Activity保持关联

重新Service的时候我们看到有个onBind()方法,这个就是与activity保持关联的方法,通过该方法返回一个Binder的实例来实现绑定,我们可以重写Binder类,在里面实现我们要实现的操作,在Service中的操作如下;

    MyBinder myBinder=new MyBinder();    
    
    class MyBinder extends Binder{
        public void shouMsg(){
            Log.i("TAG","绑成功后调用了该方法");
        }
    }
    @Override
    public IBinder onBind(Intent intent) {
        return myBinder;
    }

然后我们在activity中绑定Service

    //首先定义一个Binder的变量
    MyService.MyBinder myBinder;
    //通过该方法获取到通过onBind()方法返回的Binder
    ServiceConnection serviceConnection = new ServiceConnection() {
        //拿到Binder的实例后就可以调用里面的方法了
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myBinder = (MyService.MyBinder) service;
            myBinder.shouMsg();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

            case R.id.btn_binder:
                //绑定Service
                Intent intent = new Intent(MainActivity.this, MyService.class);
                bindService(intent, serviceConnection, BIND_AUTO_CREATE);
                break;
            case R.id.btn_unbinder:
                //取消绑定
                unbindService(serviceConnection);
                break;

启动Service,然后绑定就能使用里面的方法了,如果要销毁这个Service就必须先停止,然后再去取消绑定;

4、在Service中对于Thread的使用

因为Service和Activity运行在同一线程中,所以不能直接在Service中执行耗时的操作要在onStartCommand()和重写的Binder的耗时的地方创建子线程。

   class MyBinder extends Binder{
        public void shouMsg(){
            Log.i("TAG","绑成功后调用了该方法");
            new Thread(new Runnable() {
                @Override
                public void run() {

                }
            }).start();
        }
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("TAG", "onStartCommand");
        new Thread(new Runnable() {
            @Override
            public void run() {

            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

5、前台Service

  Service的优先级比较低为了能够让Service的优先级高一点或者是为了直观的查看服务的一些工作情况,比如更新后的天气或者下载进度等,有时候我们需要将一些Service设置为前台Service,其实就是通过通知栏来显示,通过Notification来设置要显示的内容,通过startForeForeground()来启动前台Service;

    @Override
    public void onCreate() {
        super.onCreate();
        // 在API11之后构建Notification的方式
        Notification.Builder builder = new                 
    Notification.Builder(this.getApplicationContext()); //获取一个Notification构造器
        Intent nfIntent = new Intent(this, MainActivity.class);
        builder.setContentIntent(PendingIntent.getActivity(this, 0, nfIntent, 0))
                .setContentTitle("下拉列表中的Title") // 设置下拉列表里的标题
                .setContentText("要显示的内容") // 设置上下文内容
                .setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
                .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), 
    R.mipmap.ic_launcher))
                .setWhen(System.currentTimeMillis()); // 设置该通知发生的时间

        Notification notification = builder.build(); // 获取构建好的Notification
        notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音

        startForeground(110, notification);// 开始前台服务
    }

如果要停止这个前台Service,在onDestroy()中使用stopForeground方法就可以了;

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("TAG", "onDestroy");
        stopForeground(true);
    }

6、远程Service

远程Service的存在就是为了能让不同的程序访问同一个后台服务;

首先我们先注册一个远程服务,远程服务创建后将会在新的进程中,只需要在AndroidAndroidManifest中添加android:process="remote"就好了;

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote">
            <intent-filter>
                <action android:name="com.yongjie.demotest.ServiceAIDL"/>
            </intent-filter>
        </service>

我们可以通过以下方法来查看当前运行的组件所在进程。

     /**
     * 获得当前进程的名字
     *
     * @param context
     * @return 进程号
     */
    public static String getCurProcessName(Context context) {

        int pid = android.os.Process.myPid();

        ActivityManager activityManager = (ActivityManager) context
                .getSystemService(Context.ACTIVITY_SERVICE);

        for (ActivityManager.RunningAppProcessInfo appProcess : activityManager
                .getRunningAppProcesses()) {

            if (appProcess.pid == pid) {
                return appProcess.processName;
            }
        }
        return null;
    }

通过在MainActivity的onCreate()和MyService的onCreate()方法中调用该方法,通过Log查看对比就会发现是两个不同的进程;

如果一个Service注册为远程服务,就不能用传统的方法来进行绑定了,需要使用AIDL来进行跨进程通信(IPC);

首先我们在main文件夹下面创建一个aild的文件夹,然后再在里面创建一个包,包名建议跟java的一样,

然后我可以在里面的包通过鼠标右键快捷生成AIDL文件;

文件名可以自定义

这个文件跟我们平时用的接口一样,可以在里面定义自己想要的方法;

package com.yongjie.demotest;

// Declare any non-default types here with import statements

interface ServiceAIDL {
    int  getMSG(int a,int b);
}

当我们编辑好AIDL文件之后去Service中使用会发现没有这个java文件,这时候build一下module就会生成对应的Java文件就可以使用了,在使用的过程中我们把AIDL的java文件生成的对象当成Binder的对象去使用就好了。

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

    ServiceAIDL.Stub mBinder=new ServiceAIDL.Stub() {
        @Override
        public int getMSG(int a, int b) throws RemoteException {
            return a+b;
        }
    };

其他的应用中我们去绑定Service是没有MyService这个类的,所以只能隐式调用,在AndroidManif文件中添加一个action,可以查看之前我注册远程Service的代码。接下来我们就可以去创建一个新的module来去绑定这个Service了,如果要绑定这个Service,因为远程Service使用了AIDL文件所以我们需要相同的文件才能进行配置与绑定,在绑定之前我们将aidl这个文件夹copy到新创建的module的main文件夹里面;

做好以上工作之后我们可以在Activity中通过以下代码来绑定已经启动的远程Service

    ServiceAIDL mBinder;
    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinder = ServiceAIDL.Stub.asInterface(service);
            try {
                int result = mBinder.getMSG(2, 3);
                Log.i("TAG", "在远程获取到的结果:" + result);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_bind).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.yongjie.demotest.ServiceAIDL");
                bindService(intent, connection, BIND_AUTO_CREATE);
                  }
        });
    }

最终我们可能会有以下异常:

java.lang.IllegalArgumentException: Service Intent must be explicit: Intent

原因是:

根据官方文档解释,为了确保应用的安全性,启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 bindService(),系统会抛出异常。(观看文档需要翻墙)

为了解决这个问题我们可以通过以下方法来将隐式Intent转为显示Intent

public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
        // Retrieve all services that can match the given intent
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);//根据自己所需意图返回能够响应的服务列表

        // Make sure only one match was found
        if (resolveInfo == null || resolveInfo.size() != 1) {
            return null;
        }

        // Get component info and create ComponentName
        ResolveInfo serviceInfo = resolveInfo.get(0);//得到对应的Intent,因为只有一个所以索引为0
        String packageName = serviceInfo.serviceInfo.packageName;//得到其包名
        String className = serviceInfo.serviceInfo.name;//得到其类名
        ComponentName component = new ComponentName(packageName, className);

        // Create a new intent. Use the old one for extras and such reuse
        Intent explicitIntent = new Intent(implicitIntent);//使用原Intent创建

        // Set the component to be explicit
        explicitIntent.setComponent(component);//设置component,创建明确的Intent

        return explicitIntent;
    }

 我们在绑定的时候使用该方法就可以成功的去绑定远程服务了;

                Intent intent = new Intent();
                intent.setAction("com.yongjie.demotest.ServiceAIDL");
                final Intent eintent = createExplicitFromImplicitIntent(MainActivity.this, intent);
                bindService(eintent, connection, BIND_AUTO_CREATE);

最终我们查看Log信息就会发现真的通过其他应用绑定了这个远程Service

 I/TAG: 在远程获取到的结果:5

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值