首页 文章 精选 留言 我的

精选列表

搜索[赛博朋克],共10000篇文章
优秀的个人博客,低调大师

效率淘汰赛:AI行业最近三天的三条主线

过去三天(9月7日—9月9日),AI行业的新闻密度依旧很高,但如果只看标题——又是新模型发布,又是融资,又是安全事件——很容易陷入"信息很多、认知没变"的状态。把这些事件放在一起看,会发现它们其实指向同一件事:行业正在从"比谁能力更强"转向"比谁能把能力做得更便宜、更可控"。这篇文章梳理三条主线,并给出一点自己的判断。

优秀的个人博客,低调大师

全球云巨头3A排位赛 云计算改写BAT技术格局

IDC最新的统计数据显示,截至2015年上半年,全球公共云计算市场占有率最高的前五家服务商分别为亚马逊、微软、IBM、RackSpace和阿里云。阿里云已经成为中国云计算市场的领导者,考虑其2015下半年连续两个季度的高增长及未来的增长潜力,全球市场或已形成“3A鼎立”的格局(亚马逊云AWS、阿里云AliCloud、微软云Azure)。 但从阿里巴巴最近几个季度的财报可以看出,云计算已经成为打破技术格局的那个变量。阿里云以出色的业绩,证明了阿里巴巴是一家有足够技术积累的公司。 公开资料显示,从2008年开始阿里就决定做云计算,而且当时的技术决策者并不主张采用市场上流行的开源技术构建自己的云平台,而是逼着技术人员从头开始写每一行代码,如庖丁解牛般深入最底层的技术细节。 与之几乎同步进行且关系密切的另一件中国互联网发展史上的标志性事件就是“去IOE”:阿里内部分步骤、分难易程度逐渐把传统的IT架构用更先进的互联网架构替代,涉及的技术领域依然聚焦在服务器、数据库、存储等底层核心,“去IOE”和云计算成为阿里技术发展史上的一体两面。 就IT技术而言,越是接近系统底层难度越大,越往上层应用相对就简单。好比Windows操作系统之于聊天软件,开发难度天壤之别。而云计算恰恰是集所有底层核心技术之大成。 从2008到2015年,阿里花了7年时间打磨云计算,也为阿里乃至全中国培养了一批最顶尖的底层技术人才。 财报中,阿里巴巴特别提及,阿里云在去年10月打破了一项世界纪录。在由数据库之父Jim Gray创办的排序基准评估竞赛Sort Benchmark中,阿里云把100TB数据的排序时间缩短到了377秒,打破了此前由微软、雅虎等机构研究人员保持的纪录。 不久前,阿里云发布全球首个一站式大数据平台“数加”,对外开放阿里巴巴十年来积累的大数据技术和平台,首批亮相20款产品。这是全球首个囊括了前中后台的大数据一站式开放平台。 在2015年双11,阿里云搭建了全球最大的混合云架构,以公共云与专有云的形式成功运行了淘宝、天猫、支付宝的核心交易,业务上达到了惊人的成绩:最大交易达到每秒钟14万笔,最大支付达到每秒钟8.59万笔。 是时候重新审视阿里巴巴了。那个“会做生意”、“擅长运营”公司已经找到了一个新的发动引擎,云计算足以承担起未来增长,马云“全球买全球卖”的梦想里又多了一件看不见商品叫云计算,技术为阿里铺就了通往103年梦想的新道路。 试问,如果单独拆分出阿里云它股价会是多少?是否又是下一个独角兽? 本文转自d1net(转载)

优秀的个人博客,低调大师

每日一博 | 初探 WebAssembly

