移动App混合开发之Flutter篇(3)---Android工程配置

首先要保证谷歌服务畅通访问,这个是程序员基本技能.

将 Flutter module 集成到 Android 项目

首先到Flutter工程里面执行

1
2
flutter build aar

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
master_flutter git:(main) flutter build aar
Running "flutter pub get" in master_flutter... 765ms
Running Gradle task 'assembleAarDebug'...
Running Gradle task 'assembleAarDebug'... Done 63.9s
✓ Built build/host/outputs/repo.
Running Gradle task 'assembleAarProfile'...
Running Gradle task 'assembleAarProfile'... Done 49.8s
✓ Built build/host/outputs/repo.
Running Gradle task 'assembleAarRelease'...
Running Gradle task 'assembleAarRelease'... Done 45.4s
✓ Built build/host/outputs/repo.

Consuming the Module
1. Open <host>/app/build.gradle
2. Ensure you have the repositories configured, otherwise add them:

String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
repositories {
maven {
url '/Users/admin/workspace/native-mix-cross-platform/flutter/Master-Flutter/master_flutter/build/host/outputs/repo'
}
maven {
url '$storageUrl/download.flutter.io'
}
}

3. Make the host app depend on the Flutter module:

dependencies {
debugImplementation 'com.example.master_flutter:flutter_debug:1.0'
profileImplementation 'com.example.master_flutter:flutter_profile:1.0'
releaseImplementation 'com.example.master_flutter:flutter_release:1.0'
}


4. Add the `profile` build type:

android {
buildTypes {
profile {
initWith debug
}
}
}

To learn more, visit https://flutter.dev/go/build-aar

这个先别关,等下要用.

如何新建Flutter工程,请点击 移动App混合开发之Flutter篇-1-Flutter工程配置 移动App混合开发之Flutter篇-1-Flutter工程配置 构建的。

新建Android工程

app/build.gradle里面新增配置,会用到flutter build aar的内容

别搞错了 必须是Module的gradle

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
String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
repositories {
maven {
url '/Users/admin/workspace/native-mix-cross-platform/flutter/Master-Flutter/master_flutter/build/host/outputs/repo'
}
maven {
url 'https://storage.googleapis.com/download.flutter.io'
}
}
dependencies {

implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

// flutter
debugImplementation 'com.example.master_flutter:flutter_debug:1.0'
profileImplementation 'com.example.master_flutter:flutter_profile:1.0'
releaseImplementation 'com.example.master_flutter:flutter_release:1.0'

// flutter
}

Module.gradle配置

在 Android 应用中添加 Flutter 页面

AndroidManifest.xml注册activity,这是Android基础知识,建议去youtube上看一遍Android教程,虽然不写Android,但是基本常识还是要知道的.

1
2
3
4
5
6
7
8
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
/>

activity

这个时候 android:theme=”@style/LaunchTheme” 会爆红

别紧张,src/main/res/values/strings.xml里面新增一个style

1
2
3
4
5
6
7
8

<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<!-- <item name="android:windowBackground">@drawable/launch_background</item>-->
<item name="android:windowFullscreen">true</item>
</style>

style

这个时候安装官方的教程来,需要你在 src/main/res/layout/activity_main.xml新增一个按钮, 而且默认肯定是 ConstraintLayout布局,搞半天你也不知道怎么玩,更别提还要findViewById, 咱不费那劲,直接在MainActivity的onCreate的方法里面加一行代码就行,先跑起来再说嘛.

1
2
3

startActivity(FlutterActivity.createDefaultIntent(this));

可能的导包

1
2
3
4
5
6
7


import androidx.appcompat.app.AppCompatActivity;
import io.flutter.embedding.android.FlutterActivity;

import android.os.Bundle;

MainActivity

集成效果图

移动App混合开发之Flutter篇(2)---iOS工程配置

用pod集成Flutter

打开xcode新建iOS工程,然后关闭xcode
在iOS工程根目录执行

1
2
pod init
pod install --no-repo-update --verbose

以后用xcworkspace打开工程,不要用xcodeproj了

xcworkspace

Podfile 中添加下面代码

1
2
3
#这个路径指向flutter工程路径
flutter_application_path = '../Master-Flutter/master_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

在target节点下添加

1
2
install_all_flutter_pods(flutter_application_path)

Podfile

目前iOS集成Flutter已经完成,寥寥数行代码而已.

