关于activity:Activity从零开始到掌握工作流开发业务代码

【Activity】从零开始到把握工作流开发业务代码Activity #工作流 引言本文算是本人从零边钻研边实际捣鼓到业务上线后全方位补充的一个笔记,对于技术人员举荐间接从第七章技术选型开始浏览,后面的实践局部根本是集体收集的一些网络材料和集体了解笔记,对于应用工作流实现性能没有任何影响。 再次强调,本文十分十分长,请读者按需观看。 一、工作流术语定义1.1 根本定义Georgakopoulos(乔治亚 * 科普洛斯) 给出的工作流定义是:工作流是将一组工作组织起来以实现某个经营过程,定义了工作的触发程序和触发条件,每个工作能够由一个或多个软件系统实现, 也能够由一个或一组人实现,还能够由一个或多集体与软件系统合作实现。 工作流实现了一整套流程外部主动实现的技术,能够屏蔽掉一些繁琐流程的业务进行简化。如果你的业务中没有工作流技术利用,那么齐全能够敞开本文而后去刷刷视频干点别的。 1.2 相干术语上面工作流程的术语定义。 术语含意不失当比喻工作流工作从开始到实现的过程。蕴含流程逻辑和路线规定,流程逻辑蕴含工作的执行程序,路线规定代表工作执行过程中必须恪守的路线,一项工作或者工作整体过程流程定义图形的流程定义。代表工作流的流程逻辑元素和它们元素关系。相似快递的传输路线流程实例也叫工作。流程定义的运行实例。相似正在运行的汽车工作流零碎存储流程定义,通过工作流引擎组件驱动流程定义进行工作。相似汽车发动机流程定义工具创立和更改流程定义的工具。能够是一个软件的组件,也能够是独立的应用程序。流程定义的工具具备复用性。绘制汽车设计图的工具或者软件,能够批改产品设计参与者参与者能够是形象或者具体的,比方资源集、特定资源、组织单元、角色(一个人在组织外部的作用)、人或零碎(主动代理)。路线上每一个节点的行为流动组成流程定义中的一个逻辑步骤的工作。能够是主动的或人工的。常见的主动流动就是截止期限治理,如果到期未实现就主动发送揭示音讯。手动则是须要人力驱动零碎执行业务规定,比方咱们日常生活的报销申请审批。形容某一项工作的解决细节流动所有者有权发表流动完结或者把工作流程推动到下一个流动参与者快递运输过程中的每一个站点如何解决快递工作所有者有权参加实例执行过程的参与者。相似会议的参加人员,有权加入然而不肯定有执行权工作项流程实例中流动的参与者将要执行的工作比方送货的最初一步须要快递员派送快递到用户手里1.3 为什么被叫做流?各个节点通过内外部驱动触发引起节点的推动,造成一个流式的状态达到业务起点。比方一次用户查看淘宝商品的费用、一次领取胜利后的权利开明、一次用户注册、一次调度工作的运行等,都是能够是一个工作流。 1.4 艰深了解艰深了解:工作流 抽取了流程(例如:销假、报销、岗位调整等流程)运行过程的共性,将业务解决和流程流转剥离,缩小编码过程中的重复性(审批、驳回、转办、挂起、停止等管理工作)操作,并且实现流程流转去纸质化和可视化的技术。 1.5 生命周期一个残缺的工作流生命周期次要有5步: 1、定义:即流程的定义,所有的流程总是从定义开始。次要工作是收集需要并将其转化为流程定义。 2、公布:开发人员将资源打包后在零碎平台中公布流程定义,次要工作流程定义文件/自定义表单/工作监听类等。 3、执行:具体的流程引擎依照下面定义的流程解决路线来执行业务。 4、监控:收集每个工作的后果,将依据不同后果来做解决。 5、优化:此时业务流程曾经实现,须要的就是优化流程或从新设计等。 二、为什么须要工作流2.1 工作流管理系统劣势1、疾速、高效、稳固的流程引擎,引擎反对大并发拜访。2、兼具人工和主动流程,具备显著的“中国流程”特色的柔性。3、灵便的部署形式,反对集中部署、分布式部署。4、高效的流程集成、整合框架;同时反对流程开发。5、国内数十个行业,领有近千个胜利的客户案例。 2.2 业务可视化举个例子,退款自身非常复杂,经营、产品、技术、财务可能都无奈从繁多的角色来解释分明到底退款的整个链路和关键环节,然而通过工作流的形式来出现,则所有人能疾速看到退款到底是个什么样的业务。 通过流程图,咱们能够清晰的看到节点之间的变动。 2.3 业务可编排业务流程的编排在理论的运作过程中可能随时变动,工作流流程具备编排性,能够通过节点疾速变动业务流程,能够灵便的增减节点,并且不会对于整个流程产生影响。 另一方面代码的可编排意味着代码复用性能够显著进步,比方下面的减少一个【敞开用户权利】的节点,或者删除【用户音讯】,咱们只须要批改流程而不须要调整业务代码。 2.4 主动重试局部工作流反对长久化和主动重试能力。比方有时候须要在流程外面动静增删节点,然而动静增删节点可能会呈现失败的状况,呈现这种问题的时候能够通过局部节点重试解决问题。 三、常见开源工作流的比照(国外报告)数据起源:Java Workflow Engines ComparisonFeatureWorkflow ServerActivitijBPMCamundaCopperWorkflow types(工作流类型)State machineState machineState machineState machineState machineSupported databases(反对数据库)MS SQL Server, PostgreSQL, Oracle, MySQL, MongoDBMS SQL, PostgreSQL, Oracle, MySQL, H2, DB2db2, derby, h2, hsqldb, mysql, oracle, postgresql, sqlserverMS SQL, PostgreSQL, Oracle, MySQL, H2, DB2, MariaDBPostgreSQL, Oracle, MySQL, H2, Apache CassandraLong-running operations(是否反对长期运作)YesYesYesYesYesState persistence(状态长久化)Serialization type is defined by Persistence Provider. You can easily control and change the settings saving process.Activiti supports recovery in case of error, and will restart as a transactional state machine at the lowest level.The runtime state of an executing process can be made persistent, for example, in a database. This allows to restore execution states of all running processes in case of unexpected failure.Persistence Strategy based on the following concepts - Compact Tables, Deadlock Avoidance, Control Savepoints, Intelligent Caching, True Concurrency.Not declaredVersioning & upgrading(版本治理和降级)The processes that were created before the schema change, work under the old scheme, whereas the scheme of the specific process is updated after calling the appropriate command.Versioning onlyBothWith the use of additional convertersYes, you can dynamically modify workflows at runtime. As soon as you save the changed code, Copper compiles it automatically and loads it.Scheme format(底层存储格局)The proprietary format based on XML. Import and export to BPMN2 starting with version 2.1.BPMN2BPMN2BPMN2Process scheme is declared as Java-codeInstalling the process in an arbitrary state(是否反对任意状态兼容工作流)Yes, by calling a single methodYesYesYesYesObtaining a list of available states for the current process(是否反对取得以后过程的可用状态列表)Yes, by calling a single methodYes, by calling a single methodNoNoUnknownBuilt-in authorization of access to external actions (commands) for the workflow(是否容许内部拜访外部流程)YesNoNoNoNoTimers and delays(是否反对计时和提早)YesYesYesYesYesObtaining a list of available external actions for the current process(取得以后过程的可用内部口头的列表)Yes, by calling a single methodNoNoNoNoSimulated process execution(是否反对流程单元测试)Yes, this mode is called Pre-Execution.NoNoNoNoModifying schemes at runtime(是否反对在运行时扭转流程)Yes, built in.NoNoNoYesObtaining process lists for Inbox and Outbox folders(获取收件箱和发件箱文件夹的过程列表)YesNoNoNoNo四、工作流应用场景4.1 畛域业务高复杂度比方进销存、CRM、订单治理等具备肯定的畛域复杂度的业务,能够用工作流模式,来实现业务的可视化。 ...

