yapi的使用之Mock篇

yapi的使用之Mock篇2-类型修改

一、时间

编号 接口 变量 变量含义 原来为 修改为 备注
1 order/orderList
订单列表
orderTime 下单时间 string integer @timestamp
2 order/orderDetail
订单详情
deliveryTime
gmtCreate 2处
sendTime
orderTime
overTime
payTime
送达时间
创建时间
送出时间
下单时间
超时时间
付款时间
3 product/getGoodsDetailInfo
获取商品详情(包含爆品、精品)
homeRecoTime 首页推荐时间 string integer @timestamp
4 member/getMemberInfo
获取会员信息
startTime
endTime
会员有效期 - 开始时间
会员有效期 - 开始时间
5 wish/listByBuyId
用户心愿单列表查询
gmtCreate 创建时间
6 wish/listWatchWish
查询好友的心愿单列表
gmtCreate 创建时间
7 wish/getDetail
心愿单详情页
gmtCreate 2处
gmtModify 2处
statusUpdateTime
topTime 2处
创建时间
记录更新时间
状态更新时间
置顶时间
8 /user/queryUserWishCollect
查询愿望单收藏
gmtCreate 创建时间
9 product/getGoodsInfoList
获取商品列表(包含推荐,收藏,愿望,搜索推荐,分类)
categoryRecoTime
homeRecoTime
分类推荐时间
首页推荐时间

二、数字字符串

编号 接口 变量 变量含义 原来为 修改为 备注
1 wish/getDetail
心愿单详情页
progress.rate 愿望达成率 string string 40只能数字字符串
2 wish/listByBuyId
用户心愿单列表查询
progress.rate 愿望达成率 string string 40只能数字字符串
3 wish/listWatchWish
查询好友的心愿单列表
progress.rate 愿望达成率 string string 40只能数字字符串
4 /wish/listRecommendWish
查询推荐的心愿单列表
wishProgress.rate 愿望达成率 string string 40只能数字字符串

若非数字字符串,则会报以下错误:

yapi_mockjson_error1

三、Raw

用户心愿单列表查询

编号 接口 Raw旧值 Raw新值
1 user/saveUserLookTrack
保存用户足迹接口
OK {“type”:”object”,”title”:”empty object”,”properties”:{}}
2 social/operateFriend
关注/取消关注好友操作接口

四、Type Mock

用户心愿单列表查询

编号 接口 变量 变量含义 Mock值 备注
1 order/orderList
订单列表
orderInfoAndPayInfoDetailResps.orderInfoDetails.status 订单状态 @cjtsOrderType
2 order/orderDetail
订单详情
orderInfoDetails.status 订单状态 @cjtsOrderType
3 /social/pageWatched
分页查询已关注列表
watchStatus 关注方向 @cjtsFriendshipeType
4 product/getGoodsInfoList
获取商品列表(包含推荐,收藏,愿望,搜索推荐,分类)
businessType 商品类型 @goodBusinessType

End

yapi的使用之入门篇

一、模拟接口

如果你已在http://121.41.91.92:3000/上创建了对应接口,直接在接口path后,添加.toSimulateApi()实现,你重新请求的时候调用的是你模拟的api。

1
2
3
4
static String getVirtualList = "/account/wallet/virtualAsset/page"

//模拟时候,在字符串尾部加上`.toSimulateApi()`即可
static String getVirtualList = "/account/wallet/virtualAsset/page".toSimulateApi();

如果你未创建过接口,请查看如下接口的创建方法

二、新建接口

  • 点击左侧接口分组右侧的菜单按钮,选择 添加接口,或者点击接口列表右上角的 添加接口

img

  • 选择接口分类,输入接口名称和接口路径,点击 提交

img

  • 恭喜你!创建了第一个YApi的接口,你可以看到在左侧看到接口名称,右侧有该接口的信息预览。

三、注册

登录http://121.41.91.92:3000/进行注册,注册成功,管理员添加项目权限后,即可查看对应接口列表。如:

image-20211222151919051

End

yapi的使用之设置篇

一、禁止注册

image-20201121024134502

二、swagger的接口同步

使用方法如下:

image-20220111173613924

在url处,填入从 swagger 中渠道的地址(建议http支持的情况下,填http,而不是https)

image-20220111173348600

然后点击上传即可。

同步后注意,接口的基本路径是否有发生变更。

1、同步过程中的问题

1.1、上传失败,出现error:getaddrinfo ENOTFOUND

如果上传失败,即上传过程中出现error:getaddrinfo ENOTFOUND的错误提示。只需要重启阿里云服务器即解决了。

附重试执行getaddrinfo也没用,如下:所以,还是重启服务器好了。

1
failed to install packages: cannot download packages/p/perl-Socket-GetAddrInfo
image-20220110165950164

1.2、Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate’s altnames: Host: dev2.api.xxx.com. is not in the cert’s altnames: DNS:dev.api.xxx.com

image-20220125101801211

解决方法:将原本的swagger地址,从https改为http

这个问题是因为请求的目标地址是非信任的地址(比如不是 HTTPS)导致的,可以通过在环境变量里加入下面的变量解决

1
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'

解决 Webpack devServer 请求时发生 Hostname/IP does not match certificate’s altnames 错误

End

图片库SDWebImage③解码

好文推荐:

图片常见的3种编码方式 见本文附录

万象:

1
2
CGSize thumbnailSize = CGSizeMake(200, 200); // Thumbnail will bounds to (200,200)
[imageView sd_setImageWithURL:url placeholderImage:nil options:0 context:@{SDWebImageContextImageThumbnailPixelSize : @(thumbnailSize)];

一、为什么图像在显示到屏幕上之前要进行解码

我们知道,一般我们都是使用形如以下方式加载图片:

1
2
+ (nullable UIImage *)imageNamed:(NSString *)name;      // load from main bundle // 通过图片的文件名从bundle 获取这个图片,注意该图片已经导入到工程中
+ (nullable UIImage *)imageWithContentsOfFile:(NSString *)path; // 通过文件加载指定路径下的文件内容创建图片

问1:解码是个啥?不编码是不是可以不解码?

答:那是因为一般下载的图片或者我们手动拖进主bundle 的图片都是PNG 或者JPG 其他格式的图片,这些图片都是经过编码压缩后的图片数据,并不是控件可以直接显示的位图。所以需要先将它解码转成位图数据,然后才能把位图渲染到屏幕上。这就是解码!(附:图像可以分为矢量图和位图,我们通常使用的图像为位图格式)

一张图片引发的深思

1.1、手动解码效果对比

iOS 列表性能优化-图片解码性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)queryImageCache:(NSString *)filename block:(void(^)(UIImage *image))block
{
//从内存去取,如果没取到,就直接读取文件,在缓存起来
UIImage *image = [self.memCache objectForKey:filename];
if(image) {
dispatch_async(dispatch_get_main_queue(), ^{
if(block) block(image);
});
} else {
//把解压操作放到子线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:@"jpg"];
UIImage *image = [UIImage imageWithContentsOfFile:path];
image = [UIImage decodedImageWithImage:image]; // 解码图片
[self.memCache setObject:image forKey:filename]; // 保存解码的图片
// 同步主线程
dispatch_async(dispatch_get_main_queue(), ^{
if(block) block(image);
});
});
}
}

1.1、图片不编码的话,是不是可以显示时候就可以不用去解码,从而能更快了?

实际上编码工作不可能少了。其目的可能是为了压缩、优化、添加元数据(如拍摄信息、版权信息、色彩配置文件等)。

1.2、手动解码的原理

自己手动解码的原理就是对图片进行重新绘制,得到一张新的解码后的位图。其中,用到的最核心的函数是 CGBitmapContextCreate 。

image-20240905220154620

image-20240905210236393

问2:没解码显示不了图片,那为什么直接使用上面的方式进行图片的加载可以正常显示?

答案是:从实际开发中,我们基本都是使用上面的两种方式直接在主线程加载图片,然后显示在UIImageView上,并且并没有发现什么问题。是不是代表显示图片不用解码?其实不是的,因为系统会为我们进行解码的操作。所以,解码图片的工作在图片显示流程中还是必不可少的。