1 WebAssembly是什么? 一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果 W3C WebAssembly Community Group开发的一项网络标准,对于浏览器而言,WebAssembly 提供了一条途径,让各种语言编写的代码以接近原生的速度在 Web 中运行。在这种情况下,以前无法以此方式运行的客户端软件等都将可以运行在 Web 中。 WebAssembly 设计之初就决定和 JavaScript 一起协同运行——通过JavaScript 中的 WebAssembly API,可以把 WebAssembly 模块加载到一个 JavaScript 应用中并且在两者之间互相调用。这样可以在同一个应用中使用 WebAssembly 的高性能及 JavaScript 的高灵活性。 2 为什么需要WebAssembly? 众所周知JavaScript是解释型语言,相比于编译型语言需要在运行时转换,所以解释型语言的执行速度要慢于编译型语言。 编译型语言和解释型语言代码执行的具体流程如下: 因为解释型语言每次执行都需要把源码转换一次才能执行,而转换过程非常耗费时间和性能,也就导致在JavaScript背景下,web无法执行一些高性能应用,如图片剪辑、视频剪辑、3D游戏等。 根据MDN的定义,WebAssembly是一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果。可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如C / C ++等语言提供一个编译目标,以便它们可以在Web上运行。它也被设计为可以与JavaScript共存,允许两者一起工作。 3 WebAssembly的工作原理 WebAssembly不被解释,而是由开发者提前编译为WebAssembly二进制格式,如下图所示。由于变量类型都是预知的,因此浏览器加载WebAssembly文件时,JavaScript引擎无须监测代码。它可以简单地将这段代码的二进制格式编译为机器码。 如果将每种编程语言都直接编译为机器码的各个版本,那么效率会很低。编译器中称为前端的部分会将所编写的代码编译为一种中间表示(intermediate representation,IR)。创建好IR代码后,编译器的后端部分会接收IR代码,对其进行优化,然后将其转换为所需要的机器码。 由于浏览器可以在若干不同的处理器(比如桌面计算机、智能手机和平板设备)上运行,因此为每个可能的处理器发布一个WebAssembly代码的编译后版本会非常繁复。替代方法即取得IR代码,并通过一个专门的编译器来运行,这个编译器将IR代码转换为一种专用字节码并放入后缀为.wasm的文件中。此时wasm文件中的字节码还不是机器码,它只是支持WebAssembly的浏览器能够理解的一组虚拟指令。当加载到支持WebAssembly的浏览器中时,浏览器会验证这个文件的合法性,然后这些字节码会继续编译为浏览器所运行的设备上的机器码。如下图 WebAssembly被设计为JavaScript的一个组件,不是它的替代品。虽然有些开发者试图只用WebAssembly来创建整个网站,但这不是普遍情况。一般情况JavaScript仍然是更好的选择。 4 WebAssembly模块内部 模块中不同段的含义说明: 编译器负责生成WebAssembly模块的段,并将它们按照适当顺序放置。 所有的段都是可选的,因此可能存在空模块。 如果指定了已知段,那么它们只能出现一次并且要按照特定顺序出现。 自定义段可以放置在已知段之前、之间或之后,用于指定不适用已知段的数据。 5 哪些语言可用来创建WebAssembly模块? 现在WebAssembly的最小可行性版本(Minimum Viable Product,MVP)还没有垃圾回收(garbage collection,GC),他限制了一些语言的使用。GC作为一种后MVP功能正在开发中,实现之前,有几种语言正在试验WebAssembly支持,方式是将自己的VM编译到WebAssembly,或者在某些情况下将自己的垃圾回收器包含进去。 以下语言正在试验或已经完成WebAssembly支持: C和C++ Rust正致力于成为WebAssembly的首选编程语言。 AssemblyScript是一种新编译器,它用来将TypeScript转换为WebAssembly。 TeaVM是一个将Java转译到JavaScript的工具,现在也可以生成WebAssembly了。 Go 1.11为WebAssembly增加了一个试验性项目,其编译后的WebAssembly模块包含一个垃圾回收器。 Pyodide是Python的一个项目,其中包含了Python科学栈的核心包:Numpy、Pandas和matplotlib。 Blazor是微软的实验性项目,用于将C#引入WebAssembly。 更多列表关注github: WebAssembly支持列表 相关案例: TeaVM:它可以将 JVM 字节码翻译成 JavaScript 和 WebAssembly 我们有一段时间后端开始做一些前端开发,但是结果有时并不尽如人意,关键就在于我们的后端开发人员对前端无论是框架还是语法还是规范,都不是非常了解。这是在所难免的,但是因为业务需要又不得不做。 TeaVM就为我们这种情况提供了一种解决方案,我们的后端开发人员依然使用自己熟悉的语言(java)进行开发。功能开发完成后再将_.class或_.jar文件通过TeaVM编译成wasm或JavaScript供浏览器加载调用。 git:https://github.com/konsoletyper/teavm 官网:https://teavm.org/ 6 WebAssembly可以用在哪? 目前大多数浏览器厂商都已经支持WebAssembly,包括Chrome、Edge、Firefox和Safari。移动端Web浏览器也同样支持。Node.js也从版本8开始支持。 WebAssembly不是JavaScript的替代品,而是它的一个补充,有些情况下WebAssembly是更好的选择,有些情况下使用JavaScript会是一个更优的方案。与JavaScript在同一个VM运行可让两种技术相辅相成。 WebAssembly为非JavaScript的开发者提供了一个新的道路,帮助他们在web中使用自己编写的代码。也让不了解C或C++等语言的web开发者可与访问更新、更快的库。个人理解WebAssembly也可用来优化某些库的执行速度。 6.1 一些使用webAssembly的案例 Figma — 基于浏览器的多人实时协作 UI 设计工具:https://www.figma.com/ Google Earth https://earth.google.com/ - 17年开始支持在FireFox打开,主要依赖webAssembly。之前使用Native Client导致只能在chrome中运行 Magnum — 跨平台的 OpenGL 图形引擎https://github.com/mosra/magnum Egret Engine - 一款HTML5游戏引擎https://github.com/egret-labs/egret-core/ Web-DSP — 使用浏览器就能即时制作多媒体影音特效https://github.com/shamadee/web-dsp 7 WebAssembly怎么用? 7.1 得到wasm文件手动引入 var importObject = { imports: { imported_func: function(arg) { console.log(arg); } } }; // 输出 42 fetch('simple.wasm') .then(res => res.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes, importObject) ).then(results => { results.instance.exports.exported_func(); }); 7.2 得到编译好的npm包引入执行 // alert(`Hello, ${name}`) const js = import("./node_modules/@jdl/hello-wasm/hello_wasm.js"); js.then(js => { js.greet("WebAssembly"); }); 以下为hello_wasm.js文件编译前源码 // rust extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern { pub fn alert(s: &str); } #[wasm_bindgen] pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); } 本文从为什么需要WebAssembly、WebAssembly的工作原理、哪些语言可用来创建WebAssembly模块、WebAssembly可以用在哪里 以及 怎么使用 几方面简要介绍了webAssembly。如果之前没有了解过webAssembly,可以做一些简要的了解。 参考文献 《WebAssembly 实战》 —- C. 杰勒德·加伦特 编译 Rust 为 WebAssembly - WebAssembly | MDN 作者:京东物流 潘维高 来源:京东云开发者社区 自猿其说Tech

