Android高级应用开发(基础篇) - stage5 - 学习笔记

什么是Service

有些用时比较长的操作希望在后台运行,不耽误当前操作

常见操作:访问网络、文件IO操作、大数据的数据库任务,播放音乐等


Service在后台运行,不与用户进行交互,在默认情况下Service运行在应用程序进程的主线程中,

如果需要在Service中处理一些网络连接等耗时的操作,那么应该讲这些任务放在单独线程中处理,避免阻塞用户界面


Questions:

1、启动服务后,按Home键,服务是否还在运行 —— YES

2、启动服务后,退出进程,是否服务还会运行 —— NO

3、启动服务,不用多线程,界面是否会阻塞 —— YES

4、服务是个新的进程么,服务是个新的线程么 —— 服务不是一个新的进程,也不是一个新的线程,是与当前进程绑定的


Service的分类

(一)按照启动方式分类

1、Started Service

(1)startService()启动

(2)一旦启动,就运行在后台,即便启动它的对象Activity都销毁了

(3)通常只启动,不返回值

(4)通常网络上传或者下载,操作完成后,自动停止

onStartCommand()

2、Bound Service

(1)bindService()来绑定

(2)提供客户端服务器接口来启动

(3)发送请求,得到返回值,甚至通过IPC来通讯

(4)一个服务可以被多个调用者绑定,只要有一个绑定,服务运行,所有绑定者都退出,服务退出

onnBind()

(二)按服务性质分

Local Service和Remote Service(有可能是进程与进程之间)

(三)按实现方法分

Java Service和Native Service


简单在Manifest中配置一下即可

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>


生命周期



public class ExampleService extends Service {
    int mStartMode;       // indicates how to behave if the service is killed
    IBinder mBinder;      // interface for clients that bind
    boolean mAllowRebind; // indicates whether onRebind should be used

    @Override
    public void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return mStartMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return mBinder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return mAllowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}


StartedService

创建及使用Service步骤

1、继承Service类实现自己的服务

2、在AndroidManifest中注册服务

3、启动服务startService(),

onStartCommand()返回值:START_STICKY、START_NOT_STICKY、START_REDELIVER_INTENT

·START_STICKY:如果被系统杀死了,可能会尝试重启服务,但不传递Intent

·START_NOT_STICKY:被杀死了不尝试重启

·START_NOT_STICKY:与STICKY一样会尝试重启,会传递Intent

4、停止服务stopService(),传入启动Service时的Intent,

stopSelf()也能够停止服务



IntentService:继承自Service,专门处理异步请求

通过startService(Intent)启动,通过onHandleIntnet(Intent)实现(不是通过onStartCommand())


看一下IntentService的源码



我们看到定义了一个HandlerThread,正因为有了这个HandlerThread,是启动了多线程,所以能够处理异步请求


说明:异步处理服务,新开了一个线程:HandlerThread

在线程中发消息,然后接收

处理完成后,会清除县城,并且关掉服务



BoundService


一、Local BoundService


RPC: Remote Procedure Call


Binder的介绍

kernel层       字符设备驱动 Binder 负责进程间通讯(还有其他通讯方法如管道piper、socket等)

Lib层       Binder框架 配合ServiceManager

framework层 Binder类和IBinder接口


进程之间想互相访问之间的内存地址不能直接访问

所以需要像服务器与客户端之间一样,先由客户端发起一个请求,

服务器端接受请求,找内存地址,进行处理,并返回处理结果给客户端


如何实现呢?

首先要在相当于服务器端的进程中定义并实现一个IBinder接口,暴露给客户端

相当于客户端的进程发起请求,服务器端将内存地址映射到客户端

成为stub(桩程序),也可以理解为句柄handle

服务器端是有指针能够指向内存地址的,而这个stub是一个指向指针的指针

有了这个映射后就可以读取内存了

要明白的是这个调用不是真正的调用

而是先发起的请求,然后服务器端进程调用,然后返回的值

这样的进程与进程之间通信的实现是通过Binder调用的




绑定及使用Service的步骤

1、继承Service或者IntentService类实现自己的服务

2、在AndroidManifest中注册服务

3、绑定服务bindService()

4、取消绑定服务unbindService()



public class LocalService extends Service {
    // Binder given to clients
    private final IBinder mBinder = new LocalBinder();
    // Random number generator
    private final Random mGenerator = new Random();

    /**
     * Class used for the client Binder.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods
            return LocalService.this; // 返回这个服务的一个对象
        }
    }

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

    /** method for clients */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}

onBind方法返回的是一个IBinder接口的实现,要么实现IBinder接口,要么继承Binder类

而LocalBinder是一个继承自Binder的内部类

onBind方法其实就是将Binder类暴露给客户端


调用时要使用bindService方法

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /** Called when a button is clicked (the button in the layout file attaches to
      * this method with the android:onClick attribute) */
    public void onButtonClick(View v) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call were something that might hang, then this request should
            // occur in a separate thread to avoid slowing down the activity performance.
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** Defines callbacks for service binding, passed to bindService() */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            LocalBinder binder = (LocalBinder) service; // 就是之前onBind返回的那个实现了IBinder接口的类的实例
            mService = binder.getService(); // 拿到了服务的对象,就可以调用服务的方法了
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}