问3:系统是在什么时候为我们进行图片的解码操作的?图片加载的工作流?

当你用 UIImage 或 CGImageSource 的那几个方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。

概括来说,从磁盘中加载一张图片,并将它显示到屏幕上,中间的主要工作流/图片加载的工作流如下:

  1. 假设我们使用 +imageWithContentsOfFile: 方法从磁盘中加载一张图片,这个时候的图片并没有解压缩;

  2. 然后将生成的 UIImage 赋值给 UIImageView ;

  3. 接着一个隐式的 CATransaction 捕获到了 UIImageView 图层树的变化;

  4. 在主线程的下一个 run loop 到来时,Core Animation 提交了这个隐式的 transaction ,这个过程可能会对图片进行 copy 操作,而受图片是否字节对齐等因素的影响,这个 copy 操作可能会涉及以下部分或全部步骤:

  5. ①分配内存缓冲区用于管理文件 IO 和解压缩操作;

    ②将文件数据从磁盘读到内存中;

    ③将压缩的图片数据解码成未压缩的位图形式,这是一个非常耗时的 CPU 操作;

    ④最后 Core Animation 使用未压缩的位图数据渲染 UIImageView 的图层。

问4:使用系统解码有什么问题?或者说SDWebImage还要对图片进行解码是为了什么?

①、使用系统解码有什么问题?

答:如果我们直接使用 + (nullable UIImage *)imageNamed:(NSString *)name 来加载图片,系统默认会在主线程立即进行图片的解码工作,这个过程就是把图片数据解码成可供控件直接显示的位图数据。由于这个解码/解压缩操作是一个比较耗时的CPU操作,并且默认是在主线程进行的。所以当在主线程调用了大量的 + (nullable UIImage *)imageNamed:(NSString *)name 方法后就会产生卡顿,尤其是在快速滑动的列表上,这个问题会表现得更加突出。

②、SDWebImage还要对图片进行解码的目的是

优化1:默认是在主线程解码,SDWebImage将解码这个过程放到子线程。

图片解码是耗时的,而且iOS系统默认是在主线程执行解码,所以业界通常有一种做法是,异步强制解压,也就是在异步线程主动将二进制图片数据解压成位图数据,使用CGBitmapContextCreate(…)系列方法就能实现。
该处理方式在众多图片处理框架下都有体现。

所以SDWebImage 为了提高图片的加载效率,会提前去进行解码图片到内存,即把图片数据解码成可供控件直接显示的位图数据。同时由于位图体积较大,所以在磁盘缓存中不会直接缓存位图数据,而是编码压缩过的PNG 或者JPG 数据。

在我们使用 UIImage 的时候,创建的图片通常不会直接加载到内存,而是在渲染的时候再进行解压并加载到内存。这就会导致 UIImage 在渲染的时候效率上不是那么高效。为了提高效率通过 decodedImageWithImage方法把图片提前解压加载到内存,这样这张新图片就不再需要重复解压了,提高了渲染效率。这是一种空间换时间的做法。

问5:SDWebImage解压的策略是怎样的?

SDWebImage中使用以下策略:

  1. 当图片从网络中获取到的时候就进行解压缩。
  2. 当图片从磁盘缓存中获取到的时候立即解压缩。

二、SDWebImage是怎么进行图片Decoder解码的UIImage+ForceDecode.h

1、UIImage+ForceDecode.h 的 sd_decodedImageWithImage:

下面首先看解码的方法,UIImage+ForceDecode.h

1
2
+ (nullable UIImage *)sd_decodedImageWithImage:(nullable UIImage *)image;
+ (nullable UIImage *)sd_decodedAndScaledDownImageWithImage:(nullable UIImage *)image limitBytes:(NSUInteger)bytes;
image-20211201013634575

其实现为:

1
2
3
4
5
6
+ (nullable UIImage *)sd_decodedImageWithImage:(nullable UIImage *)image {
if (!image) {
return nil;
}
return [SDImageCoderHelper decodedImageWithImage:image];
}

2、[SDImageCoderHelper decodedImageWithImage:image]

SDImageCoderHelper中的实现代码:

image-20211201014559478

这个方法传入一副图片对该图片进行解码,解码结果是另一幅图片。

2.1、[SDImageCoderHelper shouldDecodeImage:image]

[SDImageCoderHelper shouldDecodeImage:image]用来判断要不要解码,并不是所有的image 都要解码。其函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma mark - Helper Function
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
if (image == nil) {
return NO;
}
// Avoid extra decode
if (image.sd_isDecoded) {
return NO;
}
// do not decode animated images
if (image.sd_isAnimated) {
return NO;
}
// do not decode vector images
if (image.sd_isVector) {
return NO;
}

return YES;
}

1.如果 image 等于 nil,返回 NO。 // 防止 “CGBitmapContextCreateImage: invalid context 0X0” 的错误

2.如果已经解码过,返回NO。

3.如果 image 是动效图片,返回 NO。// 不要解码动画图像

4.如果sd_isVector,返回NO。

2.2、[SDImageCoderHelper CGImageCreateDecoded:cgImage]

其中+ (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
+ (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage {
return [self CGImageCreateDecoded:cgImage orientation:kCGImagePropertyOrientationUp];
}

+ (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage orientation:(CGImagePropertyOrientation)orientation {
if (!cgImage) {
return NULL;
}
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
if (width == 0 || height == 0) return NULL;
size_t newWidth;
size_t newHeight;
switch (orientation) {
case kCGImagePropertyOrientationLeft:
case kCGImagePropertyOrientationLeftMirrored:
case kCGImagePropertyOrientationRight:
case kCGImagePropertyOrientationRightMirrored: {
// These orientation should swap width & height
newWidth = height;
newHeight = width;
}
break;
default: {
newWidth = width;
newHeight = height;
}
break;
}

BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
// iOS prefer BGRA8888 (premultiplied) or BGRX8888 bitmapInfo for screen rendering, which is same as `UIGraphicsBeginImageContext()` or `- [CALayer drawInContext:]`
// Though you can use any supported bitmapInfo (see: https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB ) and let Core Graphics reorder it when you call `CGContextDrawImage`
// But since our build-in coders use this bitmapInfo, this can have a little performance benefit
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGContextRef context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo);
if (!context) {
return NULL;
}

// Apply transform
CGAffineTransform transform = SDCGContextTransformFromOrientation(orientation, CGSizeMake(newWidth, newHeight));
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); // The rect is bounding box of CGImage, don't swap width & height
CGImageRef newImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);

return newImageRef;
}

2.3、解码的核心代码

image-20211201023244869

  • 使用CGBitmapContextCreate()创建图片上下文

  • 使用CGContextDrawImage()将图片绘制到上下文中

  • 使用CGBitmapContextCreateImage()通过上下文生成图片

    其他参考:SDWebImage 中的图片解码

3、其他

3.1、两个静态不可变的类型是 size_t 的变量

①、kBytesPerPixel:每个像素占内存多少字节(Byte),赋值为4,表示每个像素占4个字节(RGBA各一个字节)。
1
static const size_t kBytesPerPixel = 4;

(图像在iOS 设备上是以像素为单位显示的)

②、kBitsPerComponent:每个组件占多少位(Bit),赋值为8
1
static const size_t kBitsPerComponent = 8;

kBitsPerComponent:每个组件占多少位(Bit),这个不好理解,举个例子,比如RGBA,其中 R (红色)G(绿色)B(蓝色)A(透明度)总共4个组件,每个像素由这4个组件组成,且该变量被赋值为8,所以一个 RGBA 像素就是8 * 4 = 32 Bits。

知道了 kBitsPerComponent 和每个像素有多少组件组成就能计算 kBytesPerPixel 了。计算公式是: (bitsPerComponent * number of components + 7)/ 8。

3.2、其他

SDWebImage源码阅读(四)Decoder,有点抽象

三、其他参考文章

SDWebImage源码阅读前的准备(三)UIImage.h

附1:图片常见的3种编码方式

类型 演示示例
baseline
逐行扫描
默认情况下,JPEG、PNG、GIF 都是这种保存方式。 img
interlaced
隔行扫描
PNG 和 GIF 在保存时可以选择这种格式。 img
progressive
渐进式
JPEG 在保存时可以选择这种方式。 img