在 iOS中把Flutter工程的代码Run起来

AppDelegate.h 继承FlutterAppDelegate,增加属性FlutterEngine

1
2
3
4
5
6
7
8
9
10
#import <UIKit/UIKit.h>
@import UIKit;
@import Flutter;

@interface AppDelegate :FlutterAppDelegate // More on the FlutterAppDelegate below.
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end



AppDelegate.m 重写didFinishLaunchingWithOptions方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#import "AppDelegate.h"
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
// Runs the default Dart entrypoint with a default Flutter route.
[self.flutterEngine run];
// Used to connect plugins (only if you have plugins with iOS platform code).
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}



ViewController.m

在ViewController.m的viewDidLoad方法新增代码

导包

1
2
3
@import Flutter;
#import "ViewController.h"
#import "AppDelegate.h"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.

// Make a button to call the showFlutter function when pressed.
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self
action:@selector(showFlutter)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
button.backgroundColor = UIColor.blueColor;
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[self.view addSubview:button];
}

新增方法

1
2
3
4
5
6
7
8
- (void)showFlutter {
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
}

ViewController.m

运行即可

运行效果

移动App混合开发之Flutter篇(1)---Flutter工程配置

开新坑,来讲讲混合开发怎么搞.

目前我们公司用的weex,在weex第一个大版本就入坑了

1
2
3
4
5
6
 "version": "0.12.2",


0.12.0
Released in Jun 9, 2017
Source | Signature | Checksum

为什么敢这么激进?

1领导强力

2 iOS和Android开发水平高,能搞定一切weex搞不定的

3 web开发人员水平高

但是现在2021了,有机会还是用Flutter吧

1
2
flutter create --template module master_flutter

创建Flutter工程

创建好就完事了,就一行命令搞定,其他暂时不用管.

下面的flutter build aar在集成Android工程的时候才需要,目前把代码推到git仓库就行了

1
flutter build aar
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
master_flutter git:(main) flutter build aar
Running "flutter pub get" in master_flutter... 765ms
Running Gradle task 'assembleAarDebug'...
Running Gradle task 'assembleAarDebug'... Done 63.9s
✓ Built build/host/outputs/repo.
Running Gradle task 'assembleAarProfile'...
Running Gradle task 'assembleAarProfile'... Done 49.8s
✓ Built build/host/outputs/repo.
Running Gradle task 'assembleAarRelease'...
Running Gradle task 'assembleAarRelease'... Done 45.4s
✓ Built build/host/outputs/repo.

Consuming the Module
1. Open <host>/app/build.gradle
2. Ensure you have the repositories configured, otherwise add them:

String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
repositories {
maven {
url '/Users/admin/workspace/native-mix-cross-platform/flutter/Master-Flutter/master_flutter/build/host/outputs/repo'
}
maven {
url '$storageUrl/download.flutter.io'
}
}

3. Make the host app depend on the Flutter module:

dependencies {
debugImplementation 'com.example.master_flutter:flutter_debug:1.0'
profileImplementation 'com.example.master_flutter:flutter_profile:1.0'
releaseImplementation 'com.example.master_flutter:flutter_release:1.0'
}


4. Add the `profile` build type:

android {
buildTypes {
profile {
initWith debug
}
}
}

To learn more, visit https://flutter.dev/go/build-aar

flutter build aar

Xpath笔记

//

所有子子孙孙

1
2
3
//div

所有div
1
2
3
//div//span

所有div下面的所有span,包含所有子,孙,孙孙span

/

所有的直接第一级子元素

1
2
3
4
//div/span

所有div下面的直接子span

[]

选某个元素的属性

1
2
3
4
 //div[@class="f6 text-gray mt-2"] 


选取所有class=f6 text-gray mt-2的div

*

任意单个元素

1
2
3
4
//div/a/*

所有div,直接所有子元素a(不包含孙),直接任意子元素(不包含孙)

text()

文本元素

1
2
3
//div/p[text()="pio"]

所有div,直接子元素p(不包含孙),p标签文本是pio
1
2
3
//div[@class='f6 text-gray mt-2']//span[text()="TypeScript"]

所有class是'f6 text-gray mt-2'的div,所有后代子子孙孙span,文本是TypeScript

contains(属性,”值”)

1
2
3
//div//p[contains(text(),"忘记密码")]

所有div 所有子孙p,文本是忘记密码

startwith(属性,”属性开头值”)