June 17, 2023 · 15 min · jiezi

关于activity:明修栈道越过Android启动栈陷阱

作者:vivo 互联网大前端团队- Zhao Kaiping本文从一例业务中遇到的问题登程,以FLAG_ACTIVITY_NEW_TASK这一flag作为切入点,带大家探索Activity启动前的一项重要的工作——栈校验。 文中列举一系列业务中可能遇到的异样情况,详细描述了应用FLAG_ACTIVITY_NEW_TASK时可能遇到的“坑”,并从源码中探究其根源。只有正当应用flag、launchMode,能力防止因为栈机制的特殊性,导致一系列与预期不符的启动问题。 一、问题及背景利用间互相联动、互相跳转,是实现零碎整体性、体验一致性的重要伎俩,也是最简略的一种办法。 当咱们用最罕用的办法去startActivity时,竟也会遇到失败的状况。在实在业务中,就遇到了这样一例异样:用户点击某个按钮时,想要“简简单单”跳转另一个利用,却没有任何反馈。 经验丰富的你,脑海中是否涌现出了各种猜测:是不是指标Activity甚至指标App不存在?是不是指标Activty没有对外开放?是不是有权限的限度或者跳转的action/uri错了…… 实在的起因被flag、launchMode、Intent等个性层层隐匿,可能超出你此时的思考。 本文将从源码登程,探索前因后果,开展讲讲在startActivity()真正筹备启动一个Activity前,须要通过哪些“磨难”,怎么有据可依地解决由栈问题导致的启动异样。 1.1 业务中遇到的问题业务中的场景是这样的,存在A、B、C三个利用。 (1)从利用A-Activity1跳转至利用B-Activity2; (2)利用B-Activity2持续跳转到利用C-Activity3; (3)C内某个按钮,会再次跳转B-Activity2,但点击后没有任何反馈。如果不通过后面A到B的跳转,C间接跳到B是能够的。 1.2 问题代码3个Activity的Androidmanifest配置如下,均可通过各自的action拉起,launchMode均为规范模式。 <!--利用A--> <activity android:name=".Activity1" android:exported="true"> <intent-filter> <action android:name="com.zkp.task.ACTION_TO_A_PAGE1" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <!--利用B--> <activity android:name=".Activity2" android:exported="true"> <intent-filter> <action android:name="com.zkp.task.ACTION_TO_B_PAGE2" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <!--利用C--> <activity android:name=".Activity3" android:exported="true"> <intent-filter> <action android:name="com.zkp.task.ACTION_TO_C_PAGE3" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>A-1到B-2的代码,指定flag为FLAG_ACTIVITY_NEW_TASK private void jumpTo_B_Activity2_ByAction_NewTask() { Intent intent = new Intent(); intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);}B-2到C-3的代码,未指定flag ...