imageIO完成渐进加载图片

Android开发环境搭建与运行

基础认识

连接设备

1
2
3
adb devices
adb kill-server
adb start-server

minSdkVersion, compileSdkVersion, targetSdkVersion的理解

Android 12 targetSdkVersion为31应用的安装问题

Android 12 targetSdkVersion为31应用的安装问题

一般为 android:exported 为声明

一、Android SDK的检查

下载 31版本的SDK

android_sdk31_1 android_sdk31_2 android_sdk31_3 android_sdk31_4

查看 Android SDK 的路径

image-20220320233100662

Mac 配置Android环境变量

环境变量设置方法:环境变量设置

修改环境变量:

非M1类型的Mac:终端输入open -n ~/.bash_profile

是M1类型的Mac:终端输入open -n ~/.zshrc

1
2
3
4
#android sdk
export ANDROID_HOME=/Users/qian/Library/Android/sdk
export PATH=${PATH}:${ANDROID_HOME}/tools
export PATH=${PATH}:${ANDROID_HOME}/platform-tools
image-20220321011954689

编辑完保存并退出

非M1类型的Mac:输入 source ~/.bash_profile 使环境变量生效。

是M1类型的Mac:输入 source ~/.zshrc 使环境变量生效。

1、mac gradle存放路径

1
2
cd ~/.gradle/wrapper/dists
open ./

android gradle 1

image-20200825154514730

image-20220401110513513

Mac android studio 一直卡在Gradle:Build Running的解决办法

1.找到路径/Users/michael/.gradle/wrapper/dists/gradle-4.1-all/bzyivzo6n839fup2jbap0tjew,在此文件夹下有一个gradle版本文件夹,打开后是一个名字很长的文件夹,
例如我的/Users/michael/.gradle/wrapper/dists/gradle-4.1-all/bzyivzo6n839fup2jbap0tjew,然后下载对应版本的gradle,将下载的压缩包直接放进名字很长的文件夹中即可,不需要解压

image-20220401111143361

附:Gradle下载地址:https://services.gradle.org/distributions/

image-20220401111917520

不要使用Safari下载,因为其会自动解压。所以我们使用Chrome下载。

image-20220401112139784

打开cmd,输入gradle -v(mac系统下是./gradle -v),测试下安装成功了没

image-20220401112616236

distributionurl gradle本地配置

1
2
3
4
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

# 修改为:
distributionUrl=file:/Users/qian/.gradle/wrapper/dists/gradle-6.7-all/cuy9mc7upwgwgeb72wkcrupxe/gradle-6.7-all.zip

安装Flutter插件

在android studio中,选择“Plugins”->”Broswer repositories”->输入搜索flutter,再点击安装即可

image-20220317184609635

先用Android Studio,新建一个全新app,然后执行尝试./gradlew assembleRelease效果。

image-20220320222222228

执行./gradlew assembleRelease过程中的问题

1、没有gradlew文件

image-20220317194632116

解决:拷贝文件

2、没有配置Java环境

image-20220317194538523

解决安装Java环境。

安装Java环境

**1.**检查Java环境

在安装JDK之前,先查看下自己电脑是否已经安装了JDK。

打开终端,输入java -version并回车。

image-20220317194403944

从上图中可以看没有安装。。

2、未安装情况下,进入oracle 官网 下载Java安装

image-20220317194000775

3. JDK安装。MAC系统JDK安装是很简单的,一路下一步就ok了。

image-20220317195327540

安装结束后,再执行java -version会看到如下结果:

image-20220317195420668

image-20220317195534731

二、Android 的打包运行

工具打包:

image-20220321164244146

通过 assembleRelease 打包

image-20220321165149026

打包成功后,

image-20220321164741999

1、卡在Running Gradle task ‘assembleDebug’…

image-20220321013241447

解决方案参考:Running Gradle task ‘assembleDebug’…

(1). 修改项目中android/build.gradle文件

(2). 修改Flutter的配置文件, 该文件在Flutter安装目录/packages/flutter_tools/gradle/flutter.gradle