优秀的个人博客,低调大师

每日一博 | 明修

作者: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 private void jumpTo_C_Activity3_ByAction_NoTask() { Intent intent = new Intent(); intent.setAction("com.zkp.task.ACTION_TO_C_PAGE3"); startActivity(intent);} C-3到B-2的代码,与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);} 1.3 代码初步分析 仔细查看问题代码,在实现上非常简单,有两个特征: (1)如果直接通过C-3跳B-2,没有任何问题,但A-1已经跳过B-2后,C-3就失败了。 (2)在A-1和C-3跳到B-2时,都设置了flag为FLAG_ACTIVITY_NEW_TASK。 依据经验,我们推测与栈有关,尝试将跳转前栈的状态打印出来,如下图。 由于A-1跳到B-2时设置了FLAG_ACTIVITY_NEW_TASK,B-2跳到C-3时未设置,所以1在独立栈中,2、3在另一个栈中。示意如下图。 C-3跳转B-2一般有3种可能的预期,如下图:预想1,新建一个Task,在新Task中启动一个B-2;预想2,复用已经存在的B-2;预想3,在已有Task中新建一个实例B-2。 但实际上3种预期都没有实现,所有Activity的任何声明周期都没有变化,界面始终停留在C-3。 看一下FLAG_ACTIVITY_NEW_TASK的官方注释和代码注释,如下图: 重点关注这一段: When using this flag, if a task is already running for the activity you are now starting, then a new activity will not be started; instead, the current task will simply be brought to the front of the screen with the state it was last in. 使用此flag时,如果你正在启动的Activity已经在一个Task中运行,那么一个新Activity不会被启动;相反,当前Task将简单地显示在界面的前面,并显示其最后的状态。 ——显然,官方文档与代码注释的表述与我们的异常现象是一致的,目标Activity2已经在Task中存在,则不会被启动;Task直接显示在前面,并展示最后的状态。由于目标Activty3就是来源Activity3,所以页面没有任何变化。 看起来官方还是很靠谱的,但实际效果真的能一直与官方描述一致吗?我们通过几个场景来看一下。 二、场景拓展与验证 2.1 场景拓展 在笔者依据官方描述进行调整、复现的过程中,发现了几个比较有意思的场景。 PS:上面业务的案例中,B-2和C-3在不同应用内,又在相同的Task内,但实际上是否是同一个应用,对结果的影响并不大。为了避免不同应用和不同Task造成阅读混乱,同一个栈的跳转,我们都在本应用内进行,故业务中的场景等价于下面的【场景0】 【场景0】把业务中B-2到C-3的应用间跳转改为B-2到B-3的应用内跳转 // B-2跳转B-3public static void jumpTo_B_3_ByAction_Null(Context context) { Intent intent = new Intent(); intent.setAction("com.zkp.task.ACTION_TO_B_PAGE3"); context.startActivity(intent);} 如下图,A-1设置NEW_TASK跳转B-2,再跳转B-3,最终设置NEW_TASK想跳转B-2。虽然跳C-3改为了跳B-3,但与之前问题的表现一致,没有反应,停留在B-3。 有的读者会指出这样的问题:如果同一个应用内使用NEW_TASK跳转,而不指定目标的taskAffinity属性,实际是无法在新Task中启动的。请大家忽略该问题,可以认为笔者的操作是已经加了taskAffinity的,这对最终结果并没有影响。 【场景1】如果目标Task和来源Task不是同一个,情况是否会如官方文档所说复用已有的Task并展示最近状态?我们改为B-3启动一个新Task的新Activity C-4,再通过C-4跳回B-2 // B-3跳转C-4public static void jumpTo_C_4_ByAction_New(Context context) { Intent intent = new Intent("com.zkp.task.ACTION_TO_C_PAGE4"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent);}// C-4跳转B-2public static void jumpTo_B_2_ByAction_New(Context context) { Intent intent = new Intent(); intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent);} 如下图,A-1设置NEW_TASK跳转B-2,再跳转B-3,再设置NEW_TASK跳转C-4,最终设置NEW_TASK想跳转B-2。 预想的结果是:不会跳到B-2,而是跳到它所在Task的顶层B-3。 实际的结果是:与预期一致,确实是跳到了B-3。 【场景2】把场景1稍做修改:C-4到B-2时,我们不通过action来跳,改为通过setClassName跳转。 // C-4跳转B-2public static void jumpTo_B_2_ByPath_New(Context context) { Intent intent = new Intent(); intent.setClassName("com.zkp.b", "com.zkp.b.Activity2"); // 直接设置classname,不通过action intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent);} 如下图,A-1设置NEW_TASK跳转B-2,再跳转B-3,再设置NEW_TASK跳转C-4,最终设置NEW_TASK想跳转B-2。 预想的结果是:与场景0一致,会跳到B-2所在Task的已有顶层B-3。 实际的结果是:在已有的Task2中,产生了一个新的B-2实例。 仅仅是改变了一下重新跳转B-2的方式,效果就完全不一样了!这与官方文档中提到该flag与"singleTask" launchMode值产生的行为并不一致! 【场景3】把场景1再做修改:这次C-4不跳栈底的B-2,改为跳转B-3,且还是通过action方式。 // C-4跳转B-3public static void jumpTo_B_3_ByAction_New(Context context) { Intent intent = new Intent(); intent.setAction("com.zkp.task.ACTION_TO_B_PAGE3"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent);} 如下图,A-1设置NEW_TASK跳转B-2,再跳转B-3,再设置NEW_TASK跳转C-4,最终设置NEW_TASK想跳转B-3。 预想的结果是:与场景0一致,会跳到B-2所在Task的顶层B-3。 实际的结果是:在已有的Task2中,产生了一个新的B-3实例。 不是说好的,Activity已经存在时,展示其所在Task的最新状态吗?明明Task2中已经有了B-3,并没有直接展示它,而是生成了新的B-3实例。 【场景4】既然Activity没有被复用,那Task一定会被复用吗?把场景3稍做修改,直接给B-3指定一个单独的affinity。 <activity android:name=".Activity3" android:exported="true" android:taskAffinity="b3.task"><!--指定了亲和性标识--> <intent-filter> <action android:name="com.zkp.task.ACTION_TO_B_PAGE3" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter></activity> 如下图,A-1设置NEW_TASK跳转B-2,再跳转B-3,再设置NEW_TASK跳转C-4,最终设置NEW_TASK想跳转B-3。 ——这次,连Task也不会再被复用了……Activity3在一个新的栈中被实例化了。 再回看官方的注释,就会显得非常不准确,甚至会让开发者对该部分的认知产生严重错误!稍微改变过程中的某个毫无关联的属性(如跳转目标、跳转方式……),就会产生很大差异。 在看flag相关注释时,我们要树立一个意识:Task和Activity跳转的实际效果,是launchMode、taskAffinity、跳转方式、Activity在Task中的层级等属性综合作用的结果,不要相信“一面之词”。 回到问题本身,究竟是哪些原因造就了上面的不同效果呢?只有源码最值得信赖了。 三、场景分析与源码探索 本文以Android 12.0源码为基础,进行探究。上述场景在不同Android版本上的表现是一致的。 3.1 源码调试注意事项 源码的调试方法,许多文章已经有了详细的教学,本文不再赘述。此处只简单总结其中需要注意的事项: 下载模拟器时,不要使用Google Play版本,该版本类似user版本,无法选择system_process进程进行断点。 即使是Google官方模拟器和源码,在断点时,也会有行数严重不对应的情况(比如:模拟器实际会运行到方法A,但在源码中打断点时,实际不能定位到方法A的对应行数),该问题并没有很好的处理方法,只能尽量规避,如使模拟器版本与源码版本保持一致、多打一些断点增加关键行数被定位到的几率。 3.2 初步断点,明确启动结果 以【场景0】为例,我们初步确认一下,为什么B-3跳转B-2会无反应,系统是否告知了原因。 3.2.1 明确启动结果及其来源 在Android源码的断点调试中,常见的有两类进程:应用进程和system_process进程。 在应用进程中,我们能获取到应用启动结果的状态码result,这个result用来告诉我们启动是否成功。涉及堆栈如下图(标记1)所示: Activity类::startActivity() → startActivityForResult() →Instrumentation类::execStartActivity(), 返回值result则是ATMS (ActivityTaskManagerService)执行的结果。 如上图(标记2)标注,ATMS类::startActivity()方法,返回了result=3。 在system_process进程中,我们看一下这个result=3是怎样被赋值的。略去详细断点步骤,实际堆栈如下图(标注1)所示: ATMS类::startActivity() →startActivityAsUser() →ActivityStarter类::execute() →executeRequest() →startActivityUnchecked() → startActivityInner() → recycleTask(),在recycleTask()中返回了结果。 如上图(标注2)所示,result在mMovedToFront=false时被赋值,即result=START_DELIVERED_TO_TOP=3,而START_SUCCESS=0才代表创建成功。 看一下源码中对START_DELIVERED_TO_TOP的说明,如下图: Result for IActivityManaqer.startActivity: activity wasn't really started, but the given Intent was given to the existing top activity. (IActivityManaqer.startActivityActivity的结果:Activity并未真正启动,但给定的Intent已提供给现有的顶层Activity。) “Activity并未真正启动”——是的,因为可以复用 “给定的Intent已提供给现有的顶层Activity”——实际没有,顶层Activity3并没有收到任何回调,onNewIntent()未执行,甚至尝试通过Intent::putExtra()传入新的参数,Activity3也没有收到。官方文档又带给了我们一个疑问点?我们把这个问题记录下来,在后面分析。 满足什么条件,才会造成 START_DELIVERED_TO_TOP的结果呢?笔者的思路是,通过与正常启动流程对比,找出差异点。 3.3 过程断点,探索启动流程 一般来说,在定位问题时,我们习惯通过结果反推原因,但反推的过程只能关注到与问题强关联的代码分支,并不能能使我们很好地了解全貌。 所以,本节内容我们通过顺序阅读的方法,正向介绍startActivity过程中与上述【场景01234】强相关的逻辑。再次简述一下: 【场景0】同一个Task内,从顶部B-3跳转B-2——停留在B-3 【场景1】从另一个Task内的C-4,跳转B-2——跳转到B-3 【场景2】把场景1中,C-4跳转B-2的方式改为setClassName()——创建新B-2实例 【场景3】把场景1中,C-4跳转B-2改为跳转B-3——创建新B-3实例 【场景4】给场景3中的B-3,指定taskAffinity——创建新Task和新B-3实例 3.3.1 流程源码概览 源码中,整个启动流程很长,涉及的方法和逻辑也很多,为了便于大家理清方法调用顺序,方便后续内容的阅读,笔者将本文涉及到的关键类及方法调用关系整理如下。 后续阅读中如果不清楚调用关系,可以返回这里查看: // ActivityStarter.java ActivityStarter::execute() { executeRequest(intent) { startActivityUnchecked() { startActivityInner(); } } ActivityStarter::startActivityInner() { setInitialState(); computeLaunchingTaskFlags(); Task targetTask = getReusableTask(){ findTask(); } ActivityRecord targetTaskTop = targetTask.getTopNonFinishingActivity(); if (targetTaskTop != null) { startResult = recycleTask() { setTargetRootTaskIfNeeded(); complyActivityFlags(); if (mAddingToTask) { return START_SUCCESS; //【场景2】【场景3】从recycleTask()返回 } resumeFocusedTasksTopActivities() return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;//【场景1】【场景0】从recycleTask()返回 } } else { mAddingToTask = true; } if (startResult != START_SUCCESS) { return startResult;//【场景1】【场景0】从startActivityInner()返回 } deliverToCurrentTopIfNeeded(); resumeFocusedTasksTopActivities(); return startResult; } 3.3.2 关键流程分析 (1)初始化 startActivityInner()是最主要的方法,如下列几张图所示,该方法会率先调用setInitialState(),初始化各类全局变量,并调用reset(),重置ActivityStarter中各种状态。 在此过程中,我们记下两个关键变量mMovedToFront和mAddingToTask,它们均在此被重置为false。 其中,mMovedToFront代表当Task可复用时,是否需要将目标Task移动到前台;mAddingToTask代表是否要将Activity加入到Task中。 (2)计算确认启动时的flag 该步骤会通过computeLaunchingTaskFlags()方法,根据launchMode、来源Activity的属性等进行初步计算,确认LaunchFlags。 此处重点处理来源Activity为空的各类场景,与我们上文中的几种场景无关,故不再展开讲解。 (3)获取可以复用的Task 该步骤通过调用getReusableTask()实现,用来查找有没有可以复用的Task。 先说结论:场景0123中,都能获取到可以复用的Task,而场景4中,未获取到可复用的Task。 为什么场景4不可以复用?我们看一下getReusableTask()的关键实现。 上图(标注1)中,putIntoExistingTask代表是否能放入已经存在的Task。当flag含有NEW_TASK且不含MULTIPLE_TASK时,或指定了singleInstance或singleTask的launchMode等条件,且没有指定Task或要求返回结果 时,场景01234均满足了条件。 然后,上图(标注2)通过findTask()查找可以复用的Task,并将过程中找到的栈顶Activity赋值给intentActivity。最终,上图(标注3)将intentActivity对应的Task作为结果。 findTask()是怎样查找哪个Task可以复用呢? 主要是确认两种结果mIdealRecord——“理想的ActivityRecord” 和 mCandidateRecord——"候选的ActivityRecord",作为intentActivity,并取intentActivity对应的Task作为复用Task。 什么ActivityRecord才是理想或候选的ActivityRecord呢? 在mTmpFindTaskResult.process()中确认。 程序会将当前系统中所有的Task进行遍历,在每个Task中,进行如上图所示的工作——将Task的底部Activity realActivity与目标Activity cls进行对比。 场景012中,我们想跳转Activity2,即cls是Activity2,与Task底部的realActivity2相同,则将该Task顶部的Activity3 r作为“理想的Activity”; 场景3中,我们想跳转Activity3,即cls是Activity3,与Task底部的realActivity2不同,则进一步判断task底部Activity2与目标Activity3的栈亲和行,具有相同亲和性,则将Task的顶部Activity3作为“候选Activity”; 场景4中,所有条件都不满足,最终没能找到可复用的Task。在执行完getReusableTask()后将mAddingToTask赋值为true 由此,我们就能解释【场景4】中,新建了Task的现象。 (4)确定是否需要将目标Task移动到前台 如果存在可复用的Task,场景0123会执行recycleTask(),该方法中会相继进行几个操作:setTargetRootTaskIfNeeded()、 complyActivityFlags()。 首先,程序会执行 setTargetRootTaskIfNeeded(),用来确定是否需要将目标Task移动到前台,使用mMovedToFront作为标识。 在【场景123】中,来源Task和目标Task是不同的,differentTopTask为true,再经过一系列Task属性对比,能够得出mMovedToFront为true; 而场景0中,来源Task和目标Task相同,differentTopTask为false,mMovedToFront保持初始的false。 由此,我们就能解释【场景0】中,Task不会发生切换的现象。 (5)通过对比flag、Intent、Component等确认是否要将Activity加入到Task中 还是在【场景0123】中,recycleTask()会继续执行complyActivityFlags(),用来确认是否要将Activity加入到Task中,使用mAddingToTask作为标识。 该方法会对FLAG_ACTIVITY_NEW_TASK、 FLAG_ACTIVITY_CLEAR_TASK、 FLAG_ACTIVITY_CLEAR_TOP等诸多flag、Intent信息进行一系列判断。 上图(标注1)中,会先判断后续是否需要重置Task,resetTask,判断条件则是FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,显然,场景0123的resetTask都为false。继续执行。 接着,会有多种条件判断按顺序执行。 在【场景3】中,目标Component(mActivityComponent)是B-3,目标Task的realActivity则是B-2,两者不相同,进入了resetTask相关的判断(标注2)。 之前resetTask已经是false,故【场景3】的mAddingToTask脱离原始值,被置为true。 在【场景012】中,相对比的两个Activity都是B-2(标注3),可以进入下一级判断——isSameIntentFilter()。 这一步判断的内容就很明显了,目标Activity2的已有Intent 与 新的Intent做对比。很显然,场景2中由于改为了setClassName跳转,Intent自然不一样了。 故【场景2】的mAddingToTask脱离原始值,被置为true。 总结看一下: 【场景123】的mMovedToFront最先被置为true,而【场景0】经历重重考验,保持初始值为false。 ——这意味着当有可复用Task时,【场景0】不需要把Task切换到前列;【场景123】需要切换到目标Task。 【场景234】的mAddingToTask分别在不同阶段被置为true,而【场景01】,始终保持初始值false。 ——这意味着,【场景234】需要将Activity加入到Task中,而【场景01】不再需要。 (6)实际启动Activity或直接返回结果 被启动的各个Activity会通过resumeFocusedTasksTopActivities()等一系列操作,开始真正的启动与生命周期的调用。 我们关于上述各个场景的探索已经得到答案,后续流程便不再关注。 四、问题修复及遗留问题解答 4.1 问题修复 既然前面总结了这么多必要条件,我们只需要破坏其中的某些条件,就可以修复业务中遇到的问题了,简单列举几个的方案。 方案一:修改flag。B-3跳转B-2时,增加FLAG_ACTIVITY_CLEAR_TASK或FLAG_ACTIVITY_CLEAR_TOP,或者直接不设置flag。经验证可行。 方案二:修改intent属性,即【场景2】。A-1通过action方式隐式跳转B-2,则B-3可以通过setClassName方式,或修改action内属性的方式跳转B-2。经验证可行。 方案三:提前移除B-2。B-2跳转B-3时,finish掉B-2。需要注意的是,finish()要在startActivity()之前执行,以避免遗留的ActivityRecord和Intent信息对后续跳转的影响。尤其是当你把B-2作为自己应用的deeplink分发Activity时,更值得警惕。 4.2 遗留问题 还记得我们在文章开端的某个疑惑吗,为什么没有回调onNewIntent()? onNewIntent() 会通过deliverNewIntent()触发,而deliverNewIntent()仅通过以下两个方法调用。 complyActivityFlags()就是上文3.3.1.5中我们着重探讨的方法,可以发现complyActivityFlags()中所有可能调用deliverNewIntent()的条件均被完美避开了。 而deliverToCurrentTopIfNeeded()方法则如下图所示。 mLaunchFlags和mLaunchMode,无法满足条件,导致dontStart为false,无缘 deliverNewIntent()。 至此,onNewIntent()的问题得到解答。 五、结语 通过一系列场景假设,我们发现了许多出乎意料的现象: 文档提到FLAG_ACTIVITY_NEW_TASK等价于singleTask,与事实并不完全如此,只有与其他flag搭配才能达到相似的效果。这一flag的注释非常片面,甚至会引发误解,单一因素无法决定整体表现。 官方文档提到START_DELIVERED_TO_TOP会将新的Intent传递给顶层Activity,但事实上,并不是每一种START_DELIVERED_TO_TOP都会把新的Intent重新分发。 同一个栈底Activity,前后两次都通过action或都通过setClassName跳转到时,第二次跳转竟然会失败,而两次用不同方式跳转时,则会成功。 单纯使用FLAG_ACTIVITY_NEW_TASK时,跳栈底Activity和跳同栈内其他Activity的效果大相径庭。 业务中遇到的问题,归根结底就是对Android栈机制不够了解造成的。 在面对栈相关的编码时,开发者务必要想清楚,承担新开应用栈的Activty在应用全局承担怎样的使命,要对Task历史、flag属性、launchMode属性、Intent内容等全面评估,谨慎参考官方文档,才能避免栈陷阱,达成理想可靠的效果。 END 猜你喜欢 Android系统服务DropBoxManagerService详解与实践应用 vivo官网App模块化开发方案-ModularDevToo 本文分享自微信公众号 - vivo互联网技术(vivoVMIC)。 如有侵权,请联系 support@oschina.cn 删除。 本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

