yapi的使用之设置篇

[toc]

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
2
> process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
>

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

End

SDWebImage③解码

[toc]

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完成渐进加载图片

JDK相关--多JDK版本

[toc]

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

感谢浏览。

Android开发环境搭建与运行

[toc]

基础认识

连接设备

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
4
> 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

状态管理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】

开发工具

[toc]

背景

问:我不要频繁安装+卸载来安装我想要的包。我能不能同时装一个测试环境和正式环境?

答:不行。安装两个包,严格上来讲,不用想了,不行。具体原因涉及应用id,及app根据该id配置了各种三方key环境(除非你连三方的也都额外提供一套),太详细的道理我就不讲了。”不行“就对了。但是,我能让你安装一个包,却使用不同环境的功能。或者额外安装一个除与应用id相关的功能外其他皆一样的安装包

一、切换环境常见的场景为:

1、测试时候:换环境不用一直下载;

2、演示时候:某个环境使用不了,不用重新下载;

3、抓包时候:想切换代理,不用重新打包安装;

见下文:app环境与切换

二、额外安装一个除与应用id相关的功能外其他皆一样的安装包的使用场景:

1、app中不能接受切换环境,即严格限制一个包只能有一个环境

不允许切换环境的考虑原因:

测试包不允许切到生产:怕脏数据

生产包不允许切到测试:测试环境不是外界用户真正需要的

见我的另一篇文章:《iOS的重签名.md

一、功能开关

tool_entrance_0

tool_entrance_2log

二、app安装包与切换

1、app安装包信息

devtool_appinfo_0package

1.1、换包:app下载页

devtool_appinfo_0package_downloadurl

1.2、版本记录:历史版本记录页

devtool_appinfo_0package_version_records

2、各文件目录及大小

devtool_appinfo_0package_dirsize

三、app环境与切换

package_env_test package_env_product

流程图:《开发工具(含环境切换等).graffle.graffle)》中的【一、切换环境】版面

devtool_appenv_home

1、切换环境

2、切换平台

3、添加代理

devtool_appenv_3proxy_1

devtool_appenv_3proxy_2

四、设备信息

devtool_device_0devicetoken

devtool_deviceinfo_0

1、设备网络类型

2、设备屏幕大小

2.1、设备适配验证

devtool_device_uiadapt_check

3、设备ip及代理

4、设备埋点参数

未截取到的其他埋点参数见我的另一篇文章:《埋点规范_结构.md

5、是否模拟器

devtool_device_simulator_check

五、用户相关

devtool_user_info

六、调试页面

支付 支付宝
认证 实名认证、真人认证
选择器 生日选择、活动时间选择、项目多选
分享
关键页面入口 个人主页、发布页、引导页、完善性别/生日
位置相关 地图位置选择

快捷入口

1、检查更新

devtool_debug_0checkversion_1

devtool_debug_0checkversion_2

2、网页测试

devtool_debug_2webview_0home

2.1、网页快捷入口

devtool_debug_2webview_1url_entrance

2.2、白屏测试

devtool_debug_2webview_2blank_test.gif

2.3、JS测试

h5js.md

devtool_debug_2webview_3js

2.4、Route测试

h5_open_app.md

devtool_debug_2webview_4route

七、开发工具安全性处理

见我在”安全”专题里的另一篇文章:《开发工具安全性处理

八、脚本工具

目前的脚本操作如下:

qtool_ui_home

其中自定义的脚本操作有如下:

qtool_ui_custom_home

附1:Charles

文档:《Charles.md

附2:代理的实现(Flutter版)

dio添加代理

1
2
3
4
5
6
7
8
9
10
11
   (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
// config the http client
client.findProxy = (uri) {
CommonUtils.log('fcs-myproxy-begin:${uri is Uri} uri:${uri.toString()}');
return "PROXY 192.168.10.47:8888";//如果设置代理(localhost,127.0.0.1这样的是不行的。必须是电脑的ip)
// return 'DIRECT';// 如果不设置代理
};
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;//忽略证书
};

End

实践小结

[toc]

为什么某些变量在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

实践小结

[toc]

待做

Flutter获取版本号

我们app版本号写在pubspec.yamlversion字段后面。例如version: 1.0.0+3 其中+前面为版本号,后面为构建号。