binderService有三个参数

Intent : 传值

ServiceConnection:Service连接对象,类似于数据库连接对象,有两个方法onServiceConnected和onServiceDisconnected

int flag:Operation options for the binding


说明:startService方法是通过Activity,new了一个Service

而bindService是Service实例化的自己

所以bindService只是发起一个请求,而Service收到后在考虑要不要实例化


bindService这一步操作是异步的


问题:

1、Service的onBind方法和ServiceConnecton的onServiceConnected方法谁先执行?

回答:onBind方法先于onServiceConnected方法执行

2、Service的onUnBind方法和ServiceConnecton的onServiceDisconnected方法谁先执行?

回答:

3、理解清楚Binder的作用么?

说明:ServiceManager是非常重要的


二、Messenger

三、AIDL


Messenger和AIDL都属于远程Service,将在高级部分中讲解



StartedService & BoundService的区别

1、StartedService和启动它的Acitivity不同生共死

BoundService和启动它的Activity同生共死

如果当前进程关闭了,StartedService也就消亡了

2、每次无论是start还是bind,只要service没有实例化,就都会被create

但start每次都会调用onStartCommand

而bind如果已经绑定,就不会在调用onBind

本质区别是BoundService其实是有一个客户端服务器的概念,借助Binder来通讯


音乐播放器采用StartedService服务的原因:

1、StartedService能与Activity分离,可以后台播放,Activity退出不影响

2、StartedService每次启动都会调用onStartCommand,可以传入不同的命令


现在按home键是能够继续在后台继续运行的,例如播放音乐

但是有可能会被系统杀死的,因为处于Service Progress级别,比可见进程级别低

可以通过在onStartCommand中使用startForeground(int, notification)将服务变为可见进程(在高级篇中讲解)


Service运行中如何与用户交互

在Service中调用Activity是不合理的

所以在Service运行中与用户交互有如下方法:

1、Toast Notification

2、StatusBar Notification


这里研究一下Toast的源码

首先是makeText方法

 public static Toast makeText(Context context, CharSequence text, int duration) {
        Toast result = new Toast(context);

        LayoutInflater inflate = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
        tv.setText(text);
        
        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }

makeText主要是inflate了显示界面,并把显示的值放到TextView中,最后返回Toast对象

这里注意LayoutInflater是context.getSystemService得来的,说明也是系统服务


下面是show方法

public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

这里能看到service.enqueueToast,是有一个消息队列的


TN是Toast的一个内部类

private static class TN extends ITransientNotification.Stub {
        final Runnable mShow = new Runnable() {
            public void run() {
                handleShow();
            }
        };

        final Runnable mHide = new Runnable() {
            public void run() {
                handleHide();
                // Don't do this in handleHide() because it is also invoked by handleShow()
                mNextView = null;
            }
        };

        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        final Handler mHandler = new Handler();    

        int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
        int mX, mY;
        float mHorizontalMargin;
        float mVerticalMargin;

       
        View mView;
        View mNextView;
        
        WindowManagerImpl mWM;

        TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
        }

        /**
         * schedule handleShow into the right thread
         */
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }

        /**
         * schedule handleHide into the right thread
         */
        public void hide() {
            if (localLOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.post(mHide);
        }

        public void handleShow() {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                mWM = WindowManagerImpl.getDefault();
                final int gravity = mGravity;
                mParams.gravity = gravity;
                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                    mParams.horizontalWeight = 1.0f;
                }
                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                    mParams.verticalWeight = 1.0f;
                }
                mParams.x = mX;
                mParams.y = mY;
                mParams.verticalMargin = mVerticalMargin;
                mParams.horizontalMargin = mHorizontalMargin;
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                mWM.addView(mView, mParams);
                trySendAccessibilityEvent();
            }
        }

        private void trySendAccessibilityEvent() {
            AccessibilityManager accessibilityManager =
                    AccessibilityManager.getInstance(mView.getContext());
            if (!accessibilityManager.isEnabled()) {
                return;
            }
            // treat toasts as notifications since they are used to
            // announce a transient piece of information to the user
            AccessibilityEvent event = AccessibilityEvent.obtain(
                    AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
            event.setClassName(getClass().getName());
            event.setPackageName(mView.getContext().getPackageName());
            mView.dispatchPopulateAccessibilityEvent(event);
            accessibilityManager.sendAccessibilityEvent(event);
        }        

        public void handleHide() {
            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
            if (mView != null) {
                // note: checking parent() just to make sure the view has
                // been added...  i have seen cases where we get here when
                // the view isn't yet added, so let's try not to crash.
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }

                mView = null;
            }
        }
    }

可以看到这个内部类TN的show方法是交由Handler去处理

handleShow方法是设定显示的位置




如何在Service和线程之间选择

Service是一个很方便的组件让你运行后台操作,并且不影响当前操作(但还是是在主线程内的)

当你需要在你当前操作的时候另外起一个更独立操作时候可以考虑线程,比如当前程序不关闭的情况下运行音乐

从逻辑上你认为需要一个后台操作,但跟我们现在主线程操作通讯频繁,逻辑关系紧密,就用多线程(直接用)

能够从逻辑上区分开来,就用后台服务




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值