April 18, 2023 · 3 min · jiezi

关于activity:Android-四大组件之-Activity

读前思考学习一门技术或者看一篇文章最好的形式就是带着问题去学习,这样能力在过程中有茅塞顿开、灯火阑珊的感觉,记忆也会更粗浅。 说下 Activity 的生命周期?Activity A 启动另一个 Activity B 会回调哪些办法?如果 Activity B 是齐全通明呢?如果启动的是一个 Dialog 呢?谈谈 onSaveInstanceState() 办法?何时会调用?如何防止配置扭转时 Activity 重建?优先级低的 Activity 在内存不足被回收后怎么做能够复原到销毁前状态?说下 Activity 的四种启动模式?别离会在什么时候应用?onNewIntent()调用机会?如何启动其余利用的Activity?Activity 生命周期onCreate() -> onStart() - > onResume() -> onPause() -> onStop() -> onDestroy() onCreate(): 在流动第一次被创立时会调用,能够在这个办法中实现初始化操作,如:布局加载、绑定事件等。onStart(): 在流动由不可见变为可见时候调用。onResume(): 在返回栈顶端,取得焦点,能够和用户进行交互。onPause: 失去焦点,零碎去启动另一个流动时候会调用,能够在这里做一些数据保留,资源开释,但不能做耗时操作。onStop: 流动齐全不可见时调用。onDestroy: 流动被销毁时调用。onRestart: 流动由后盾不可见变为前台可见时候调用。启动 ActivityA 启动 B重写 A 和 B 的生命周期办法 A 启动 B com.keven.jianshu E/TAG: A 的 onPause()com.keven.jianshu E/TAG: B 的 onCreate()com.keven.jianshu E/TAG: B 的 onStart()com.keven.jianshu E/TAG: B 的 onResume()com.keven.jianshu E/TAG: A 的 onStop()从 B 返回 A ...

March 31, 2022 · 2 min · jiezi

activiti-工作流activiti-7-中多实例并行会签的实现