一、基本设置

1、设置背景颜色/背景图

1.1、设置普通视图的背景色

采用包一层Container,进行对应的color设置。

1
2
3
4
return Container(
color: Colors.red, // 设置视图颜色
child: xxx,
);

1.2、设置页面的背景色

1
2
3
4
5
return Scaffold(
backgroundColor: Colors.yellow, // 设置页面背景颜色
appBar: _appBar(),
body: _pageWidget(),
);

2、设置背景图片

2.1、设置普通视图的背景图

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
class EmptyView extends StatelessWidget {
const EmptyView({Key key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration( // 设置视图的背景图/秀视图
image: new DecorationImage(
fit: BoxFit.cover, // 充满容器,可能会被截断。
image: new NetworkImage(
'https://randomuser.me/api/portraits/men/43.jpg'),
),
),
child: Container(
color: Colors.red.withOpacity(.5),
child: Center(
child: Text(
"我在图片的上面哦~",
style: TextStyle(color: Colors.white, fontSize: 33),
),
),
),
);
}
}

2.2、设置页面的背景图

设背景图为如上EmptyView,则设置代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.yellow,
appBar: _appBar(),
body: _emptyView(),
);
}

// 特殊场景:如果你的页面上有TextField等会弹出键盘的视图。因为默认情况下键盘出现时是否应调整主体Scaffold的大小。所以,为了图片能正确显示,我们使用resizeToAvoidBottomInset指定在键盘出现时是否应调整主体的大小。
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.yellow,
resizeToAvoidBottomInset: false, // 增加此行,指定在键盘出现时不调整主体的大小
appBar: _appBar(),
body: _emptyView(),
);
}

二、布局

1、充满

默认情况下,大多数组件都会使用尽可能小的空间。所以如果不设置视图的大小的话,其会自动计算大小,只显示对应的大小。所以当你的视图不够大,而你却又想让它充满父视图的话。那应该进行一些对应的设置。

1.1、设置width、height等于父视图大小方式(不建议)

①、如果视图有width和height属性,则直接设置。如ContainerImage等。如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
return Image(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
image: AssetImage(
'lib/commonui/cq-guide-overlay/Resources/bg_背景遮罩.png'),
fit: BoxFit.fill,
),

return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.red,
child: xxx,
);

缺点:需要计算大小,不建议使用。

②、如果该视图没有width和height属性,则我们通过为该视图包一层Container后来设置width、height。
1
2
3
4
5
6
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.red,
child: xxx,
);

1.2、设置constraints方式

如果你不想/不能通过设置width、height属性的方式设置,则你可以构建一层Container或者ConstrainedBox,然后设置constraints的属性。建议两者的选择,使用ConstrainedBox会使得语意更明白。

1
2
3
4
5
6
7
8
9
10
11
12
13
return Container(
constraints: BoxConstraints(
minWidth: double.infinity,
minHeight: double.infinity,
),
color: Colors.red,
child: xxx,
);

return ConstrainedBox(
constraints: new BoxConstraints.expand(),
child: xxx,
),

默认情况下,大多数组件都会使用尽可能小的空间:

ConstrainedBox 让部件可以使用期望的剩余空间。

BoxConstraints.expand 将会让组件使用无限制(所有可用)的空间,除非另有指定。

1
2
3
BoxConstraints.tightFor(width: 80.0,height: 80.0),
// 等价于
BoxConstraints(minHeight: 80.0,maxHeight: 80.0,minWidth: 80.0,maxWidth: 80.0)

更多

2、图片的显示方式

BoxFit.fill:图显示,显示可能拉伸,充满

3、绝对布局

三、控件

Flutter 自定义组件之列表头悬浮

Flutter笔记-深入分析滑动控件

1
2
3
4
5
6
7
/* 滑动效果
* AlwaysScrollableScrollPhysics() 总是可以滑动,默认值
* NeverScrollableScrollPhysics禁止滚动
* BouncingScrollPhysics 内容超过一屏 上拉有回弹效果
* ClampingScrollPhysics 包裹内容 不会有回弹
*/
final ScrollPhysics physics;

三方控件

Flutter 中使用 video_player 播放视频

Flutter 中使用 video_player 播放视频

实践小结

[toc]

传值

前言

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,
),
)