1
2
3
4
//div[starts-with(text(),'忘记密码')]

所有div文本是忘记密码开头

1
2
3
4
//div[@class="f6 text-gray mt-2"]//a[starts-with(@class,"d-inline-block")]

所有div,类名是f6 text-gray mt-2,所有子孙a,类名d-inline-block开头

/@href

选取href属性

1
/a/@href

and

属性多选

1
2
3
4
5
//div[starts-with(@class,"WB_text") and @nick-name='xxx微博' ]/a[@action-type="feed_list_url" and starts-with(@suda-uatrack,"key=tblog_card")]/@href

所有的class以WB_text开头 并且 nick-name是xxx微博的div
下面的所有直接子元素(不包含孙)a a的action-type是feed_list_url 并且suda-uatrack这个属性是key=tblog_card开头
的href属性

节点关系

1
2
3
4
5
6
7
8
9
10
11

父(Parent):每个元素以及属性都有一个父

子(Children):元素节点可有零个、一个或多个子

同胞(Sibling):拥有相同的父的节点

先辈(Ancestor):某节点的父、父的父

后代(Descendant):某个节点的子,子的子

##谓语

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
放在[]中的几种查找方式写法如下:

//ul/li[1] , 表示选择 ul多个li子元素中的第一个

//ul/li[last()], 表示选择ul子元素中的最后一个li元素

//ul/li[last()-1], 表示选择ul子元素中的倒数第二个li元素

//ul/li[position()<3],表示选取最前面2个属于ul元素的li子元素

//a[@href] ,表示选取只要含有href属性的a元素

//a[@href='http://www.cnblogs.com/jenniferhuang'] ,属性值完全匹配

//input[contains(@id,'quantityTextBox')], 当属性值部分匹配时,插入函数的写法

flutter相关命令

查看所有连接的设备

1
flutter devices

查看所有模拟器,并运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
flutter emulators
2 available emulators:

apple_ios_simulator • iOS Simulator • Apple • ios
Nexus_5X_API_27 • Nexus 5X API 27 • Google • android

To run an emulator, run 'flutter emulators --launch <emulator id>'.
To create a new emulator, run 'flutter emulators --create [--name xyz]'.


flutter emulators --launch Nexus_5X_API_27

flutter run

iOS命令行打开指定模拟器

有时候不想打开xcode来启动iOS模拟器,可以直接通过命令行打开.

1
open -a Simulator

但是有个弊端,这样会打开默认的,我的电脑上默认的是iPhone SE…. 如果打开iPhone 11 Pro Max呢?

1
xcrun simctl list

列出所有的设备

这个命令会列出所有设备,或者

1
xcrun instruments -s

列出所有的设备

获得你想启动的模拟器ID,然后执行

1
2
3
xcrun simctl boot EBC41BE9-6F4D-48F6-B872-C344504CAD63

open -a Simulator

well down!

iOS原生工程集成flutter

集成到已有App官方文档

    • 初始化flutter module
1
2
cd some/path/
flutter create --template module my_flutter

如图所示

初始化的flutter目录结构

    • 集成到CocoaPods
1
2
3
4
5
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'MyApp' do
install_all_flutter_pods(flutter_application_path)
end

podfile文件

    • 执行 pod install

至此,准备工作已经完成,大概三分钟就搞定.然后就可以开心的用flutter写代码了,一次编写两端运行,测试人员笑的合不拢嘴.

用Android studio打开刚才的my_flutter工程,或者用Xcode打开my_flutter文件夹下隐藏目录.ios下的Runner.xcworkspace,当场运行起来,立竿见影.

Android studio开发flutter

    • 把flutter写的页面,集成到iOS工程里,作为一个iOS页面.

    AppDelegate.h:

1
2
3
4
5
6
7
@import UIKit;
@import Flutter;

@interface AppDelegate : FlutterAppDelegate // More on the FlutterAppDelegate below.
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end

AppDelegate.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Used to connect plugins (only if you have plugins with iOS platform code).
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>

#import "AppDelegate.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
// Runs the default Dart entrypoint with a default Flutter route.
[self.flutterEngine run];
// Used to connect plugins (only if you have plugins with iOS platform code).
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

    • 随意找个页面,点击某个按钮,push到flutter页面
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
@import Flutter;
#import "AppDelegate.h"
#import "ViewController.h"

@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];

// Make a button to call the showFlutter function when pressed.
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self
action:@selector(showFlutter)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
button.backgroundColor = UIColor.blueColor;
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[self.view addSubview:button];
}

