• [技术干货] harmony-cordova如何快速Android APP和Ios APP生成原生态鸿蒙APP
    harmony-cordova摘要cordova是美国Apache基金会下的移动端跨平台开源项目,目前并不支持HarmonyOS next版本,但是在鸿蒙三方库中心的harmony-cordova主要用于鸿蒙版跨平台研发,特别是原Android和Ios的cordova项目,无需投入任何研发即可轻松生成鸿蒙版APP。背景知识Android是谷歌旗下的操作系统,由于制裁原因,华为已不能使用Android操作系统,从HarmonyOS Next版本之后,华为手机也慢慢将不再兼容安卓APP,原安卓APP,如果继续要在华为手机上用,必须适配鸿蒙操作系统,说适配是好听的,真实情况是在HarmonyOS next操作系统上全部重新研发,不是简单的适配工作,原Android的代码已一无是处了,这样势必增加研发成本。对于大厂这些都是小case了,但是对于中小企业在新的操作系统投入研发,投入的时间成本和资金势必难以接受,另外由于HarmonyOS是一个新的生命,社区支持不够成熟,人才短缺很多中小企业就会望而生畏了。为此我推荐cordova混合研发,只有cordova才是真正的混合研发免费且开源,调用原生态API,一次研发满足安卓、Ios和鸿蒙操作系统。对于市场上流行的开源项目,我可以好不夸张的说只有cordova,没有之一了。国内也有也有一个开发框架,托管了APP的打包工作,虽然也是打着免费的口号,确强制集成了不该集成的SDK,以至于在上架的时候无法满足应用市场的要求和国家法律的要求,因为要说明这些SDK的作用是什么,但是很多开发者就不知道集成有这个SDK,因此隐私政策写不好而不能上架,因为盈利性公司不是基金会,不会提供免费的午餐。我们能够理解,这里我就不明说了。因此对于用心运营的APP的企业,我推荐混合式研发,也不要装X全部使用原生研发,后面维护和升级拿石头砸自己脚。鸿蒙原生开发原生鸿蒙研发支持ArkTS和C/C++语言。并不支持Java,Java的同学可能比较难过,其实也不用担心,看我接下来的分析,ArkTS是华为在TS语言上的进一步优化的后开发语言,不同于TS语言,学起来并不难,只需要几分钟看看就可以上手了,当然这是对于之前熟练的掌握了Java、Js或C/C++的基础上,说实在的如果有了前面的基础TS无需单独学习,直接就可以上手,如果使用native c/c++就不同了,C/C++语言会使很多开发者心里发怵,以致于不敢轻易创建native C++项目,所以有一个部分开发者只能使用ArkTS开发原生鸿蒙APP了,但这样就失去了优势,只能开发更上层应用性的内容,涉及框架或者底层的开发就无法胜任了.harmony-cordova就是使用C/C++研发的,cordova的插件也是C/C++研发的,只有涉及到鸿蒙UI层的必须使用ArkTS了。所以是两者结合开发的,harmony-cordova所涉及的技术方案,我这里不过多的说明,大家只要会用就可以了。接下来说一下harmonyOS吧,HarmonyOS 内核并不是Linux内核,虽然支持C/C++研发,但是并不是现存所有的Linux的开源项目都可以集成到HarmonyOS上的,在加上DevEco采用的是CMake编译,如果原Linux的开源项目是Makefile编译的,要移植到HarmonyOS上,也并非易事,需要Linux C/C++开发的熟手才可以做到,并非入门级别的就可以胜任。如果原Linux下的开源项目,源文件数量少,可以直接拷贝源码集成,如果原Linux下的开源项目文件众多,编译复杂,就需要使用交叉编译移植到HarmonyOS上,为什么要移植Linux开源项目呢?因为大多项目都会依赖Linux的动态库so,所以要进行移植,如果不移植很多C/C++程序就很难开发。但是并不是所有的Linux的开源项目都可以移植的,因为HarmonyOS并不是Linux内核,使用的编译器也不是gcc,所以Linxu下面的so之间互相依赖,因此在Harmony OS上有些so无法移植。另外Linux下面的C/C++的程序和HarmonyOS也有差异,HarmonyOS也预制集成了类似Linux下的so,虽然有预制so,但是并不是所有的函数都可以使用,因为接口并不全,特别是一些涉及到内核的调用和Linux并不一样。综上所述,在HarmonOS平台上,使用native c/c++研发框架类的har,则需要更多的C/C++开发经验,这方面的人才会更少。因此您在网上看到最多的都是ArkTS开发的。harmony-cordova为什么要研发鸿蒙版cordova,公司研发harmony cordova是因为公司内的APP原使用cordova框架研发,很遗憾的是Apache基金会不支持,所以我们公司就自己研发了,研发好以后首先使用在我们公司自己的产品上,1.0版本首先满足了我们公司自己需要插件,后面会慢慢升级以兼容大部分cordova插件。自定义插件,很多Android项目或者Ios项目,集成了自定义插件,例如每个手机厂商的推送功能,都是自定义插件研发的,这里插句话,不要集成第三方的统一推送的SDK,会让你的APP由于隐私政策审核起来出现很多的问题,所以建议集成所有手机厂商自己的推送SDK,因为手机厂商并不多,集成也没有太多的工作。还有OSS对象存储等都需要自定义插件,harmony-cordova也集成了一些常用的自定义插件。如果您的项目中有自定义插件,需要使用harmony-cordova的,就需要开发者支持了。Android移植鸿蒙步骤1,打开DevEco创建项目,选择Empty Ability进入下一步,填写必要信息,这里要注意,bundle name 先填写com.example.myapplication,也就是保持默认不变,因为在没有cordova.crt证书的情况下,cordova鸿蒙版要求bundle name必须为com.example.myapplication,主要用于研发测试,如果开发测试完成要修改bundle name上架鸿蒙应用市场,请联系开发者申请cordova.ert证书,或者事先联系开发者提供技术服务。2,项目创建成功后,复制原有Android studio的工程assests/www目录下面的所有文件到鸿蒙工程entry/src/main/resources/目录下,注意直接复制原andriod工程www目录下的文件,不包含www。3,复制原android工程res/xml目录下的config.xml文件到鸿蒙工程entry/src/main/resources/目录下。4,打开DevEco studio的Terminal终端,进入工程目录,执行 ohpm install harmony-cordova 安装本插件。5,打开鸿蒙工程文件entry/src/main/etx/pages/Index.ets文件,修改代码如下:import { MainPage, pageBackPress, pageHideEvent, pageShowEvent } from 'harmony-cordova/Index';  @Entry  @Component  struct Index {    onPageShow(){      pageShowEvent(); //页面显示通知cordova    }      onBackPress() {      pageBackPress(); //拦截返回键由cordova处理      return true;    }    onPageHide() {      pageHideEvent(); //页面隐藏通知cordova    }      build() {      RelativeContainer() {        MainPage(); //webview首页index.html      }      .height('100%')      .width('100%')    }  }6,打开鸿蒙工程文件/entry/src/main/ets/entryAbility/EntryAbility.ets文件,修改onCreate函数如下import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';      import { hilog } from '@kit.PerformanceAnalysisKit';      import { window } from '@kit.ArkUI';      import { webview } from '@kit.ArkWeb';      import { setSchemeHandler } from 'harmony-cordova/Index';...onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {webview.WebviewController.initializeWebEngine();//webview引擎初始化setSchemeHandler();//设置webview schemehilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');}7,鸿蒙混合研发,也许您会增加其他page页面,不一定应用的首页为cordova webview(index.html)的首页,例如应用增加了鸿蒙的原生的启动页面,包含首页弹窗,同意隐私政策后,然后再从启动页面进入cordova的页面,这样避免在用户没有同意隐私政策的情况下,初始化cordova sdk,因为初始化cordova sdk,系统读取了设备的网络状态,因为国内相关法律规定,在用户没有同意隐私政策的情况下,不允许读取设备的网络标识。8,做以上代码修改后,鸿蒙的移植已经完毕,可以使用模拟器或者真机进行编译和测试了。Ios移植鸿蒙步骤如果您的项目有android和Ios的工程,请参考android项目移植项目的鸿蒙下,如果您的项目没有andriod工程,只有Ios工程,请使用如下方法移植,移植时大部分内容和安卓一样,只是复制的文件的路径不一致,以下只介绍不同部分,相同部分请参考android移植步骤。1,复制Xcode的Ios工程目录下的Staging/www目录下的所有文件到鸿蒙工程entry/src/main/resources/目录下。 2,Xcode工程的config.xml文件在Staging目录下,Xcode工程的该文件不能直接被鸿蒙版cordova使用,需要进行转换,该文件主要记录的是插件的名称和初始化的类,因为鸿蒙版是根据android的config.xml进行插件初始化的,因此需要将Xcode工程config.xml转为安卓的config.xml,请将Xcode工程使用node加入安卓平台,系统会自动生成android版的config.xml。然后将文件复制到鸿蒙版工程的entry/src/main/resources/下。附加说明:本人认为使用cordoca跨平台研发,一般至少都会包含android和ios两大平台,很少只有ios平台,没有android平台的,所以大部分移植鸿蒙参考android移植步骤,后续升级SDK会兼容Ios工程的config.xml,无需转换就可以使用。新项目,一次开发适用于andriod、Ios和Harmony三大平台由于cordova官方当前并不支持HarmonyOS平台,使用node无法直接将HarmonyOS加入到cordova,也无法直接安装插件到HarmonyOS,因此对于新项目要一次开发满足三大平台的话,建议先通过node加入Android和Ios平台和安装插件,后续研发可以使用Android studio研发和调试,待研发成功后,然后再在Xcode和DevEco做跨平台适配。Xcode适配请参考cordova的官方文档,HarmonyOS适配请参考以上Android的移植步骤。特别说明当前版本不支持使用者自定义插件研发,如果该版本没有包含您要使用的插件,或者您的项目中有Android或Ios的自定义插件,需要移植到HarmonyOS平台,请您和本开发者联系,获取技术支持。使用鸿蒙版cordova sdk在开发测试阶段务必将bundle name修改为com.example.myapplication,如果将bunlde name改为正式的Id,鸿蒙版cordova sdk会读取entry/src/main/resources/目录的cordova.crt证书文件,用于验签,如果该文件不存在,启动应用后,应用会闪退。如果应用的bundle name为com.example.myapplication,鸿蒙版 cordova sdk会跳过验签,不检测cordova.crt文件。但是上架鸿蒙应用市场,必须将bundle name改为正式的id,所以请联系开发者申请cordova.ert证书,另外由于操作系统之间的差异,虽然保持了cordova的插件接口不变,但是返回值会有所调整,后续文档会逐步完善,在使用本插件跨平台研发时请联系开发者提供技术服务。                                                               
  • [问题求助] 求助iOS OBSClient调用PutObject,正在上传的过程中如何取消?
    如题,PutObject开始后如何停止。尝试调用了request的cancel方法,看起来没起作用,仍然能接到uploadProgressBlock回调。又看了看任务返回值OBSBFTask,也没有找到有什么能取消任务的方式,有大佬遇到过类似的问题么,求解决办法!~
  • [问题求助] OBS的iOS版SDK只能通过本地引入的形式吗
    https://bbs.huaweicloud.com/forum/thread-0281121402742044007-1-1.html  之前有人发过这个帖子,但是我没有找到具体的文档,我下载了开发指南也没有看到如何通过cocoapod来引入SDK
  • [热门活动] 没有 Mac,不想装黑苹果(包括在虚拟机内),有可能开发流畅(用户体验好)的 iOS 应用吗?
    首先想到的是多端框架,写了两个 demo ,测试了下 Flutter 和 React Native 在 UI 复杂的情况下都很卡,毫无用户体验可言,貌似只剩下 Xamarin 一个选择了,但好像用这个开发 Widget 之类的功能还是需要在 macOS 用 Swift + Xcode 原生开发,但我短期内没购买 Mac 的计划,而且实在不喜欢 Swift 语言的设计(不是重点,不要讨论这个防止楼歪)。(调试设备不缺,我有 iPad Pro 2021 / iPad mini 6 / iPhone Xr 真机,不知道能不能完全代替模拟器)(编译完上传包应该可以参考 Bitwarden 的 iOS 客户端直接用 GitHub 的 CI 上传到 App Store )
  • [技术干货] 报错如下内容,怎么解决?
    报错如下内容,怎么解决?报错以下内容:[2023-02-27 09:14:31 CST] <main> INFO: Logging level set to informational[2023-02-27 09:14:31 CST] <main> INFO: Logging configured successfully.[2023-02-27 09:14:32 CST] <main> INFO: kxapp [1.0.0][2023-02-27 09:14:32 CST] <main> INFO: OS identifier: Windows 10 10.0 (amd64); jvm=GraalVM 21.0.0 Java 11; jre=null[2023-02-27 09:14:32 CST] <main> INFO: Upload mode selected.[2023-02-27 09:14:32 CST] <main> INFO: Examining the package at: C:\Users\鍟嗙鐟瀄AppData\Local\Temp\au_8564049820444212912\1568223082.itmsp[2023-02-27 09:14:32 CST] <main> INFO: Ensuring that package has well formed metadata file...[2023-02-27 09:14:32 CST] <main> INFO: Gathering the list of valid files from the package ...[2023-02-27 09:14:32 CST] <main> INFO: Finished gathering the list of valid files from the package.[2023-02-27 09:14:32 CST] <main> INFO: Verify progress: Analyzing metadata (Step 0/?)[2023-02-27 09:14:32 CST] <main> INFO: Performing authentication of package 1568223082.itmsp ...[2023-02-27 09:14:32 CST] <main> INFO: Verify progress: Analyzing metadata (Step 1/2)[2023-02-27 09:14:32 CST] <main> INFO: id = 20230227091432-317[2023-02-27 09:14:33 CST] <main> INFO: id = 20230227091433-619[2023-02-27 09:14:34 CST] <main> INFO: The list of files requested for upload by Apple is: [metadata.xml, 1568223082.ipa]. These are the files that will be uploaded.[2023-02-27 09:14:34 CST] <main> INFO: Starting media analysis of assets[2023-02-27 09:14:34 CST] <main> INFO: Verify progress: Adding asset to analyze: 1568223082.ipa (Step 2/3)[2023-02-27 09:14:34 CST] <main> INFO: Asset media analysis has completed[2023-02-27 09:14:34 CST] <main> INFO: id = 20230227091434-739[2023-02-27 09:14:35 CST] <main> INFO: Signiant TransferEngine build 10.6.0.59156[2023-02-27 09:14:35 CST] <main> INFO: Using Signiant UDP transport[2023-02-27 09:14:35 CST] <main> INFO: Transfer engine: 10.6.0.59156[2023-02-27 09:14:35 CST] <Transport Protocol> INFO: The Signiant transfer engine's status is DISCONNECTED[2023-02-27 09:14:35 CST] <Transport Protocol> INFO: The Signiant transfer engine's status is CONNECTING[2023-02-27 09:14:35 CST] <Transport Protocol Seeker> INFO: Processing URL list: mxwan://sgr802.apple.com:44001/[2023-02-27 09:14:35 CST] <Connector: UDP sgr802.apple.com/17.133.233.102:44001> INFO: Trying UDP sgr802.apple.com/17.133.233.102:44001...[2023-02-27 09:14:36 CST] <Transport Protocol Seeker> INFO: Connected to UDP sgr802.apple.com/17.133.233.102:44001[2023-02-27 09:14:37 CST] <Transport Protocol> INFO: Authenticating[2023-02-27 09:14:54 CST] <Transport Protocol> INFO: The Signiant transfer engine's status is AWAITING_DATA_STREAMS[2023-02-27 09:14:54 CST] <Transport Protocol> INFO: Setting up data streams[2023-02-27 09:14:58 CST] <Data Stream 0 (0)> INFO: The Signiant transfer engine's status is CONNECTED[2023-02-27 09:14:58 CST] <Data Stream 0 (0)> INFO: Connected using WAN accelerator[2023-02-27 09:14:58 CST] <File Progress Event Thread> INFO: File: a4810215-9452-4bd5-bca5-39ae297f5cc02644872023157440642.txt 11/11, 100% completed[2023-02-27 09:14:59 CST] <Transport Protocol> INFO: The Signiant transfer engine's status is DISCONNECTED[2023-02-27 09:14:59 CST] <Transport Protocol> INFO: The Signiant transfer engine's status is DISCONNECTED[2023-02-27 09:14:59 CST] <main> INFO: Verify progress: Validating assets (Step 3/3)[2023-02-27 09:14:59 CST] <main> INFO: Verify progress: Validating assets (Step 4/3)[2023-02-27 09:14:59 CST] <main> INFO: id = 20230227091459-285[2023-02-27 09:15:01 CST] <main> ERROR: An error occurred saving your changes to the Apple database. This problem may be a transient issue on the Apple side. If the problem persists for more than an hour, please contact your iTunes representative. (1015)[2023-02-27 09:15:01 CST] <main> INFO: Done performing authentication.[2023-02-27 09:15:01 CST] <main> INFO: Verify progress: Operation failed (Step 3/3)Package Summary:1 package(s) were not uploaded because they had problems:C:\Users\鍟嗙鐟瀄AppData\Local\Temp\au_8564049820444212912\1568223082.itmsp - Error Messages:An error occurred saving your changes to the Apple database. This problem may be a transient issue on the Apple side. If the problem persists for more than an hour, please contact your iTunes representative. (1015)使用工具:http://gui.applicationloader.net:8480/index.html#/home。 
  • [算子开发] 【AICPU自定义算子】【算子代码实现】输入张量X按照输入的axis轴展开的代码怎么写?
    【功能模块】算子分析:找出张量的唯一元素。当提供可选属性“轴”时,将返回沿“轴”切片的唯一子张量。否则,输入张量被展平,并返回展平张量的唯一值。第一个输出张量“Y”包含输入的所有唯一值或子张量。第二个可选输出张量'idx'包含'Y'元素在'X'中第一次出现的索引。第三个可选输出张量'inverse_idx'包含'X'的元素,它在'Y'中的相应索引。"。第四个可选输出张量 'counts' 包含输入中 'Y' 的每个元素的计数。算子属性:Attributesout_idx: int(default is int64)(Optional) 输出idx的type。默认值int64。sorted : int (default is True)(Optional) 在作为输出返回之前是否按升序对唯一元素进行排序。必须是 true(默认值) 或 false之一。return_inverse(default is false)(Optional) 是否输出inverse_idx,默认false。InputsX (non-differentiable) : T 要处理的 ND 输入张量。axis : A 1D tensor (default is -1)(Optional) 要应用唯一的维度。如果未指定,则返回扁平化输入的唯一元素。负值意味着从后面计算尺寸。可接受的范围是 [-r, r-1] 其中 r = rank(input)。默认值-1。Outputs (1 - 4)Y (non-differentiable) : T与“X”类型相同的张量,包含沿“X”中提供的“轴”切片的所有唯一值或子张量,按照它们在输入“X”中出现的相同顺序排序或维护idx(non-differentiable) : tensor(int64)一个一维 INT64 张量,包含在 'X' 中第一次出现的 'Y' 元素的索引。当提供“axis”时,它包含“axis”上输入“X”中子张量的索引。当未提供 'axis' 时,它包含扁平输入张量中值的索引。inverse_idx (optional, non-differentiable) : tensor(int64)一维 INT64 张量,对于 'X' 的元素,包含其在 'Y' 中的相应索引。当提供“axis”时,它包含“axis”上输出“Y”中子张量的索引。如果未提供“axis”,则它包含输出“Y”中值的索引。counts (non-differentiable) : tensor(int64)一维 INT64 张量,包含输入“X”中“Y”的每个元素的计数Type ConstraintsT : tensor(uint8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(int8), tensor(int16), tensor(int32), tensor(int64), tensor(float16), tensor(float), tensor(double), tensor(string), tensor(bool), tensor(complex64), tensor(complex128)算子案例:Example 1: input_X = [2, 1, 1, 3, 4, 3] attribute_sorted = false attribute_axis = Noneoutput_Y = [2, 1, 3, 4] output_indices = [0, 1, 3, 4] output_inverse_indices = [0, 1, 1, 2, 3, 2] output_counts = [1, 2, 2, 1]Example 2: input_X = [[1, 3], [2, 3]] attribute_sorted = true attribute_axis = Noneoutput_Y = [1, 2, 3] output_indices = [0, 2, 1] output_inverse_indices = [0, 2, 1, 2] output_counts = [1, 1, 2]Example 3: input_X = [[1, 0, 0], [1, 0, 0], [2, 3, 4]] attribute_sorted = true attribute_axis = 0output_Y = [[1, 0, 0], [2, 3, 4]] output_indices = [0, 2] output_inverse_indices = [0, 0, 1] output_counts = [2, 1]【操作步骤&问题现象】自己写的思路,不清楚输入张量X按照输入的axis轴展开的代码怎么写?希望能提供下用到的API和参考代码,谢谢1.新建变量,输入的X、axis、idx(type)、sorted、return_inverse,输出的Y,idxout,inverse_idxout,counts_out。2.注意axis,for循环X,for循环y,判断y中有无X的元素,没有就加入,有就跳过;判断sorted,然后对y进行排序,如果有需要的话3.循环y,设置flag=false,index=0,count=0循环x,检查第一次出现y,flag=0了,记录index=i,然后继续循环,出现一次,就count++,把index和count保存到idxout和counts_out中。4.判读return_inverse循环,循环x,设置flag=false,index=0,循环y,检查第一次出现y,flag=0了,记录index=i,保存到inverse_idxout。【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [问题求助] 小白求解生成iOS APP之迷茫
    不知道指的是哪个网站,我也没有苹果手机。
  • [技术干货] 10天学会flutter DAY10 flutter 玩转 动画与打包[转载]
    动画​ Flutter中的动画系统基于Animation对象的,和之前的手势不同,它不是一个Widget,这是因为Animation对象本身和UI渲染没有任何关系。Animation是一个抽象类,就相当于一个定时器,它用于保存动画的插值和状态,并执行数值的变化。widget可以在build函数中读取Animation对象的当前值, 并且可以监听动画的状态改变。AnimationController​ AnimationController用于控制动画,它包含动画的启动forward()、停止stop() 、反向播放 reverse()等方法。AnimationController会在动画的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内线性的生成从0.0到1.0(默认区间)的数字。AnimationController controller = AnimationController(  duration: const Duration(milliseconds: 2000), //动画时间 lowerBound: 10.0,    //生成数字的区间  upperBound: 20.0,    //10.0 - 20.0 vsync: this  //TickerProvider 动画驱动器提供者);Ticker​ Ticker的作用是添加屏幕刷新回调,每次屏幕刷新都会调用TickerCallback。使用Ticker来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源。因为Flutter中屏幕刷新时会通知Ticker,锁屏后屏幕会停止刷新,所以Ticker就不会再触发。最简单的做法为将SingleTickerProviderStateMixin添加到State的定义中。import 'package:flutter/material.dart';void main() => runApp(AnimationApp());class AnimationApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: "animation",      home: Scaffold(        appBar: AppBar(          title: Text('animation'),        ),        body: AnimWidget(),      ),    );  }}// 动画是有状态的class AnimWidget extends StatefulWidget {  @override  State<StatefulWidget> createState() {    return _AnimWidgetState();  }}class _AnimWidgetState extends State<AnimWidget>    with SingleTickerProviderStateMixin {  AnimationController controller;  bool forward = true;  @override  void initState() {    super.initState();    controller = AnimationController(      // 动画的时长      duration: Duration(milliseconds: 2000),      lowerBound: 10.0,      upperBound: 100.0,      // 提供 vsync 最简单的方式,就是直接混入 SingleTickerProviderStateMixin      // 如果有多个AnimationController,则使用TickerProviderStateMixin。      vsync: this,    );       //状态修改监听    controller      ..addStatusListener((AnimationStatus status) {        debugPrint("状态:$status");      })      ..addListener(() {        setState(() => {});      });    debugPrint("controller.value:${controller.value}");  }  @override  Widget build(BuildContext context) {    return Column(      children: <Widget>[        Container(          width: controller.value,          height: controller.value,          color: Colors.blue,        ),        RaisedButton(          child: Text("播放"),          onPressed: () {            if (forward) {              controller.forward();            } else {              controller.reverse();            }            forward = !forward;          },        ),        RaisedButton(          child: Text("停止"),          onPressed: () {            controller.stop();          },        )      ],    );  }}动画状态监听:在forword结束之后状态为completed。在reverse结束之后状态为dismissedTween​ 默认情况下,AnimationController对象值为:double类型,范围是0.0到1.0 。如果我们需要不同的范围或不同的数据类型,则可以使用Tween来配置动画以生成不同的范围或数据类型的值。要使用Tween对象,需要调用其animate()方法,然后传入一个控制器对象,同时动画过程中产生的数值由Tween的lerp方法决定。import 'package:flutter/material.dart';void main() => runApp(AnimationApp());class AnimationApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: "animation",      home: Scaffold(        appBar: AppBar(          title: Text('animation'),        ),        body: AnimWidget(),      ),    );  }}// 动画是有状态的class AnimWidget extends StatefulWidget {  @override  State<StatefulWidget> createState() {    return _AnimWidgetState();  }}class _AnimWidgetState extends State<AnimWidget>    with SingleTickerProviderStateMixin {  AnimationController controller;  bool forward = true;  Tween<Color> tween;  @override  void initState() {    super.initState();    controller = AnimationController(      // 动画的时长      duration: Duration(milliseconds: 2000),      // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin      vsync: this,    );    //使用Color    tween = ColorTween(begin: Colors.blue, end: Colors.yellow);    //添加动画值修改监听    tween.animate(controller)..addListener(() => setState(() {}));  }  @override  Widget build(BuildContext context) {    return Column(      children: <Widget>[        Container(          width: 100,          height: 100,          //获取动画当前值          color: tween.evaluate(controller),        ),        RaisedButton(          child: Text("播放"),          onPressed: () {            if (forward) {              controller.forward();            } else {              controller.reverse();            }            forward = !forward;          },        ),        RaisedButton(          child: Text("停止"),          onPressed: () {            controller.stop();          },        )      ],    );  }}Curve​ 动画过程默认是线性的(匀速),如果需要非线形的,比如:加速的或者先加速后减速等。Flutter中可以通过Curve(曲线)来描述动画过程。import 'package:flutter/material.dart';void main() => runApp(AnimationApp());class AnimationApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: "animation",      home: Scaffold(        appBar: AppBar(          title: Text('animation'),        ),        body: AnimWidget(),      ),    );  }}// 动画是有状态的class AnimWidget extends StatefulWidget {  @override  State<StatefulWidget> createState() {    return _AnimWidgetState();  }}class _AnimWidgetState extends State<AnimWidget>    with SingleTickerProviderStateMixin {  AnimationController controller;  Animation<double> animation;  bool forward = true;  @override  void initState() {    super.initState();    controller = AnimationController(      // 动画的时长      duration: Duration(milliseconds: 2000),      // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin      vsync: this,    );    //弹性    animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);    //使用Color    animation = Tween(begin: 10.0, end: 100.0).animate(animation)      ..addListener(() {        setState(() => {});      });  }  @override  Widget build(BuildContext context) {    return Column(      children: <Widget>[        Container(          //不需要转换          width: animation.value,          height: animation.value,          //获取动画当前值          color: Colors.blue,        ),        RaisedButton(          child: Text("播放"),          onPressed: () {            if (forward) {              controller.forward();            } else {              controller.reverse();            }            forward = !forward;          },        ),        RaisedButton(          child: Text("停止"),          onPressed: () {            controller.stop();          },        )      ],    );  }}AnimatedWidget​ 通过上面的学习我们能够感受到Animation对象本身和UI渲染没有任何关系。而通过addListener()和setState() 来更新UI这一步其实是通用的,如果每个动画中都加这么一句是比较繁琐的。AnimatedWidget类封装了调用setState()的细节,简单来说就是自动调用setState()。​ Flutter中已经封装了很多动画,比如对widget进行缩放,可以直接使用ScaleTransitionimport 'package:flutter/material.dart';void main() => runApp(AnimationApp());class AnimationApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: "animation",      home: Scaffold(        appBar: AppBar(          title: Text('animation'),        ),        body: AnimWidget(),      ),    );  }}// 动画是有状态的class AnimWidget extends StatefulWidget {  @override  State<StatefulWidget> createState() {    return _AnimWidgetState();  }}class _AnimWidgetState extends State<AnimWidget>    with SingleTickerProviderStateMixin {  AnimationController controller;  Animation<double> animation;  bool forward = true;  @override  void initState() {    super.initState();    controller = AnimationController(      // 动画的时长      duration: Duration(milliseconds: 2000),      // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin      vsync: this,    );    //弹性    animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);    //使用Color    animation = Tween(begin: 10.0, end: 100.0).animate(animation);  }  @override  Widget build(BuildContext context) {    return Column(      children: <Widget>[        ScaleTransition(          child:  Container(            width: 100,            height: 100,            color: Colors.blue,          ),          scale: controller,        ),        RaisedButton(          child: Text("播放"),          onPressed: () {            if (forward) {              controller.forward();            } else {              controller.reverse();            }            forward = !forward;          },        ),        RaisedButton(          child: Text("停止"),          onPressed: () {            controller.stop();          },        )      ],    );  }}Hero动画​ Hero动画就是在路由切换时,有一个共享的Widget可以在新旧路由间切换,由于共享的Widget在新旧路由页面上的位置、外观可能有所差异,所以在路由切换时会逐渐过渡,这样就会产生一个Hero动画。import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return new MaterialApp(      title: 'Flutter Demo',      home: Scaffold(          appBar: AppBar(            title: Text("主页"),          ),          body: Route1()),    );  }}// 路由Aclass Route1 extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Container(      alignment: Alignment.topCenter,      child: InkWell(        child: Hero(          tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同          child: CircleAvatar(            backgroundImage: AssetImage(              "assets/banner.jpeg",            ),          ),        ),        onTap: () {          Navigator.push(context, MaterialPageRoute(builder: (_) {            return Route2();          }));        },      ),    );  }}class Route2 extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Center(      child: Hero(          tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同          child: Image.asset("assets/banner.jpeg")),    );  }}组合动画有些时候我们可能会需要执行一个动画序列执行一些复杂的动画。import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return new MaterialApp(      title: 'Flutter Demo',      home: Route(),    );  }}class Route extends StatefulWidget {  @override  State<StatefulWidget> createState() {    return RouteState();  }}class RouteState extends State<Route> with SingleTickerProviderStateMixin {  Animation<Color> color;  Animation<double> width;  AnimationController controller;  @override  void initState() {    super.initState();    controller = AnimationController(      // 动画的时长      duration: Duration(milliseconds: 2000),      // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin      vsync: this,    );    //高度动画    width = Tween<double>(      begin: 100.0,      end: 300.0,    ).animate(      CurvedAnimation(        parent: controller,        curve: Interval(          //间隔,前60%的动画时间 1200ms执行高度变化          0.0, 0.6,        ),      ),    );    color = ColorTween(      begin: Colors.green,      end: Colors.red,    ).animate(      CurvedAnimation(        parent: controller,        curve: Interval(          0.6, 1.0, //高度变化完成后 800ms 执行颜色编码        ),      ),    );  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("主页"),      ),      body: InkWell(        ///1、不用显式的去添加帧监听器,再调用setState()        ///2、缩小动画构建的范围,如果没有builder,setState()将会在父widget上下文调用,导致父widget的build方法重新调用,现在只会导致动画widget的build重新调用        child: AnimatedBuilder(            animation: controller,            builder: (context, child) {              return Container(                color: color.value,                width: width.value,                height: 100.0,              );            }),        onTap: () {          controller.forward().whenCompleteOrCancel(() => controller.reverse());        },      ),    );  }}打包​ Flutter在打Release包时候回使用AOT,因此在对一个Flutter测试时候务必使用Release来进行测试。打包命令:flutter build apk 。当然我们需要打包时,还需要配置一些比如签名的内容。配置这些内容和普通Android工程没有区别,都是在build.gradle中进行,只是Flutter工程AS没有提供GUI。​ 在Flutter工程的android/app下面的build.gradle可以修改包名、版本等信息,这就不用多说了。获得签名文件之后,将它复制到flutter的android目录:​ 然后在app的build.gradle中配置:signingConfigs {        release {            keyAlias 'enjoy'            keyPassword '123456'            // 因为是放到父级的根目录,使用rootProject            // 如果放在这个build.gradle的同级,直接使用file            storeFile rootProject.file('enjoy.jks')            storePassword '123456'        }    }    buildTypes {        release {            // TODO: Add your own signing config for the release build.            // Signing with the debug keys for now, so `flutter run --release` works.            signingConfig signingConfigs.release        }    }饼图https://github.com/google/chartsStack布局中的fit属性与Image的fit类似,表示内容的扩充情况。默认为StackFit.loose表示Stack与内容一样大。如果设置为StackFit.passthrough则表示Stack父Widget的约束会传给Stack内部非Positioned的子Widget。效果如代码中的StackFit.dart原文链接:https://blog.csdn.net/u010755471/article/details/124691809
  • [版本公告] 华为云会议V8.5.5版本新特性:Mac客户端隐藏非视频与会者、iOS客户端麦克风浮窗等功能来啦~
    华为云会议V8.5.5版本新特性本月优化内容:PC端:1. Windows客户端1080P功能优化2. 网络研讨会Windows客户端虚拟背景性能优化3. Windows客户端麦克风异常提示优化4. Mac客户端支持开启原始声音5. Mac客户端支持隐藏非视频与会者6. 支持用户加入体验改进计划7. 华为云会议管理平台预约会议可添加直播地址移动端:1. iOS客户端支持会中麦克风浮窗2. iOS客户端支持画廊3*3画面3. 支持用户加入体验改进计划一、Windows客户端1080P功能优化适用场景:当企业开通了1080P功能,在硬件设备满足的情况下,Windows客户端企业用户入会后将收到设置提醒,可前往开启高清画质,让视频会议更清晰生动。二、网络研讨会Windows客户端虚拟背景性能优化适用场景:网络研讨会场景,Windows客户端开启虚拟背景时将减少CPU占用,提升会议体验。三、Windows客户端麦克风异常提示优化适用场景:Windows客户端与会者的麦克风缺失或异常时,进入会议、被主持人解除静音将收到麦克风异常提示,可前往检测或调试麦克风,更快恢复会议音视频状态。四、Mac客户端支持开启原始声音适用场景:Mac客户端支持开启原始声音,启用原始声音后,啸叫抑制和音频降噪等声音增加能力将被自动关闭。五、Mac客户端支持隐藏非视频与会者适用场景:当参会人数较多,想要保持会议中展示画面的美观和一致性时,可选择隐藏未开启视频画面与会者。六、支持用户加入体验改进计划适用场景:手机号码注册、微信注册、创建企业时,支持勾选《用户体验改进计划》。适用场景(桌面端):已注册用户登录客户端后,可选择加入或退出《用户体验改进计划》。适用场景(移动端):已注册用户登录客户端后,可选择加入或退出《用户体验改进计划》。七、华为云会议管理平台预约会议可添加直播地址适用场景:在华为云会议管理平台预约会议时,可添加并设置临时直播间,开启会议直播更便捷。八、iOS客户端支持会中麦克风浮窗适用场景:iOS客户端可开启会中麦克风浮窗,当与会者发言时,麦克风浮窗将显示当前会中音频状态,调整麦克风更方便。九、iOS客户端支持画廊3*3画面适用场景:iOS客户端多人入会时,支持画廊3*3画面,会中画面显示更加美观。(iPhone 7及以上设备支持)
  • [问题求助] iOS集成OBS调用报错
    按照文档一步一步集成OBS SDK,运行时报错:terminating with uncaught exception of type NSException代码如下: OBSStaticCredentialProvider *provider = [[OBSSTSCredentialProvider alloc] initWithAccessKey:@"xxxx" secretKey:@"xxx" stsToken:@"xxxx"]; provider.securityToken = @"xxxxx"; OBSServiceConfiguration *conf = [[OBSServiceConfiguration alloc] initWithURL:@"https://obs.cn-east-3.myhuaweicloud.com" credentialProvider:provider]; OBSClient *client = [[OBSClient alloc] initWithConfiguration:conf]; OBSPutObjectWithDataRequest *request = [[OBSPutObjectWithDataRequest alloc] initWithBucketName:@"isong" objectKey:@"test/ios.txt" uploadData:[@"hello" dataUsingEncoding:NSUTF8StringEncoding]]; request.uploadProgressBlock = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) { NSLog(@"%0.1f%%",(float)floor(totalBytesSent*10000/totalBytesExpectedToSend)/100); }; [client putObject:request completionHandler:^(OBSPutObjectResponse *response, NSError *error) { }];Swift 集成也是报同样的错误
  • [问题求助] IOS上传图片成功后没有返回上传成功图片的URL地址
  • [技术干货] 使用Cloud DB构建APP 快速入门 - iOS篇
    概述此示例应用演示了如何快速的使用Cloud DB构建简单的图书管理服务。通过快速入门和示例应用,您将会了解到如下信息:如何使用Cloud DB进行应用开发。应用数据如何写入到Cloud DB。如何实现数据的查询。实时侦听数据的更改。体验端云数据同步等功能。开发准备使用Cloud DB构建应用服务,需要完成以下准备工作:您已经在开发者联盟官网注册帐号并通过实名认证,详细请参见帐号注册认证。您已经在AppGallery Connect控制台上创建项目和应用,详细请参见创建项目。示例应用使用了认证用户的相关权限,需要开通AppGallery Connect认证服务中“匿名帐号”服务,详细请参见认证服务。您已经获取到示例代码,请从示例代码获取。您已在本地安装Xcode。启用服务使用Cloud DB服务前,您需要先启用服务。登录AppGallery Connect网站,选择“我的项目”。在项目列表页面中选择项目,单击项目下需要启用云数据库服务的应用。在导航树上选择“构建 > 云数据库”。单击“立即开通”,开通云数据库服务。            (可选)如您还未选择数据处理位置,需要您先设置数据处理位置,具体操作请参见设置数据处理位置。服务初始化成功后,即启用云数据库服务成功。新增和导出对象类型您需要基于AppGallery Connect控制台创建对象类型,请您遵循操作步骤创建示例中涉及的对象类型,并导出Objective-C格式的对象类型文件。不允许修改导出的Objective-C格式文件,否则会导致数据同步功能异常。登录AppGallery Connect网站,选择“我的项目”。在项目列表页面中选择项目,单击项目下需要创建对象类型的应用。在导航树上选择“构建 > 云数据库”。单击“新增”,进入创建对象类型页面。            输入对象类型名为“BookInfo”后,单击“下一步”。单击 +新增字段,新增如下字段后,单击“下一步”。表1 字段定义表字段名称类型主键非空加密默认值idInteger√√––bookNameString––––authorString––––priceDouble––––publisherString––––publishTimeDate––––shadowFlagBoolean–––true单击 +新增索引 ,设置索引名为“bookName”,索引字段为“bookName”后,单击“下一步”。按照如下要求设置各角色权限后,单击“下一步”。单击“确定”。创建完成后返回对象类型列表中,可以查看已创建的对象类型。单击“导出”。                                                        选择导出文件格式,选择“Objective-C格式”。单击“导出”。文件将会导出至本地,其内包含该版本中所有的对象类型。导出的Objective-C格式文件在后续步骤用于添加至本地开发环境。新增存储区可基于AppGallery Connect控制台在云侧创建数据存储区,请您遵循操作步骤创建一个存储区名称为“CloudDBDemo”的存储区。登录AppGallery Connect网站,选择“我的项目”。在项目列表页面中选择项目,单击项目下需要创建存储区的应用。在导航树上选择“构建 > 云数据库”。选择“存储区”页签。单击“新增”,进入创建存储区页面。输入存储区名称为“CloudDBDemo”。单击“确定”。创建完成后返回存储区列表中,可以查看已创建的存储区。配置开发环境使用CocoaPods集成SDK。集成AGC SDK,详细请参见集成AGC SDK。在podfile文件中添加Cloud DB SDK依赖。pod 'AGConnectDatabase', '~>1.0.4.300'执行pod install,然后打开.xcworkspace文件查看该项目。pod install注意:使用SQLCipher默认生成的文件权限为0644,建议将文件权限修改为0640,保证文件在使用过程中的安全性。每次pod install后,在pod工程中的“TARGETS > SQLCipher > Build Settings > Other C Flags”文件中添加“-DSQLITE_DEFAULT_FILE_PERMISSIONS=0640”。Cloud DB iOS SDK依赖SQLCipher,而SQLCipher是基于SQLite开发的开源组件,由于iOS SDK并没有直接集成SQLCipher源码,所有我们无法提供已修复漏洞的SQLCipher版本。但是,我们会持续关注SQLite上的漏洞信息,当发现新的漏洞时,我们会及时分析和验证,确认漏洞可以修复时,会更新至漏洞补丁表中,您可以根据应用需要选择是否修复该漏洞,详细请参见SQLite漏洞修复。添加对象类型文件在开发应用时,可直接将AppGallery Connect控制台上导出的Objective-C格式文件添加至本地开发环境,并通过AGConnectCloudDB类中的createObjectType方法实现对象类型的定义和创建。在进行本地应用开发时,无需再次创建对象类型。将已在AppGallery Connect控制台上导出的全部Objective-C格式文件添加至本地开发环境。初始化Cloud DB,通过AGConnectCloudDB类中的createObjectType方法实现对象类型的定义和创建,详细请参见初始化。初始化在配置完开发环境后,就可以使用云数据库进行应用开发。开发应用时,需要先执行初始化操作,初始化AGConnectCloudDB、创建Cloud DB zone和对象类型。通过initEnvironment方法初始化AGConnectCloudDB。NSError *error = nil; [AGConnectCloudDB initEnvironment:&error];通过shareInstance方法获取AGConnectCloudDB实例,并使用createObjectType方法创建对象类型。self.agcConnectCloudDB = [AGConnectCloudDB shareInstance]; NSError *createError= nil; [self.agcConnectCloudDB createObjectType:[AGCCloudDBObjectTypeInfoHelper obtainObjectTypeInfo] error:&createError];创建Cloud DB zone配置对象,并打开该Cloud DB zone(以Cloud DB zone的同步属性为缓存模式、访问属性为公共存储区为例),详细请参考AGCCloudDBZoneConfig。AGCCloudDBZoneConfig *zoneConfig = [[AGCCloudDBZoneConfig alloc] initWithZoneName:zoneName syncMode:AGCCloudDBZoneSyncModeCloudCache accessMode:AGCCloudDBZoneAccessModePublic]; __weak typeof(self) weakSelf = self; [self.agcConnectCloudDB openCloudDBZone2:zoneConfig allowCreate:YES callback:^(AGCCloudDBZone * _Nullable zone, NSError * _Nullable error) { weakSelf.dbZone = zone; }];写入数据在本节主要介绍如何在应用程序中进行数据写入操作,以便您了解如何使用Cloud DB SDK实现数据的写入。在应用界面中,增加了“添加”按钮,用于用户新增数据,并在代码中通过executeUpsert实现数据的写入。- (void)executeUpsertWithBooks:(NSArray <BookInfo *> *__nonnull)books complete:(void(^)(BOOL success, NSError *error))complete { if (books.count == 0) { return; } [self.dbZone executeUpsert:books onCompleted:^(NSInteger count, NSError * _Nullable error) { if (error) { if (complete) { complete(NO, error); } } else { if (complete) { complete(YES, nil); } } }]; }查看数据获取数据变化用户在应用界面中新增的数据,将会被存储在云侧。在端侧注册数据变化侦听器,当云侧数据发生变化时,端侧能够感知数据变化,及时刷新本地应用数据。通过查询条件与subscribeSnapshotWithQuery方法组合使用,可以指定侦听对象,当侦听对象的数据发生变化时,端侧会收到通知,根据快照获取变化的数据信息,从云侧同步数据至端侧应用。- (void)subscribeSnapshotComplete:(void(^)(NSArray *bookList, NSError *error))complete { AGCCloudDBQuery *query = [AGCCloudDBQuery where:[BookInfo class]]; [query equalTo:@YES forField:@"showFlag"]; [self.dbZone subscribeSnapshotWithQuery:query policy:AGCCloudDBQueryPolicyCloud listener:^(AGCCloudDBSnapshot * _Nullable snapshot, NSError * _Nullable error) { if (snapshot != nil) { NSArray *bookList = snapshot.snapshotObjects; if (complete) { complete(bookList, nil); } } else { if (complete) { complete(nil, error); } } }]; }数据查询和排序在应用界面中,增加了“查询”按钮和排序功能,通过executeQuery实现异步方式查询数据。- (void)queryAllBooksWithResults:(void(^)(NSArray * bookList, NSError *error))results { AGCCloudDBQuery *query = [AGCCloudDBQuery where:[BookInfo class]]; [self.dbZone executeQuery:query policy:AGCCloudDBQueryPolicyCloud onCompleted:^(AGCCloudDBSnapshot *_Nullable snapshot, NSError *_Nullable error) { if (error) { if (results) { results(nil, error); } } else { NSArray *bookList = snapshot.snapshotObjects; if (results) { results(bookList, nil); } } }]; }通过查询与limit方法组合,实现限制查询数据显示条数的功能;与orderByAsc方法或者orderByDesc方法组合来实现数据的排序功能。- (void)fuzzyQueryAGCDataWithBookInfo:(nonnull BMQueryBookDataModel *)bookInfo results:(void(^)(NSArray * bookList, NSError *error))results { AGCCloudDBQuery *query = [AGCCloudDBQuery where:[BookInfo class]]; //contains [query contains:bookInfo.bookName forField:@"bookName"]; //greaterThanOrEqualTo [query greaterThanOrEqualTo:bookInfo.minBookPrice forField:@"price"]; //lessThanOrEqualTo [query lessThanOrEqualTo:bookInfo.maxBookPrice forField:@"price"]; //limit [query limit:(int)bookInfo.count]; //orderByAsc [query orderByAsc:@"bookName"]; [self.dbZone executeQuery:query policy:AGCCloudDBQueryPolicyCloud onCompleted:^(AGCCloudDBSnapshot *_Nullable snapshot, NSError *_Nullable error) { if (error) { if (results) { results(nil, error); } } else { NSArray *bookList = snapshot.snapshotObjects; if (results) { results(bookList, nil); } } }]; }应用编译运行到此,您已经了解了示例应用的开发流程。您可以编译并在iPhone手机或模拟器上运行示例应用。如需体验示例应用,您可以在A手机或A模拟器上打开应用,并新增数据;然后您可以在B手机或B模拟器上打开应用,查看在A手机或A模拟器上写入的数据。
  • [热门活动] lts日志服务什么时候可以支持andriod/ios等端上报日志做分析?
    lts日志服务什么时候可以支持andriod/ios等端上报日志做分析?阿里云的sls就支持(https://help.aliyun.com/document_detail/48869.html?spm=a2c4g.11186623.6.544.5e9d44f0kUY8Vb),不知道lts的定位是不是对标sls的
  • [优化更新] 20210310云速建站版本升级日志
    更新描述:1.小程序新增产品详情文字设置项;2.小程序编辑器团购列表优化;3.管理日志新增添加模块/插件日志;4.订单导出新增发 票字段;5.产品列表会员产品优化;6.新增横向导航样式;7.新增素材样式;一. 小程序新增产品详情文字设置项1. 在小程序编辑器中,拖出“产品详情”插件,在文字设置中新增了产品详情的设置项,如图所示:二. 小程序编辑器团购列表优化1. 在小程序编辑器中,团购列表插件的图片样式已经和开发者工具(实际环境)保持一致了,如图所示:三. 管理日志新增添加模块/插件日志1. 在建站后台,“网站日志”-“管理日志”中,新增了添加和删除插件操作的日志记录,如图所示:四. 订单导出新增发票字段1. 在建站后台,“订单”-“订单管理”页面导出订单时,新增了发 票字段,如图所示:五. 产品列表会员产品优化1. 在pc和手机的基础版、标准版建站中,现在在未登录状态查看会员产品,会先提示登录,再判断是否有查看会员产品的权限了。六. 新增横向导航样式(手机全版本)1. 在手机版建站中,拖出“导航栏”模块,在横向分类中新增了一个样式,可以左右滑动来选择不同的页面,如图所示:七. 新增素材样式1. 在pc版建站,素材模块“人物介绍”中新增了一个样式,如图所示:2. 在手机版建站,素材模块“人物介绍”中新增了一个样式,如图所示:3. 在小程序编辑器中,也新增了一个样式,如图所示:
  • [调优工具] 【Hyper Tuner调优实践 21】基于Java性能分析工具的GC日志调优实践
    1      调优概述华为鲲鹏性能优化工具是一款针对鲲鹏平台的性能调优工具,包含系统性能优化工具和Java性能优化工具。本文使用Java性能优化工具对运行中的Java程序进行在线分析和采样分析,找到程序问题,并根据分析结果进行优化修改,从而实现Java程序最佳运行。2      环境要求项目说明服务器TaiShan 200 服务器(型号2280)CPU鲲鹏920 4826处理器OSCentOS 7.6调优工具华为鲲鹏性能优化工具 V2.3.T103      前提条件服务器和操作系统正常运行。PC端已经安装SSH远程登录工具。需要优化的Java程序且程序开启GC日志打印。4      调优思路 使用Java性能优化工具对Java进程GC日志进行在线采集或离线上传,解析获取的GC日志。 针对解析后得到的优化建议项进行调优。 观察优化后的Java程序日志,判断对应问题是否改善。 5      调优过程5.1      获取GC日志5.1.1        对程序进行在线分析,在GC页签下GC日志页面点击执行GC日志采集,获取日志并进行解析。5.2      查看优化建议项当前GC日志共有五项优化建议:依次查看对应优化建议,并根据建议项进行信息修改。5.2.1        元空间扩容导致的GC优化jdk8以后移除永久区,使用本地内存来存储类元数据信息并称之为元空间(Metaspace)。持续的元空间垃圾回收说明:可能存在类、类加载器导致的内存泄漏或是大小设置不合适。根据优化建议对代码进行排查,发现代码中存在每次调用循环动态创建并加载类。针对本例:可减循环次数,减少类的加载;将classes对象设为单例模式避免重复调用。对于无法进行代码优化的案例结合成因分析,查看因元数据产生的GC次数及时间占比是否偏高,若想降低可调高-XX:MetaspaceSize,少因扩容造成的GC。5.2.2        疏散失败当给出疏散失败时,表示存在GC幸存区或Old区空间不足,可增大响应空间避免出现疏散失败。当空间无法增加时可尝试调整如下选项:1、调大-XX:G1ReservePercent(默认值10),设置堆老年代预留内存晋升,以降低提升失败的可能性;降低-XX:InitiatingHeapOccupancyPercent阈值(默认45);3、增加并行标记线程的数目,调大-XX:ParallelGCThreads参数(默认值随运行平台不同而不同)。可在优化后重新执行GC日志采集,查看疏散失败优化建议是否触发。或在日志中统计单位时间内疏散失败的比例。5.2.3        发生巨型对象导致的GC当发生巨型对象分配时(大于region size的50%),G1会找出一个连续的可用分区集合,这样就能汇总出足够的内存来容纳巨型对象。如果没有足够的连续可用空间,G1就会启动一次Full GC 来压缩Java堆空间。巨对象分配后对应region不再存储其它对象造成了一定的空间浪费。通过调大-XX:G1HeapRegionSize可减少因巨对象空间不足导致的Full GC且能提升内存利用率。可通过GC成因分析查看G1 Humongous Allocation触发的GC占比统计看巨对象触发的GC是否由改善。5.2.4        发生Full GC当空间即将耗尽或分配速度无法追上回收速度时就会触发Full GC,当有Full GC发生代表性能可能不足。除关注程序本身是否可优化或提升机器性能(增加内存,提升cpu性能)外,还可以尝试调整相关参数提高GC效率来降低Full GC发生的可能性:1、增加堆内存(Xmx)大小,让G1有更多的时间去完成 Concurrent Marking。2、适当加大 -XX:ConcGCThreads 选项的值,增加并发标记的线程数。3、通过增加 -XX:G1ReservePercent(默认10%)的选项值,增加 G1 在 IHOP 分析过程中所需要的内存空间。4、通过设置 -XX:-G1UseAdaptiveIHOP 的选项值禁用自适应 IHOP 分析机制,通过调小 -XX:InitiatingHeapOccupancyPercent 的选项(默认值为 45)值达到提前触发GC标记周期的目的。可对比优化前与优化后的GC活动细化分析数据下的Full GC统计数据,通过对Full GC平均间隔时间对比可确定Full GC频率是否降低。5.2.5        发生System.GC()导致的GC程序中不建议显示调用System.GC()强制触发Full GC。建议排查代码将相关代码移除。或使用-XX:+DisableExplicitGC忽略System.GC()调用。若为系统级别或框架级别的调用无法禁用,可开启-XX:+ExplicitGCInvokesConcurrent,Full GC 会变成 initial-mark。检验优化效果可对优化后程序进行日志采集查看优化建议中是否还有system.GC()的优化建议。或查看GC日志看system.GC()触发日志是否存在,若存在看是否为initial-mark阶段6      实践总结Java中通过查看GC日志较难直接定位问题,故平台提供的GC日志解析后优化建议,通过优化建议可较为便捷的对代码进行排查或修改启动参数。重启进程并在运作一段时间后再次采集解析GC日志,通过对比前后日志吞吐量、GC平均暂停时间、活动细化分析-平均间隔时间等数据的变化,从而确定GC效率是否已经改善。针对GC日志的调优不建议作为优先使用方案,可作为在程序自身调优已达瓶且无更多调优手段时的尝试。据GC优化建议对程序进行优化后无明显改善的,建议:升级机器配置以适应业务需求!