注意:要加上

1
2
3
maven {
url 'http://download.flutter.io'
}

image-20220427032205807

Installing build/app/outputs/flutter-apk/app.apk…
Error: ADB exited with exit code 1
Performing Streamed Install

adb: failed to install /Users/qian/Project/Bojue/app-paipal/follow_shot/build/app/outputs/flutter-apk/app.apk: Failure [INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: Failed parse during installPackageLI: /data/app/vmdl1858238574.tmp/base.apk (at Binary XML file line #29): com.example.untitled.MainActivity: Targeting S+ (version 31 and above) requires that an explicit value for android:exported be defined when intent filters are present]
Error: Failed to install APK again.
Error launching application on sdk gphone64 arm64.

Android Studio Kotlin版本兼容

1.build.gradle中的kotlinVersion

1
val kotlinVersion = "1.3.**"
  1. Android Studio中kotlin插件版本

在File-Settings中搜索kotlin plugin,查看版本

image-20220427031441396

JDK相关--多JDK版本

JDK相关–多JDK版本

一、背景

某些时候,我们执行java命令,可能会有版本要求。

如,我们在使用新版的bugly符号表上传脚本的时候,发现其要求我们使用jdk8。而我们打包apk的时候需要使用jdk11。

问:此时我们怎么处理?

解决:使用多jdk,并灵活切换。

二、查看当前使用的java版本

1、查看当前使用的java版本

1
java -version

image-20230522013119037

2、查看本地所有已安装的jdk

2.1、jdk 常见的存放位置

序号 描述 路径 其他
1 系统本地 JDK Home /Library/Java/JavaVirtualMachines
2 Android Studio的 JDK Home /Applications/Android\ Studio.app/Contents/jbr/Contents/Home
3 自定义的 JDK 目录 依自定义

2.2、jdk 各个位置的查看

2.2.1、查看【系统 JDK Home】已安装的jdk
1
2
3
4
5
6
# 方法1:
$ cd /Library/Java/JavaVirtualMachines
$ ls -al

# 方法2:
$ /usr/libexec/java_home -V

方法1的结果如下:

image-20230522013520673

方法2的结果如下:

image-20230615184042643

经查看我们已经在系统jdk home下安装了jdK17和jdk1.8了。

2.2.2、查看【Android Studio的 JDK Home】已安装的jdk

但是上述的方法并不代表着检查到了本地所有的jdk版本。比如通过Android Studio 下载的,默认是放在

1
$ open /Applications/Android\ Studio.app/Contents/jbr/Contents/Home # 空格需要通过反斜杠\来转义

image-20230615185217226

三、下载想要安装的其他jdk

如果已安装请忽略此步。

1、【系统 JDK Home】下jdk的安装

以安装jdk1.8为例,请进入其相应的下载地址:javase8u211-later-archive-downloads.html

2、【Android Studio的 JDK Home】下jdk的安装

安装方法如上述《查看【Android Studio的 JDK Home】已安装的jdk》中的截图中点击+号即可。

下载安装后,按照下文的jdk切换方式,即可使用想要的jdk版本(如执行java -version)。

四、切换 jdk

1、配置切换jdk版本的环境变量

①打开你的环境变量文件

1
2
3
4
5
# 苹果的intel芯片(x86),使用如下命令
open ~/.bash_profile

# 苹果的arm芯片(m1或者m2),使用如下命令
open ~/.zshrc

②在其中添加以下内容

1
2
3
4
5
6
7
8
#添加下面的代码
export JAVA_8_HOME="$(/usr/libexec/java_home -v 1.8)"
export JAVA_17_HOME="$(/usr/libexec/java_home -v 17)"
export JAVA_11_HOME=/Applications/Android\ Studio.app/Contents/jbr/Contents/Home
alias jdk8='export JAVA_HOME=$JAVA_8_HOME'
alias jdk11='export JAVA_HOME=$JAVA_11_HOME'
alias jdk17='export JAVA_HOME=$JAVA_17_HOME'
export JAVA_HOME=$JAVA_8_HOME

在上述代码中,我们设置两个 JAVA_8_HOME 、 JAVA_11_HOME 、 JAVA_17_HOME 三个环境变量。并让 JAVA_HOME 默认等于 JAVA_8_HOME。

同时我们使用alias命令:给命令定义别名,从而得到jdk8jdk11jdk17三个终端命令。其使得当我们在终端输入jdk8的时候,我们的JAVA_HOME 会变成1.8的版本,从而执行的java也就变成了1.8。

③生效环境变量

1
2
3
4
5
# 苹果的intel芯片(x86),使用如下命令
source ~/.bash_profile

# 苹果的arm芯片(m1或者m2),使用如下命令
source ~/.zshrc

④在终端执行刚生成的命令,正式切换java版本

image-20230522015426032

五、Android Studio 中的 jdk 截图

image-20230615185843914

End

感谢浏览。

状态管理1Provider

Flutter页面跳转和传值传参,接收页面返回数据、以及解决返回(pop)页面时黑屏的问题

Flutter页面跳转和传值传参,接收页面返回数据、以及解决返回(pop)页面时黑屏的问题

flutter的渲染方式和移动端完全不同,它采用的React的思路。

移动端的UI控件可以通过修改其属性改变外观,但是flutter和RN,改变样式基本是靠重新渲染,所以想要更新内容,就要改变state,然后再通过setState()更新UI。

ReactNative的Redux

CQComponent/UIKit-Overlay-ReactNative/TSOverlayDemo/src/Redux

一、SetState

我们都知道,Flutter中Widget的状态控制了UI的更新,比如最常见的StatefulWidget,通过调用setState({})方法来刷新控件。那么其他类型的控件,比如StatelessWidget就不能更新状态来吗?答案当然是肯定可以的。前文已经介绍过几种状态管理

二、flutter全局状态管理器Provider

https://pub.dev/packages/provider/install

1
provider: ^4.3.1

为节省代码,我们以flutter全局状态管理器Provider中的代码为例,进行说明。

1、声明ChangeNotifier,类似于iOS中的Manager。

ChangeNotifier == Manager

ChangeNotifier 的 method == Manager 的 method

ChangeNotifier 的 notifyListeners == Manager 的 broadcast

ChangeNotifier 的 notifyListeners 的内部:Flutter系统提供的通知中心
Manager 的 broadcast 的内部:自己实现的多协议通知中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'package:flutter/cupertino.dart';

class NumModel with ChangeNotifier{
int _theNum;
NumModel(this._theNum);
/*_theNum递增*/
void add() {
_theNum++;
notifyListeners();//通知所有监听的页面,如果写在runapp中那么通知所有页面重新加载
}
/*_theNum递减*/
void reduction(){
_theNum--;
notifyListeners();
}
/*获取_theNum*/
int get theNum => _theNum;
}

2、搭建监听系统,即搭建可以接收变化的结构

如果只监听一个model

1
2
3
4
5
6
main() {
runApp(ChangeNotifierProvider<NumModel>.value(
value: NumModel(1),
child: MyApp(),
));
}

如果监听多个model

1
2
3
4
5
6
7
8
9
main() {
runApp(
MultiProvider(providers: [
ChangeNotifierProvider<NumModel>.value(value: NumModel(1)),
ChangeNotifierProvider<NameModel>.value(value: NameModel("json"))
],
child: MyApp()
));
}

我们的项目中很多情况下监听一个model是不够的,所以我们默认使用MultiProvider

个人理解:它类似将所有的监听放在了AppDelegate上。

3、使用Provider,发送变化

1
2
3
4
5
6
MaterialButton(
onPressed: () {
Provider.of<NumModel>(context).add();
},
child: Text("num递增"),
)

4、使用Provider,接收变化

1
2
3
4
Text(
"name=" + Provider.of<NameModel>(context).theName,
style: TextStyle(fontSize: 20, color: Colors.red),
)

BloC【Business Logic Component】

Flutter工程实践小结

为什么某些变量在flutter自定义类中被标记为final?

升级 Flutter

1、命令方式

2、自己下载替换方式

  • Flutter SDK 官网 下载对应的SDK
  • 替换掉你本地的
  • 关掉所有正在编辑Flutter的项目(如果没有关掉所有的,则会遇到如 @require等找不到的问题)
  • 重新pub get

Flutter出现List is not a subtype of type List解决方法

控制台错误如下:

1
2
type 'List<dynamic>' is not a subtype of type 'List<String>'
1

出现这个错误的原因是由于解析json的数据类型不一致导致

后台返回的一个json字段为:

1
2
3
4
5
6
List<String> imagesString = ['https://1.png', 'https://2.png'];
String imageParam = jsonEncode(imagesString);
print('imageParam = $imageParam');
//List<String> images = jsonDecode(imageParam); // 错误写法,会造成崩溃
List<String> images = jsonDecode(imageParam).cast<String>(); // 正确写法
print('转换完成');

解析json用到的model实体类,由于labelList是一个字符串集合数组,
需要在解析labelList字段时加上cast<String>()

1
List<String> labelList = json['labelList'].cast<String>();

二、问题

问题1:Flutter gpu_surface_gl

原因为:机型的gpu不匹配

Flutter gpu_surface_gl

如果你使用的是命令来运行flutter的话,那么在flutter run加参数就可以。即flutter run --enable-software-rendering

但是这个办法有个坑,就是热加载的时候要手动在命令行输入r(不是说好的自动的么,也许是我打开的方式不对);

如果不是用命令来运行的话,那解决步骤如下:

Flutter gpu_surface_gl问题

Flutter gpu_surface_gl问题3

–enable-software-rendering

问题2:resource android:attr/fontVariationSettings not found

解决方案:

try to change the compileSdkVersion to:

compileSdkVersion 28

-bash: dart: command not found

原因是下载Fluter SDK时,Dart SDK已经在捆绑Fluter里了,所以需要设置下Dart环境变量的路径:

①打开配置环境变量的文件: .bash_profile

1
open ~/.bash_profile

②配置Dart SDK所在路径

1
2
export DART_BIN=/Applications/flutter/bin/cache/dart-sdk/bin
export PATH=${PATH}:${DART_BIN}

③最后在终端里执行: source ~/.bash_profile 重新加载一下

1
source ~/.bash_profile

④此时,就可以用dart命令来执行你想要的操作了。比如

1
dart --version

Failed assertion: line 1687 pos 12: ‘hasSize’

原因:Column的子widget中包含ListView

Column的子widget中包含ListView

Flutter数据传值

传值

前言

1
2
3
4
5
6
7
数据传输一般处理方式:自上而下的一层一层传递数据。

场景: WidgetB数据改变后,WidgetA也随之作出响应。

解决方案1: 参考Flutter知识点: InheritedWidget

解决方案2:Notification,子节点状态变更,发送通知上报。

一、父传给子

1、直接传

Flutter中界面之间参数的传递与接收

二、子传给父

1、Callback(子传给父)

大家都知道回调方法在父子Widget之间传值是非常有用的,特别是用于子Widget向父Widget传值,接。

定义一个回调函数类型

1
typedef clickCallback = void Function(String value);

将回调函数透出子Widget

1
2
3
4
5
6
final clickCallback onClick;

TestWidget({
Key key,
this. onClick,
}):super(key:key);

三、传给其它(给父或给其它)

1、Notification(给其它)

1.自定义notification

1
2
3
4
5
6
7
class TestNotification extends Notification {
final int count;

TestNotification({
@required this.count,
});
}

2.子节点发送通知

1
2
3
4
5
6
7
8
9
new RaisedButton(
textColor: Colors.black,
child: new Center(
child: new Text('点击传递随机数给上层Widget'),
),
onPressed: () {
new TestNotification(count: new Random().nextInt(100)).dispatch(key.currentContext);
},
)

3.父节点使用NotificationListener进行监听子节点发出的通知,并作出响应

1
2
3
4
5
6
7
8
9
new NotificationListener(
onNotification: (TestNotification n) {
scaffoldStateKey.currentState.showSnackBar(new SnackBar(content: new Text('随机数:${n.count}')));
return true;
},
child: new TestAPage(
key: key,
),
)