- (void)showFlutter {
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
}
@end

iOS页面push到flutter页面

iOS自动化打包上传App Store

最近iOS打包上传的任务比较多,有时候甚至每天多达近20次提交审核.这种无脑,机械,频繁,耗时的任务最适合搞成自动化来简化工作量.

所需工具如下

Xcodeproj

用来切换测试和生产证书

fastlane

打包上传TestFlight和App Store

fir

测试环境上传到fir,会有链接和二维码可以让测试直接扫码安装.

用Xcodeproj自动修改证书

    1. Installing Xcodeproj
1
[sudo] gem install xcodeproj
    1. 工程根目录新建.rb文件

执行ruby xxx.rb 将会自动修改证书为com.xxx.yyy xxx_appStore,省的每次都要从测试证书修改为生产证书,至少能节省10秒钟的时间.

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
require 'xcodeproj'
project_path = './xxx.xcodeproj' # 工程的全路径
project = Xcodeproj::Project.open(project_path)
puts 'ruby开始修改证书id和描述文件...'

project.targets.each do |target|
target.build_configurations.each do |config|
# 修改描述文件的id,值为id而不是名称
config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Distribution'
config.build_settings['CODE_SIGN_IDENTITY[sdk=iphoneos*]'] = 'Apple Distribution'
config.build_settings['CODE_SIGN_STYLE'] = 'Manual'

config.build_settings['DEVELOPMENT_TEAM'] = '4LJHGGLLHHB'

config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = 'com.xxx.yyy'
config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = 'xxx_appStore'
end
end

project.save
puts '修改完成...'

project.targets.each do |target|
target.build_configurations.each do |config|
puts '修改之后描述文件的id'
puts config.build_settings['PROVISIONING_PROFILE']

# puts '修改之后证书签名标识'
puts config.build_settings['CODE_SIGN_IDENTITY']

# 工程的标识
puts '修改之后工程的id'
puts config.build_settings['PRODUCT_BUNDLE_IDENTIFIER']
end
end

用fastlane自动打包上传App Store

证书修改好了,可以在xcode里面点product-archive,经过漫长的等待,然后下一步下一步.耗费了青春,掉落了头发.一个有追求的程序员,怎么能忍受这么无聊的事情.

上大招 fastlane 直达文档

1
2
3
4
5
6
7
xcode-select --install

sudo gem install fastlane -NV

fastlane init

fastlane release

有图有真相,我装完是这样,你装完也是这样.

fastlane整体截图
fastfile截图
直接 fastlane release 就完事了

上传到fir用来内测试

fir官网

fir.im fastlane 插件

github教程

完事之后就像这样

fastlane+App Store+fir

直接fastlane beta就完事了

用markdown记录工作中不机密的内容

标题 (一至六个 # 符号)

1
2
3
4
5
# 最大标题

## 第二大标题

###### 最小标题

样式文本

1
2
3
**这是粗体文本**

*这是斜体文本*

图片引用

1
2
![空山新雨后](http://johnnie-198312.github.io/images/3d3e558f5a3f7119fc9b2ddf5c037b69.jpg)

空山新雨后

标记

1
`ctrl+a`

ctrl+a

引用文本

用 Abraham Lincoln 的话来说:

原谅我爆粗口

1
2
3
用 Abraham Lincoln 的话来说:

> 原谅我爆粗口

引用代码 三反引号 ``` 包裹起来

1
2
3
4
5
require 'redcarpet'
markdown = Redcarpet.new("Hello World!")
puts markdown.to_html
puts markdown.to_html

链接

1
2
本站点是使用 [GitHub Pages](https://pages.github.com/) 构建的。

点我,我是超链接

列表

1
2
3
- George Washington
- John Adams
- Thomas Jefferson
  • George Washington
  • John Adams
  • Thomas Jefferson
1
2
3
1. James Madison
2. James Monroe
3. John Quincy Adams
  1. James Madison
  2. James Monroe
  3. John Quincy Adams

段落

通过在文本行之间留一个空白行,可创建新段落。

忽略 Markdown 格式

通过在 Markdown 字符前面输入 \,可告诉 GitHub 忽略(或规避)Markdown 格式。

1
2
让我们将 \*our-new-project\* 重命名为 \*our-old-project\*。

让我们将 *our-new-project* 重命名为 *our-old-project*。