图片

图片

作者介绍

RichsJeson曾就任于亚信科技担任 Android 端主要负责人,现就职某知名互联网公司高级软件开发工程师,花名 Jeson,目前从事 Android 端客户端开发和应用框架技术研究的工作。并且参与 Google Fuchsia 团队的 fuchsia 项目开发以及对 Fuchsia 系统的源码分析。


引言

延续上篇《一种可复用的View的引用方式》,由于 setId 的方式太频繁了,在代码可读性和扩展方面能力较弱。因此,需要采用另外一种方式来解决 ID 设置频繁的问题。

这只是一个原因,另外一个原因是新的需求要求横竖屏布局的情况下实时显示按钮、图片、文字的变化。状态如下:未安装学生端的情况下显示下载按钮->下载中->下载完成->安装中->打开学生端。

于是再次到源码中寻找答案。查看源码以后,提出了一个问题:“需求中的横竖屏切换所使用到的层是一样的,那么能否使用观察者的模式进行事件分发后进行响应呢?” 。YES,在 android 的底层,安卓通过状态机来维护网络、声音、4G、蓝牙等状态,而且这些状态都有一个状态机维护,通过系统多个状态机组成一个大的控制端-状态机树,安卓使用状态机树对每个状态机的状态进行处理。

那么,基于横竖屏实时更新按钮状态的能力也可以基于状态机来实现。这样就能彻底解决以上业务。而且不仅仅是横竖屏,很多地方都可以使用到状态机树。

状态机

      mP0     /   \    mP1   mS0   /   \  mS2   mS1 /  \    \mS3  mS4  mS5

那么什么是状态机呢?状态机顾名思义就是对多个状态的管理,每个状态都有自身实现业务的能力,通过状态机来切换一组状态。

安卓中的状态机,是维护安卓中每一个业务的状态,并且可以执行状态切换、状态保持等能力。比如我们 Launcher 主页下拉出来的设置选项,选项中包含了数据、WLAN、手电筒、声音、蓝牙等这些按钮,供用户进行选择。当用户选择开关蓝牙的时候,由安卓的系统状态机切换状态至蓝牙的状态机。这是由于安卓系统为了好管理状态,将每个模块的不同状态组合封装成一个状态机。而这些大大小小的状态机组合成一棵树-状态机树,如上图所示。

了解状态机的原理,我们更多关注的是整个状态机的运作流程是怎样。安卓的状态机充分利用了消息收发的这一项功能,使得消息可以在各个状态中进行处理。

在安卓底层源码中就有一颗这样的树-StateMachine。StateMachine 正是利用了这一特点将条消息发送至消息队列中。每个状态通过 processMessage 方法中获取到从消息队列中已取出的消息,并且执行自身的业务。例如:StateMachine 发送一条消息到消息队列中,将当前状态 A 切换至目标状态B,状态 B 就能接收到消息队列取出来的消息,并对消息做处理。

然而需要注意的是,在状态没有切换的情况下,即使状态机发送了消息,目标 B 状态也仍然收不到消息。这样设计的好处,就是消息不会滥用,而且安卓的状态机中仅能存放的只有 10 条。

状态机的使用

从事件分发到状态机,现在就将这套实现原理应用到项目上。首先,我们来看下 StateMachine 的实现方式,StateMachine 中维护着一个 Handler-SmHandler,该类充当了StateMachine 的助理角色,维护了一组状态以及一组消息,其作用如下:

  • 接收消息,并将消息分发至某一个状态上;

  • 从消息队列中移除旧的消息;

  • 维持消息队列;

  • 添加状态;

  • 执行状态切换;

现在,来学习下如何使用状态机吧!

步骤1:创建状态机,并继承 StateMachine。该状态机执行切换状态和将参数体转换至消息中的能力。