每日一博 | Loki 漫谈

Loki诞生背景 Kubernetes已经成为编排领域事实上的标准,同时Prometheus也成为基于Kubernetes平台之上、监控领域的标配。Prometheus能够收集业务metrics数据,Grafana界面展示,AlertManager告警,一站式的监控框架就此诞生。通过这一套框架可以在线监控服务运行状态,如果不正常,能够通过各种途径通知给相关人员;相关人员通过查看告警信息,通过日志分析出现问题具体原因。 如何查看日志? 我们可以进入Pod中查询,如果Pod进程已经崩溃,那么将无法进入容器内部,没关系,Pod所在宿主机挂载的日志文件,你不得不查询已经崩溃Pod所在宿主机,然后通过命令行进入宿主机中查询日志,这样的话如果碰到一个服务多个副本运行在同一个节点上,那么可能会出现日志交叉打印的情况,服务崩溃还没有解决,你已经崩溃了,其实出现这种问题的真正原因是Kubernetes超强的自动横向扩容能力,你可能无法准确预测到服务副本数量和所在节点,大多数公司是基于ELK(日志收集解决方案)搭建一套日志收集和查看平台,就这一套平台不仅耗费资源,而且需要Kibina和Grafana两套平台之间频繁切换,影响工作效率,为了解决此问题Loki问世。 从此,一站式的监控、告警、日志分析平台解决了我们不用频繁切换系统的麻烦。 Loki架构设计思路 基于Loki的完整的日志收集框架需要三部分完成 Promtail:日志收集客户端,以DaemonSet方式运行在各个计算节点上、当然也可以通过sidercar方式运行在Pod内部。Promtail本身可以替换为fluent-bit或者fluentd Loki:日志收集服务端,接收来自Promtail发送的日志 Grafana:日志展示 Loki是一个高可用、可扩展、多租户的日志收集系统,受Prometheus启发而出现,但Loki侧重点在于日志并且通过客户端推送获取日志信息,Prometheus更多在于监控指标并且通过拉取获取指标信息,相比于其它日志系统具有以下优势: 非常节省资源,提供日志压缩功能。 没有把全文添加到索引中,而是把标签加入到索引中,对于用过Prometheus的人来说,使用起来非常顺手。 非常适合存储和搜索Kubernetes Pod的日志,因为它能够把Pod所在的节点信息、容器信息、命名空间、标签添加到索引中。 原生支持Grafana 6.0以上版本。 Loki内部组件介绍 Distributor 它的主要功能是接收来自客户端的日志,Distributor接收到日志之后,首先会校验正确性,校验通过之后会把它划分为多个批次,并发送给Ingester。每个发送过来的流都对应一个Ingester,当日志发送到Distributor之后,Distributor会根据hash和元数据算法计算应该路由到那个Ingester上。 其中Distributor和Ingester之间是通过gRPC通信,都是无状态应用,支持横向扩展。 Ingester 它的主要功能是接收来自Distributor发送的日志并写入到后端存储中,其中后端存储可以是DynamoDB、 S3、 Cassandra、FS等等。其中需要注意,ingester会严格验证接收到的日志行是以时间戳升序接收的(即,每个日志的时间戳都比之前的日志晚一些)。 当ingester收到不遵循此顺序的日志时,日志行将被拒绝,并返回错误(Entry out of order)。 总结起来说,首先distributor会接受来自外部数据流请求发送,每个数据流都有自己的一致性hash,然后distributor通过计算hash,把数据流发送到正确的ingester上面;ingester会创建chunk或者或者追加数据到已存在chunk上面(必须保证租户和标签唯一),最后完成数据存储。 Chunks和index Chunks是Loki长期数据存储,旨在提供查询和写入操作,支持DynamoDB、Bigtable、 Cassandra、S3、FS(单机)。index是根据chunks中元数据生成的索引,支持DynamoDB、Bigtable、 Apache Cassandra、BoltDB(单机)。默认情况下Chunks使用FS本地文件系统存储,文件系统存储存在一定的限制,大约可以存储550W个chunk,超过这个限制可能会有问题。 Index使用BoltDB存储,BoltDB是相当出名的Go实现的KV读写引擎, 用户有etcd等。如果需要支持高可用部署,则需要引入大数据组件 Query 主要负责调度前端的查询请求,首先会 Ingesters内存中查询数据,然后再回退到后端存储中查询数据,支持并行化查询和数据缓存。 Loki配置 Loki的配置比较多,配置在/etc/loki/loki.yaml中,如果需要优化存储或者日志接收出现异常问题时可能需要修改配置。比如Loki在接收客户端发送日志可能会出现发送速率超过限制,这个时候可能需要修改ingestion_rate_mb。具体可以参考: https://github.com/grafana/loki/blob/v1.5.0/docs/configuration/README.md#storage_config Loki使用建议 使用Loki的过程中,可能会疑惑,为了提升查询速度,是不是应该使用尽可能多的标签,因为Loki本身的索引是由标签生成的,使用其它日志系统的情况下,可以通过添加尽可能多的索引解决查询速度慢的问题,这是常见的思维方式。然而Loki数据存储设计思想是使用尽可能少的索引,因为Loki本身会把数据存储为多个数据块,并通过标签中的索引匹配数据块。如果你觉得查询速度慢,可以重新配置分片大小和间隔,也可以通过配置的方式使用尽可能多的查询器并行查询。较小的索引和并行蛮力查询与较大/较快的全文本索引之间的这种权衡使Loki与其他系统相比可以节省成本。操作大索引的成本和复杂性很高,而且索引一旦建立,通常是固定的,如果您要查询或不查询,则全天24小时付费,这种设计的优点意味着您可以决定要拥有查询要求是什么,可以根据需要进行更改,同时数据被大量压缩并存储在低成本对象存储中,以将固定的运营成本降至最低,同时仍然具有令人难以置信的快速查询功能,Loki跟云原生思想也是契合的。 Loki安装 Loki的安装方式大致有四种,TK(官方推荐)、helm、docker、二进制部署,我是通过k8s statefulset方式编排运行的。具体请参考: https://github.com/grafana/loki/blob/v1.5.0/docs/installation/README.md Promtail 看到这个名字就会想到Prometheus,其实它们设计思想也是相通的,它作为一个客户端端代理运行在计算节点上,当然也可以通过边车模式运行在Pod中,主要功能是收集日志、为日志流添加标签、推送日志。 功能配置 clients:用于配置Loki服务端地址 positions:收集日志文件位置,在Kubernetes中服务以Pod形式运行,Pod生命周期有可能随时结束,所以需要记录日志收集位置并挂载到宿主机,通过位置记录方便下次继续收集。 scrape_configs:日志文件收集配置,支持收集syslog、jouanl、docker、Kubernetes、以及日志文件。根据收集需求,自行配置。 详细参考: https://github.com/grafana/loki/blob/v1.5.0/docs/clients/promtail/configuration.md 安装部署 推荐使用DaemonSet方式运行,具体参考官方yaml编排示例: https://github.com/grafana/loki/blob/v1.5.0/docs/clients/promtail/installation.md ,不在赘述。 Grafana配置 Grafana版本应该使用6.0以上版本。 admin账号登录 Grafana实例 左侧菜单栏点击 Configuration > Data Sources 点击 + Add data source按钮 输入 Loki服务地址,如果在本地输入 http://localhost:3100或者 Loki svc地址: https://loki:3100 点击右侧 Explore,会提示 Log labels搜索按钮,点击即可搜索。 日志根据label类型进行查询,可以根据具体关键字进行内容搜索和日志内容统计,参考logQL: https://github.com/grafana/loki/blob/v1.5.0/docs/logql.md 总结 以上就是我对Loki使用过程中的心得总结,Loki用户体验良好,设计优雅,但是配置复杂,本文有很多无法覆盖到知识点,如有问题或者想深入讨论的同学请关注公众号,拉你进群讨论,希望能够帮助到大家,谢谢。 推荐阅读 K8S集群模式下fluent-bit日志收集方案 Kubernetes集群环境下fluentd日志收集方案 轻量级日志收集转发 | fluent-bit 日志收集,我用洛基 原创不易,随手关注或者”三连“,诚挚感谢! 本文分享自微信公众号 - 云原生技术爱好者社区(programmer_java)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

Android OTA相关博文

OTA升级介绍 http://blog.csdn.net/u013947002/article/details/49024637 http://blog.chinaunix.net/uid-29728680-id-5253651.html [FAQ15046]L版本Recovery Mode打开adb功能 http://blog.chinaunix.net/uid-29728680-id-5252653.html [FAQ11059]T卡升级时,在recovery模式下升级完成后将手动重启修改为自动重启 http://blog.chinaunix.net/uid-29728680-id-5254405.html [FAQ12130]如何通过adb command 完成自动SD卡升级? http://blog.chinaunix.net/uid-29728680-id-5171946.html 1、正常开机模式下:手机连接usb成功。 2、输入adb cmd: adb shell " echo\ "--update_package=/sdcard/update.zip\"> /cache/recovery/command" 3、输入:adbreboot recovery MTK recovery相关的FAQ总结 http://blog.csdn.net/ly890700/article/details/56044355?locationNum=9&fps=1

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册