在实现并行会签的时候,遇到了奇怪的问题,记录一下方便遇到同样问题的朋友快速解决问题。 问题起源首先,官方文档里提到如何使用多实例,以下是官方提供的sample: <userTask id="miTasks" name="My Task" activiti:assignee="${assignee}"> <multiInstanceLoopCharacteristics isSequential="false" activiti:collection="assigneeList" activiti:elementVariable="assignee" > <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition> </multiInstanceLoopCharacteristics></userTask>解释一下上面的代码,assigneeList 为 集合,例如["kermit", "gonzo", "fozzie"];activiti:elementVariable="assignee" 为 接收 loop 中的值的变量名;activiti:assignee="${assignee}" 相当于将认领人指定为loop中取得的变量对象,就和java 中 foreach 差不多的意思;nrOfInstances:实例总数nrOfActiveInstances:当前活动(即尚未完成)实例的数量。对于顺序多实例,这将始终为1。nrOfCompletedInstances:已完成实例的数量。注意,这三个变量存在当前执行的父执行中。${nrOfCompletedInstances/nrOfInstances >= 0.6 } ,意思不言而喻,就是完成改执行的量大于等于60%就可以通过该节点进入下一节点。 整个这段就是完成会签的雏形。如果完成了60%,我认为绝大部分通过(这里的通过是指完成这个节点任务,和业务上的通过不同。),可以进入下一阶段。 官网就只写到这里。具体可以参考 activiti 用户手册--- 8.5.14。多实例(每个) 然后问题出现了。我参照上列配置,发现nrOfCompletedInstances 始终为0。所以完全没有触发停止循环的条件。 通过跟踪代码,发现当循环中的节点被complete 之后,会经过ParallelMultiInstanceBehavior 这个类的 leave(DelegateExecution execution) 方法 其中一段代码 /** * Called when the wrapped {@link ActivityBehavior} calls the {@link AbstractBpmnActivityBehavior#leave(ActivityExecution)} method. Handles the completion of one of the parallel instances */ public void leave(DelegateExecution execution) { boolean zeroNrOfInstances = false; if (resolveNrOfInstances(execution) == 0) { // Empty collection, just leave. zeroNrOfInstances = true; removeLocalLoopVariable(execution, getCollectionElementIndexVariable()); super.leave(execution); // Plan the default leave execution.setMultiInstanceRoot(false); } int loopCounter = getLoopVariable(execution, getCollectionElementIndexVariable()); int nrOfInstances = getLoopVariable(execution, NUMBER_OF_INSTANCES); int nrOfCompletedInstances = getLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES) + 1; int nrOfActiveInstances = getLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES) - 1; Context.getCommandContext().getHistoryManager().recordActivityEnd((ExecutionEntity) execution, null); callActivityEndListeners(execution); if (zeroNrOfInstances) { return; } DelegateExecution miRootExecution = getMultiInstanceRootExecution(execution); if (miRootExecution != null) { // will be null in case of empty collection setLoopVariable(miRootExecution, NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances); setLoopVariable(miRootExecution, NUMBER_OF_ACTIVE_INSTANCES, nrOfActiveInstances); } .......}可以看到这段代码里,nrOfCompletedInstances 是通过getLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES)取得然后+1 得到的。那我们猜想,这意思应该就是每次完成后,这个计数器就+1,然后存会到参数当中去,在下面的代码中setLoopVariable(miRootExecution, NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances);确实也说明他是这样的操作。 ...

June 25, 2019 · 2 min · jiezi

Android源码分析Android90下的Activity启动流程

前言最近在阅读Android源码时,发现最新的Android9.0源码中startActivity启动Activity的流程相比于低版本的Android源码来说改动较大,且本人在网上也没有找到基于Android9.0的相关源码分析文章。故写下此文,记录下源码追踪流程,方便以后自己复查,同时也分享给有需要的读者。 Activity->startActivity @Override public void startActivity(Intent intent, @Nullable Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } }直接调用startActivityForResult Activity->startActivityForResult public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) { // mParent指的是Activity 赋值是在内部API调用setParent方法 if (mParent == null) { options = transferSpringboardActivityOptions(options); //Instrumentation为工具类ActivityResult为其静态内部类 工具类调用执行开始Activity方法execStartActity //mMainThread为ActivityThread ,getApplicationThread()方法获取的是ApplicationThread实例 //ApplicationThread是ActivityThread的内部类 该类继承ApplicationThreadNative抽象类, //而ApplicationThreadNative继承Binder类并实现IApplicationThread接口 //IApplictionThread继承了IInterface接口 //Binder类继承IBinder接口,这就是为什么execStartActivity方法的第二个参数定义为IBinder Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); if (ar != null) { // ar不为空说明Activity启动成功 mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); } if (requestCode >= 0) { mStartedActivity = true; } cancelInputsAndStartExitTransition(options); } else { if (options != null) { mParent.startActivityFromChild(this, intent, requestCode, options); } else { // Note we want to go through this method for compatibility with // existing applications that may have overridden it. mParent.startActivityFromChild(this, intent, requestCode); } } }调用了mInstrumentation.execStartActivity方法 ...

April 24, 2019 · 11 min · jiezi

Android 原生app获取用户授权访问Autodesk云应用数据

oAuth机制对于网站间的授权管理是很容易实现的,设置好app回调端口,当数据服务提供方拿到其用户授权,则返回授权码发送到回调端口。上一篇文章介绍了如何授权Forge app访问Autodesk 云应用数据,即,获取三条腿的token。但移动端的原生app,授权码返回到发起请求app的某个Activity,可如何得知是哪个Activity并且跳转到此Activity?这时就要用到URL Scheme。iOS和Android都提供了这样的机制。它实现了页面内跳转协议,通过定义自己的scheme协议,非常方便跳转到app中的各个Activity。也让不同app可以唤起其它app的Activity。当然,前提app已经安装到系统。这位大咖对此话题做了专业的讲解,推荐大家先阅读:https://www.jianshu.com/p/7b0…我的同事Bryan搭建了一个框架,演示Android app 集成Forge oAuth,拿到三条腿token,并调用了用户信息(Profile)API,列出登录用户的在Forge数据管理中的基本信息。该框架很好的展示了使用URL Scheme整个过程。https://github.com/dukedhx/oa…我借此学习了有关内容。另外,基于此代码,略微改造,添加流程展示获取Autodesk云应用的Hub,Project,Folder和File,为进一步的综合应用app做一些铺垫。https://github.com/xiaodongli…oAuth相关的几个步骤:1.定义监听oAuth回传的Activity属性 <activity android:name=".MainActivity"> <intent-filter> <action android:name=“android.intent.action.MAIN”/> <category android:name=“android.intent.category.LAUNCHER”/> </intent-filter> <intent-filter> <data android:scheme="@string/FORGE_CALLBACK_SCHEME" android:host="@string/FORGE_CALLBACK_HOST"/> <action android:name=“android.intent.action.VIEW”/> <category android:name=“android.intent.category.DEFAULT”/> <category android:name=“android.intent.category.BROWSABLE”/> </intent-filter> </activity>其中,FORGE_CALLBACK_SCHEME和FORGE_CALLBACK_HOST定义为: <string name=“FORGE_CALLBACK_SCHEME”>lxdapp</string> <string name=“FORGE_CALLBACK_HOST”>mytest3legged</string>同样的, URL Scheme必须和Forge app注册的时候填写的callback URL要一致。 2.登录事件将创建一个Intent.ACTION_VIEW,用以启动网页,URL拼接了Forge app的client id,授权方式 (response_type=code),回传地址和授权的权限范围 跳转到Autodesk登录过程,等待客户授权。public void onLoginClick(View v) { Intent i = new Intent(Intent.ACTION_VIEW); Resources resources = getResources(); i.setData(Uri.parse(getResources().getString(R.string.FORGE_AUTHORIZATION_URL) + “?response_type=code&redirect_uri=” + this.getCallbackUrl() + “&scope=” + resources.getString(R.string.FORGE_SCOPE) + “&client_id=” + resources.getString(R.string.FORGE_CLIENT_ID))); startActivity(i); }3.用户授权后,将对URLScheme地址回传,也就是发起请求的Activity。Activity的OnStart拿到回传信息。由于没有设定android:mimetype, 则任何数据类型都可接收。但我们需要只是授权码,在回传请求的URL参数中。 @Override protected void onStart() { super.onStart(); Intent intent = getIntent(); Uri data = intent == null ? null : intent.getData(); //从回传请求中拿到授权码 String authorizationCode = data == null ? null : data.getQueryParameter(“code”); if (authorizationCode != null && !authorizationCode.isEmpty()) { ……4.接下来的过程就和常规的网页应用类似了,依授权码得到最终的token。5.该样例调用Forge服务采取okhttp3,推荐参考下文的详细讲解:https://www.jianshu.com/p/da4…注:Activity的URLScheme是可能同名的,而且如何保证应用之间跳转和传参的目标正确性,这方面我还在研究中。本例为方便计,将Forge Client Secret也存在了app之中。我想安全的方式是由app服务器端进行secret的管理,当原生app拿到授权码,通过它来向app服务器端发起请求,由app服务器来获取Forge token,再传回原生app。扩展的部分(Hub,Project,Folder和File)只是简单的替换ListView的内容,尚未对页面回退做处理。 ...

February 25, 2019 · 1 min · jiezi

Flutter Exception降到万分之几的秘密

flutter exception闲鱼技术团队于2018年上半年率先引入了Flutter技术实现客户端开发,到目前为止成功改造并上线了复杂的商品详情和发布业务。随着flutter比重越来越多,我们开始大力治理flutter的exception,起初很长一段时间内闲鱼内flutter的exception率一直在千分之几左右。经过我们的整理和解决,解决了90%以上的flutter exception。我们对exception进行了归类,大头主要分为两大类,这两大类堆栈数量很多,占到整体90%左右:1.第一大类的堆栈都指向了setstate#0 State.setState (package:flutter/src/widgets/framework.dart:1141)#1 _DetailCommentWidgetState.replyInput.<anonymous closure>.<anonymous closure> (package:fwn_idlefish/biz/item_detail/fx_detail_comment.dart:479)#2 FXMtopReq.sendReq.<anonymous closure> (package:fwn_idlefish/common_lib/network/src/mtop_req.dart:32)#3 NetService.requestWithModel.<anonymous closure> (package:fwn_idlefish/common_lib/network/src/net_service.dart:58)#4 _rootRunUnary (dart:async/zone.dart:1132)#5 _CustomZone.runUnary (dart:async/zone.dart:1029)#6 _FutureListener.handleValue (dart:async/future_impl.dart:129)2.第二大类堆栈都与buildContext直接或者间接相关#0 Navigator.of (package:flutter/src/widgets/navigator.dart:1270)#1 Navigator.pop (package:flutter/src/widgets/navigator.dart:1166)#2 UploadProgressDialog.hide (package:fwn_idlefish/biz/publish/upload_progress_dialog.dart:35)#3 PublishSubmitReducer.doPost.<anonymous closure> (package:fwn_idlefish/biz/publish/reducers/publish_submit_reducer.dart:418)<asynchronous suspension>#4 FXMtopReq.sendReq.<anonymous closure> (package:fwn_idlefish/common_lib/network/src/mtop_req.dart:32)#5 NetService.requestWithModel.<anonymous closure> (package:fwn_idlefish/common_lib/network/src/net_service.dart:58)#6 _rootRunUnary (dart:async/zone.dart:1132)#7 _CustomZone.runUnary (dart:async/zone.dart:1029)第一类明显与element和sate的生命周期有关。第二类与buildContext有关。buildContext是什么?下面是一段state中获取buildContext的实现Element get _currentElement => _registry[this];BuildContext get currentContext => _currentElement;很明显buildContext其实就是element实例。buildContext是一个接口,element是buildContext的具体实现。所以上面的exception都指向了flutter element和state的生命周期2.flutter 生命周期1.state生命周期2. element 与state生命周期element是由widget createElement所创建。state的生命周期状态由element调用触发。最核心的是在new elment的时候element的state的双向绑定正式建立。在umount的时候element和state的双向绑定断开。3. activity生命周期与state关系flutter提供WidgetsBindingObserver给开发者来监听AppLifecycleState。AppLifecycleState有4中状态1.resumed界面可见,比如应用从后台到前台2.inactive页面退到后台或者弹出dialog等情况下这种状态下接收不到很任何用户输入,但是还会有drawframe的回调3.paused应用挂起,比如退到后台。进入这种状态代表不在有任何drawframe的回调4.suspendingios中没用,puased之后进入的状态,进入这种状态代表不在有任何drawframe的回调看下android生命周期和appLifecycleState、state关系创建2.按home键退到后台3.从后台回到前台4.back键退出当前页面(route pop)5.back键退出应用3.常见的exception例子1.在工程开发中,我们最容易忽略了state的dispose状态。看一段例子:这个例子可能会在某些情况下excetion。在state dispose后,element会和state断开相互引用,如果在这个时候开发者去拿element的位置信息或者调用setstate 刷新布局时就会报异常。最常见的是在一些timer、animate、网络请求等异步逻辑后调用setstate导致的excetion。安全的做法是在调用setstate前判断一下state是否是mounted状态。如下:2.buildcontext使用错误看一段错误使用buildcontext例子上面的错误在于在跨堆栈使用了buildcontext。由于outcontext的生命周期与buttomcontext不一致,在弹出bottomsheet的时候outcontext可以已经处于umount或者deactivite。上面例子正确的做法是使用bottomcontext获取focusScopeNode。我们在跨堆栈传递参数(如bottomsheet、dialog、alert、processdialog等)场景时特别要注意buildcontext的使用。最后不过瘾?如果你还想了解更多关于flutter开发更多有趣的实战经验,就来关注微信公众号 “闲鱼技术”。参考https://github.com/flutter/flutterhttps://flutter.io/docs本文作者:闲鱼技术-虚白阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 27, 2018 · 1 min · jiezi

Android 8.1 源码_组件篇 -- 探讨 Activity 的生命周期

活动状态每个活动在其生命周期中最多可能会有 4 种状态:1、运行状态当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。2、暂停状态当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。你可能会觉得既然活动已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个活动都会占满整个屏幕,比如对话框形式的活动只会占用屏幕中间的部分区域。处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响,)只有在内存极低的情况下,系统才会去考虑回收这种活动。3、停止状态当一个活动不再处于栈顶位置,并且完全不可见的状态,就进入了停止状态。系统仍然会为这种活动保持相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。4、销毁状态当一个活动从返回栈种移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。回调方法Activity 类中定义了 7 个回调方法,覆盖了 Activity 生命周期的每一个环节:onCreate()这个方法你已经看到过很多次了,每个活动中我们都重写了这个方法,它会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如加载布局、绑定事件等。onStart()这个方法在活动由不可见变为可见的时候调用。onResume()这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。onPause()这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗 CPU 的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。onStop()这个方法在活动完全不可见的时候调用。它和 onPause() 方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么 onPause() 方法会得到执行,而 onStop() 方法并不会执行。onDestroy()这个方法在活动被销毁之后调用,之后活动的状态将变为销毁状态。onRestart()这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。生存期以上 7 个方法中除了 onRestart() 方法,其他都是两两对应的,从而可以将活动分为 3 种生存期。完整生存期:活动在 onCreate() 方法和 onDestroy() 方法之间所经历的,就是完整生存期。一般情况下,一个活动会在 onCreate() 方法中完成各种初始化操作,而在 onDestroy() 方法种完成释放内存的操作。可见生存期():活动在 onStart() 方法和 onStop() 方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法,合理地管理那些对用户可见的资源。比如在 onStart() 方法中对资源进行加载,而在 onStop() 方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。前台生存期:活动在 onResume() 方法和 onPause() 方法之间所经历的,就是前台生存期。在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行交互的,我们平时看到和接触最多的就是这个状态下的活动。我们看下官方给出的 Activity 生命周期的示意图:实战演练Code我们先定义一个:NormalActivity<?xml version=“1.0” encoding=“utf-8”?><android.support.constraint.ConstraintLayout xmlns:android=“http://schemas.android.com/apk/res/android" xmlns:app=“http://schemas.android.com/apk/res-auto" xmlns:tools=“http://schemas.android.com/tools" android:layout_width=“match_parent” android:layout_height=“match_parent” tools:context=".NormalActivity”> <TextView android:layout_width=“match_parent” android:layout_height=“wrap_content” android:text=“This is a normal activity” /></android.support.constraint.ConstraintLayout>再定义一个:DialogActivity<?xml version=“1.0” encoding=“utf-8”?><android.support.constraint.ConstraintLayout xmlns:android=“http://schemas.android.com/apk/res/android" xmlns:app=“http://schemas.android.com/apk/res-auto" xmlns:tools=“http://schemas.android.com/tools" android:layout_width=“match_parent” android:layout_height=“match_parent” tools:context=".DialogActivity”> <TextView android:layout_width=“match_parent” android:layout_height=“wrap_content” android:text=“This is a dialog activity” /></android.support.constraint.ConstraintLayout>为了让 DialogActivity 使用对话框式主题,我们在 AndroidManifest.xml 中做如下设置:<?xml version=“1.0” encoding=“utf-8”?><manifest xmlns:android=“http://schemas.android.com/apk/res/android" package=“com.example.marco.activitylifecycletest”> <application android:allowBackup=“true” android:icon="@mipmap/ic_launcher” android:label="@string/app_name” android:roundIcon="@mipmap/ic_launcher_round” android:supportsRtl=“true” android:theme="@style/AppTheme”> <activity android:name=".MainActivity”> <intent-filter> <action android:name=“android.intent.action.MAIN” /> <category android:name=“android.intent.category.LAUNCHER” /> </intent-filter> </activity> <activity android:name=".NormalActivity" /> <activity android:name=".DialogActivity" android:theme="@style/Theme.AppCompat.Dialog"> // Look here </activity> </application></manifest>接下来修改 activity_main.xml,重新定制主活动的布局:<?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:orientation=“vertical” android:layout_width=“match_parent” android:layout_height=“match_parent” tools:context=".MainActivity”> <Button android:id=”@+id/start_normal_activity" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:text=“Start NormalActivity” /> <Button android:id="@+id/start_dialog_activity" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:text=“Start DialogActivity” /></LinearLayout>修改 MainActivity :package com.example.marco.activitylifecycletest;import android.content.Intent;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 { public static final String TAG = “MainActivity”; private Button startNormalActivity = null; private Button startDialogActivity = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startNormalActivity = findViewById(R.id.start_normal_activity); startDialogActivity = findViewById(R.id.start_dialog_activity); startNormalActivity.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, NormalActivity.class); startActivity(intent); } }); startDialogActivity.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, DialogActivity.class); startActivity(intent); } }); } @Override protected void onStart() { super.onStart(); Log.d(TAG, “onStart”); } @Override protected void onResume() { super.onResume(); Log.d(TAG, “onResume”); } @Override protected void onPause() { super.onPause(); Log.d(TAG, “onPause”); } @Override protected void onStop() { super.onStop(); Log.d(TAG, “onStop”); } @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, “onDestroy”); } @Override protected void onRestart() { super.onRestart(); Log.d(TAG, “onRestart”); }}Result(1)当 MainActivity 第一次被创建时,如下方法被执行:2018-10-18 04:31:29.071 2526-2526/? D/MainActivity: onCreate2018-10-18 04:31:29.077 2526-2526/? D/MainActivity: onStart2018-10-18 04:31:29.083 2526-2526/? D/MainActivity: onResume(2)点击 Start NormalActivity 按钮:2018-10-18 04:33:02.159 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onPause2018-10-18 04:33:02.745 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onStop因为 NormalActivity 已经把 MainActivity 完全遮挡住,因此 onPause() 和 onStop() 方法都会得到执行。(3)点击 Back 键返回 MainActivity:2018-10-18 04:35:00.010 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onRestart2018-10-18 04:35:00.012 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onStart2018-10-18 04:35:00.014 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onResume由于之前 MainActivity 已经进入了停止状态,所以 onRestart() 方法会得到执行,之后又会执行 onStart() 和 onResume() 方法。注意,此时 onCreate() 方法不会执行,因为 MainActivity 并没有重新创建。(4)点击 Start DialogActivity 按钮:2018-10-18 04:43:38.006 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onPause通过 Log 可以看到,只有 onPause() 方法得到了执行,onStop() 方法并没有执行,这是因为 DialogActivity 并没有完全遮挡住 MainActivity,此时 MainActivity 只是进入了暂停状态,并没有进入停止状态。(5)点击 Back 键返回 MainActivity:2018-10-18 04:50:12.222 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onResume按下 Back 键返回 MainActivity 也应该只有 onResume() 方法会得到执行。(6)在 MainActivity 界面按下 Back 键退出程序:2018-10-18 04:51:47.673 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onPause2018-10-18 04:51:48.013 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onStop2018-10-18 04:51:48.015 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onDestroy依次会执行 onPause()、onStop() 和 onDestroy 方法,最终销毁 MainActivity。疑问我们在之前分析 Activity 的生命周期的时候曾经提到过:如果一个活动进入了 onStop (停止)状态,是有可能被系统回收的!场景比如我们看以下的场景:应用中有一个活动 A ,用户在活动 A 的基础上启动了活动 B ,活动 A 就进入了停止的状态,这个时候由于系统内存不足,将活动 A 回收掉了,然后用户按下 Back 键返回活动 A ,会出现什么情况呢?其实还是会正常显示活动 A 的,但是此时并不会执行 onRestart() 方法了,而是会执行活动 A 的 onCreate() 方法,因为活动 A 在这种情况下会被重新创建一次。可能这并不会影响正常的功能,但是存在一个特殊情况:如果活动 A 中存在临时数据和状态(比如 A 中有一个文本输入框,我们输入了一些文字,然后启动了 B 活动,如果 A 被 kill了,在重新回到 A 后,A 活动重新创建,那么数据都丢失了),此时会严重影响用户体验,该怎么办?策略其实官方文档给出了解决方案,Activity 中提供了一个 onSaveInstanceState() 回调方法,这个方法可以保证在活动被回收之前一定会被调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。onSaveInstanceState() 方法会携带一个 Bundle 类型的参数,Bundle 提供了一系列的方法用于保存数据,比如可以使用 putString() 方法保存字符串,使用 putInt() 方法保存整型数据,依次类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle 中取值,第二个参数是真正要保存的内容。Code我们现在对上面的代码进行修改,在 MainActivity 中添加如下代码将临时数据进行保存: @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); String tempData = “Something you just typed”; outState.putString(“data_key”, tempData); }Ok,数据保存好了,那我们应该在哪边进行恢复?不知道你有没有发现,在 onCreate() 方法中有一个 Bundle 类型的参数。这个参数一般情况下是 null ,但是如果在活动被系统回收之前有通过 onSaveInstanceState() 方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。修改 MainActivity 的 onCreate() 方法: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, “onCreate”); if (savedInstanceState != null) { String tempData = savedInstanceState.getString(“data_key”); Log.d(TAG, tempData); } … … }通过上面的方法取出值之后再做相应的恢复操作就可以了,比如说将文本内容重新赋值到文本输入框上即可。 ...

December 3, 2018 · 3 min · jiezi