/*** Created by richsjeson on 2017/8/4.* @see <p>VR学生端引导页的状态机</p>*/public class VrStudentStateMachine extends StateMachine implements VrStudentState {private List<BusinessObserve> mViews;private Context mContext;private BaseState beforeState;private BaseState aflterState;private BaseState progressState;private BaseState packageInstallState;private BaseState exceptionErrorState;private BaseState downCompleteState;public static class VrInStallExec {    public static final int STATE_ERROR=0;    public static final int STATE_UNINSTALL = 1;    public static final int STATE_PROGRESS = 2;    public static final int STATE_DOWNCOMP = 3;    public static final int STATE_INSTALLPROGESS=4;    //    @IntDef({STATE_UNINSTALL, STATE_PROGRESS, STATE_DOWNCOMP,STATE_INSTALLPROGESS,STATE_ERROR})    public @interface  VrState {}}protected VrStudentStateMachine(String name) {    super(name);}protected VrStudentStateMachine(String name, Looper looper) {    super(name, looper);}public void addLayoutObservers(BusinessObserve observable,Context mContext){    this.mContext=mContext;    if(mViews==null){        mViews=new ArrayList<BusinessObserve>();    }    mViews.add(observable);}public void startMachine(){   if(beforeState==null){       beforeState=new UnInstallState(mViews,this,mContext);       addState(beforeState);   }   if(aflterState==null){       aflterState=new DownCompleteState(mViews,this,mContext);       addState(aflterState);   }   if(progressState==null){       progressState=new ProgressState(mViews,this,mContext);       addState(progressState);   }   if(packageInstallState==null){       packageInstallState=new PackageInstallState(mViews,this,mContext);       addState(packageInstallState);   }   if(downCompleteState==null){       downCompleteState=new DownCompleteState(mViews,this,mContext);       addState(downCompleteState);   }   if(exceptionErrorState==null){       exceptionErrorState=new ErrorState(mViews,this,mContext);       addState(exceptionErrorState);   }   setInitialState(beforeState);   this.start();}//获取当前的statepublic VrStudentState getNowState(){    if(getCurrentState()== progressState){        return (VrStudentState) progressState;    }else if(getCurrentState()==beforeState){        return (VrStudentState) beforeState;    }else if(getCurrentState()==aflterState){        return (VrStudentState)aflterState;    }    return (VrStudentState) beforeState;}@Overridepublic void transitionToAflter() {    transitionTo(aflterState);    sendMessage(obtainMessage(STATE_DOWNCOMP));}@Overridepublic void transitionToBefore() {    transitionTo(beforeState);    sendMessage(obtainMessage(STATE_UNINSTALL));}@Overridepublic void transitionToProgress() {    transitionTo(progressState);    sendMessage(obtainMessage(STATE_PROGRESS));}@Overridepublic void trainsitionToInstall() {    transitionTo(packageInstallState);    sendMessage(obtainMessage(STATE_INSTALLPROGESS));}@Overridepublic void trainsitionToError() {    transitionTo(exceptionErrorState);    sendMessage(obtainMessage(STATE_ERROR));}}

步骤2:创建状态,以未下载安装学生端为例

/*** Created by richsjeson on 2017/8/4.*/public class UnInstallState extends BaseState {public UnInstallState(List<BusinessObserve> mViews, VrStudentStateMachine mMachine, Context mContext) {    super(mViews, mMachine, mContext);}@Overridepublic void processMessage(BaseState state) {}@Overridepublic void dispatchView() {    if(mViews!=null && mViews.size()>0){        for(BusinessObserve observer: mViews) {            observer.dispatchView(this, null);        }    }}@Overridepublic boolean processMessage(Message msg) {    super.processMessage(msg);    android.util.Log.i("VRMachine","BeforeState");    switch (msg.what){        case STATE_UNINSTALL:            //分发事件至View上            if(mViews!=null && mViews.size()>0){                for(BusinessObserve observer: mViews) {                    observer.dispatchView(this, msg);                }            }            break;        case STATE_PROGRESS:            //切换至下载中的状态            mMachine.transitionToProgress();            break;        case STATE_DOWNCOMP:               //下载完成->安装中的状态            mMachine.trainsitionToInstall();        case STATE_INSTALLPROGESS:            //            break;    }    return true;}    }

步骤3:创建状态,以下载学生端中为例

public class ProgressState extends BaseState {/** *  <p>下载管理器</p> */private DownloadManager.Request mDownloadRequest;private DownloadManager mDownloadManager;private DownloadManagerObserver observer;final static String url="http://cs.101.com/v0.1/realUrl?" +        "path=/ppt_101_mobile/android/vrtransfer/vrstudent.apk&attachment=true&serviceName=ppt_101_mobile";;private long downloadId=0;public ProgressState(List<BusinessObserve> mViews, VrStudentStateMachine mMachine, Context mContext) {    super(mViews, mMachine, mContext);}@Overridepublic void processMessage(BaseState state) {}@Overridepublic void dispatchView() {}@Overridepublic boolean processMessage(Message msg) {     super.processMessage(msg);     android.util.Log.i("VRMachine","start down progress");     startDownloadManager();     return true;}//执行下载操作public void startDownloadManager(){    if(mDownloadManager == null){        mDownloadManager= (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);    }    if(mDownloadRequest==null){        mDownloadRequest=new DownloadManager.Request(Uri.parse(url));        mDownloadRequest.setTitle("VR学生端下载");        mDownloadRequest.setNotificationVisibility(1);        mDownloadRequest.setMimeType("application/vnd.android.package-archive");        mDownloadRequest.allowScanningByMediaScanner();        mDownloadRequest.setDestinationInExternalPublicDir("Download","vrstudent.apk");        if(observer==null){            observer=new DownloadManagerObserver();        }        mContext.getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"),true,observer);    }else{        mDownloadRequest.setDestinationInExternalPublicDir("Download","vrstudent.apk");    }    downloadId=mDownloadManager.enqueue(mDownloadRequest);}//执行状态监听。private class DownloadManagerObserver extends ContentObserver{    public DownloadManagerObserver() {        super(downloadHandler);    }    @Override    public boolean deliverSelfNotifications() {        return super.deliverSelfNotifications();    }    @Override    public void onChange(boolean selfChange) {        updateProgress();    }}private Handler downloadHandler=new Handler(){    @Override    public void handleMessage(Message msg) {        super.handleMessage(msg);        if(msg.arg1==msg.arg2){            mMachine.transitionToAflter();        }else{            if(mViews != null && mViews.size()>0){                for(BusinessObserve observe :mViews){                    Message message=new Message();                    message.what= (int) (((float)msg.arg1/msg.arg2)*100);                    observe.dispatchView(ProgressState.this,message);                }            }        }    }    @Override    public void dispatchMessage(Message msg) {        super.dispatchMessage(msg);    }};private void updateProgress() {    int[] bytesAndStatus = getBytesAndStatus(downloadId);    Message message=Message.obtain();    message.arg1=bytesAndStatus[0];    message.arg2=bytesAndStatus[1];    downloadHandler.sendMessage(message);}/** * 通过query查询下载状态,包括已下载数据大小,总大小,下载状态 * * @param downloadId * @return */private int[]  getBytesAndStatus(long downloadId) {    int[] downloadBytes = new int[2];    DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);    Cursor cursor = null;    try {        cursor = mDownloadManager.query(query);        if (cursor != null && cursor.moveToFirst()) {            //已经下载文件大小            downloadBytes[0] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));            //下载文件的总大小            downloadBytes[1] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));        }    } finally {        if (cursor != null) {            cursor.close();        }    }    return downloadBytes;}}

设计思路

知道了状态机怎么使用,回到业务上,结合 PUB_SUB 模式来解决我们现有的问题吧。我的设计的目标将事件分发至 View 上,并且要实时更新View的状态,同时还要实现View和业务间的隔离。按照常理,很多开发着都会想着如何利用后台服务或者系统自身的广播,以及 Event-BUS 方式去实现。但是这里不打算用以上解决的方式。

每个状态都能实现自身的业务以及去更新UI的能力。首选,在 Fragment 中初始化状态机以及View的集合,并且初始化状态为未下载的状态(BeforeSate)。紧接着在 VrStateLayout 中点击下载,则通知状态机将当前状态(BeforeStates)切换至下载中的状态(ProgressState)。ProgressState 接收到传入的消息,调用 DownloadManager 执行下载的操作,同时DwonloadManager 在下载过程中,利用 PUB-SUB 模式,将下载的进度更新至 VrStateLayout中,VrStateLayout 根据传入的状态类型,进行UI的切换。

现在,来看下 HostFragment(在DEMO中为MainActivity类)这个类的实现吧。这里以 AndroidVRScence 为例。代码如下:

public class MainActivity extends Activity {private FrameLayout mLinearPort;private FrameLayout mLinearLand;private VrStudentGuildLayout mStartVrLinearLayoutPort;private VrStudentGuildLayout mStartVrLinearLayoutLand;private VrStudentStateMachine mMachine;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    this.setContentView(R.layout.activity_main);    mLinearPort= (FrameLayout) this.findViewById(R.id.ll_port);    mLinearPort.setBackgroundColor(Color.RED);    mLinearLand= (FrameLayout) this.findViewById(R.id.ll_land);    mLinearLand.setBackgroundColor(Color.BLACK);    mStartVrLinearLayoutPort=new VrStudentGuildLayout(this,null);    mStartVrLinearLayoutPort.setId(R.id.tv_start_vrresource_port);    mStartVrLinearLayoutLand=new VrStudentGuildLayout(this,null);    mStartVrLinearLayoutLand.setId(R.id.tv_start_vrresource_land);    mLinearPort.addView(mStartVrLinearLayoutPort);    mLinearLand.addView(mStartVrLinearLayoutLand);    if(mMachine==null){        //保存虚拟机状态        mMachine=new VrStudentStateMachine("VrDonwloadState");        mMachine.addLayoutObservers(mStartVrLinearLayoutLand,getBaseContext());        mMachine.addLayoutObservers(mStartVrLinearLayoutPort,getBaseContext());        //启动状态机        mMachine.startMachine();        Message message=Message.obtain();        message.what=STATE_UNINSTALL;        //安卓的状态机需要发送消息到指定的状态中进行响应        mMachine.sendMessage(message);    }}//从xml中配置的按钮点击事件,点击后切换至下载public void onStartVrDownlaod(View view){    Message message=Message.obtain();    message.what=STATE_PROGRESS;    mMachine.sendMessage(message);}}

首先,VrStudentGuildLayout 就是上述的 View,它分为横屏(mStartVrLinearLayoutLand)和竖屏(mStartVrLinearLayoutPort)并且继承于 BusinessObserve。

其次,将 BusinessObserve 放入 Observe 队列中后初始化状态机,初始状态为未下载的状态。

再次,每个状态切换后,根据消息类型更新UI。

代码如下:

package com.richsjeson.vrstate.view;/** * Created by richsjeson on 2017/8/4. *  <p>根据状态机进行UI的处理</p>*/public class VrStudentGuildLayout  extends RelativeLayout implements BusinessObserve {//按钮点击private Button starVrSTtudentStatusButton;//private ImageView mImageView;private TextView mTextView;private UIButton mUIButton;private View rootView;private Handler handler=new Handler();public VrStudentGuildLayout(Context context) {    super(context);    BitmapDrawable bitmap = (BitmapDrawable) mImageView.getDrawable();    bitmap.getBitmap().getHeight();    bitmap.getBitmap().getWidth();}public VrStudentGuildLayout(Context context, @Nullable AttributeSet attrs) {    super(context, attrs);    onCreate(context,attrs);}public VrStudentGuildLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    onCreate(context,attrs);}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)public VrStudentGuildLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {    super(context, attrs, defStyleAttr, defStyleRes);    onCreate(context,attrs);}private  void onCreate(Context context , AttributeSet attrs){    if(rootView==null){        rootView=LayoutInflater.from(context).inflate(R.layout.frame_vr_storehost,this);    }    this.mImageView= (ImageView) rootView.findViewById(R.id.iv_action_image);    this.mTextView= (TextView) rootView.findViewById(R.id.tv_tipvrstart);    this.mUIButton=(UIButton) rootView.findViewById(R.id.download_btn);}@Overridepublic void dispatchView(BaseState mState, Message message) {     //根据状态机类型对UI进行处理。    if(mState instanceof UnInstallState){        //状态机初始化后的布局        beforeStateMachine();    }else if(mState instanceof DownCompleteState){        //状态机结束后        afterStateMachine();    }else if(mState instanceof ProgressState){        //状态机下载按钮的布局        progressStateMachine(message);    }else if(mState instanceof ErrorState){        errorState();    }}private void progressStateMachine(final Message message) {    handler.post(new Runnable() {        @Override        public void run() {            int progress=0;            if(message != null){                progress=message.what;            }            mUIButton.setState(UIButton.STATE_DOWNLOADING);            mUIButton.setProgressText("下载中",progress);        }    });}private void afterStateMachine() {    handler.post(new Runnable() {        @Override        public void run() {            mUIButton.setState(UIButton.STATE_NORMAL);            mUIButton.setCurrentText("安装中...");        }    });}private void beforeStateMachine() {    handler.post(new Runnable() {        @Override        public void run() {            mUIButton.setState(UIButton.STATE_NORMAL);            mUIButton.setCurrentText("下载");            mUIButton.setProgress(0);        }    });}public void errorState(){    handler.post(new Runnable() {        @Override        public void run() {            mUIButton.setState(UIButton.STATE_NORMAL);            mUIButton.setCurrentText("下载失败");            mUIButton.setProgress(0);        }    });}}

总结

通过以上的实现,发现很多业务都可以使用到状态机,但是为了避免过度设计,很多时候,都可以从源码中寻找解决方案,当然了,能简易实现尽量就简易实现,如果场景不复杂,就没有必要考虑那么复杂的因素,否则会造成过度设计导致的代码浪费。

以上就是今天要讲解的内容,剩下的有问题群里可以发问。谢谢大家!


更多相关文章

  1. 状态机设计
  2. 客户端请求服务器时的状态码讲解
  3. 设计模式之状态模式
  4. 最近状态不咋好 ...
  5. 共享可变状态中出现的问题以及如何避免[每日前端夜话0xDB]
  6. Java线程之线程状态的转换
  7. 实现一个简单的 JavaScript 状态机[每日前端夜话0xBF]
  8. js和jquery使按钮失效为不可用状态的方法
  9. 如何在进度条全屏表单界面上添加百分比状态

随机推荐

  1. 星星CheckBox按钮
  2. android工程版key
  3. 史上最全干货:Android中的Intent
  4. SPEEX ON ANDROID
  5. ProgressBar 颜色的设置
  6. android xlistview
  7. Talking about Android Process
  8. android 系统编译要求,官方资料
  9. Android(安卓)BroadcastReceiver 简介
  10. Android local manifest