//使用try-catch捕获同步异常
try {
throw StateError('This is a Dart exception.');
}
catch(e) {
print(e);
}
//使用catchError捕获异步异常
Future.delayed(Duration(seconds: 1))
.then((e) => throw StateError('This is a Dart exception in Future.'))
.catchError((e)=>print(e));
//注意,以下代码无法捕获异步异常
try {
Future.delayed(Duration(seconds: 1))
.then((e) => throw StateError('This is a Dart exception in Future.'))
}
catch(e) {
print("This line will never be executed. ");
}
runZoned(() {
//同步抛出异常
throw StateError('This is a Dart exception.');
}, onError: (dynamic e, StackTrace stack) {
print('Sync error caught by zone');
});
runZoned(() {
//异步抛出异常
Future.delayed(Duration(seconds: 1))
.then((e) => throw StateError('This is a Dart exception in Future.'));
}, onError: (dynamic e, StackTrace stack) {
print('Async error aught by zone');
});
runZoned<Future<Null>>(() async {
runApp(MyApp());
}, onError: (error, stackTrace) async {
//Do sth for error
});
@override
void performRebuild() {
Widget built;
try {
//创建页面
built = build();
} catch (e, stack) {
//使用ErrorWidget创建页面
built = ErrorWidget.builder(_debugReportException(ErrorDescription("building $this"), e, stack));
...
}
...
}
ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails){
return Scaffold(
body: Center(
child: Text("Custom Error Widget"),
)
);
};
FlutterError.onError = (FlutterErrorDetails details) async {
//转发至Zone中
Zone.current.handleUncaughtError(details.exception, details.stack);
};
runZoned<Future<Null>>(() async {
runApp(MyApp());
}, onError: (error, stackTrace) async {
//Do sth for error
});
class FlutterCrashPlugin {
//初始化方法通道
static const MethodChannel _channel =
const MethodChannel('flutter_crash_plugin');
static void setUp(appID) {
//使用app_id进行SDK注册
_channel.invokeMethod("setUp",{'app_id':appID});
}
static void postException(error, stack) {
//将异常和堆栈上报至Bugly
_channel.invokeMethod("postException",{'crash_message':error.toString(),'crash_detail':stack.toString()});
}
}
Pod::Spec.new do |s|
...
s.dependency 'Bugly'
end
@implementation FlutterCrashPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
//注册方法通道
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"flutter_crash_plugin"
binaryMessenger:[registrar messenger]];
//初始化插件实例,绑定方法通道
FlutterCrashPlugin* instance = [[FlutterCrashPlugin alloc] init];
//注册方法通道回调函数
[registrar addMethodCallDelegate:instance channel:channel];
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if([@"setUp" isEqualToString:call.method]) {
//Bugly SDK初始化方法
NSString *appID = call.arguments[@"app_id"];
[Bugly startWithAppId:appID];
} else if ([@"postException" isEqualToString:call.method]) {
//获取Bugly数据上报所需要的各个参数信息
NSString *message = call.arguments[@"crash_message"];
NSString *detail = call.arguments[@"crash_detail"];
NSArray *stack = [detail componentsSeparatedByString:@"\n"];
//调用Bugly数据上报接口
[Bugly reportExceptionWithCategory:4 name:message reason:stack[0] callStack:stack extraInfo:@{} terminateApp:NO];
result(@0);
}
else {
//方法未实现
result(FlutterMethodNotImplemented);
}
}
@end
dependencies {
implementation 'com.tencent.bugly:crashreport:latest.release'
implementation 'com.tencent.bugly:nativecrashreport:latest.release'
}
public class FlutterCrashPlugin implements MethodCallHandler {
//注册器,通常为MainActivity
public final Registrar registrar;
//注册插件
public static void registerWith(Registrar registrar) {
//注册方法通道
final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter_crash_plugin");
//初始化插件实例,绑定方法通道,并注册方法通道回调函数
channel.setMethodCallHandler(new FlutterCrashPlugin(registrar));
}
private FlutterCrashPlugin(Registrar registrar) {
this.registrar = registrar;
}
@Override
public void onMethodCall(MethodCall call, Result result) {
if(call.method.equals("setUp")) {
//Bugly SDK初始化方法
String appID = call.argument("app_id");
CrashReport.initCrashReport(registrar.activity().getApplicationContext(), appID, true);
result.success(0);
}
else if(call.method.equals("postException")) {
//获取Bugly数据上报所需要的各个参数信息
String message = call.argument("crash_message");
String detail = call.argument("crash_detail");
//调用Bugly数据上报接口
CrashReport.postException(4,"Flutter Exception",message,detail,null);
result.success(0);
}
else {
result.notImplemented();
}
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hangchen.flutter_crash_plugin">
<!-- 电话状态读取权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 访问网络状态权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 访问wifi状态权限 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 日志读取权限 -->
<uses-permission android:name="android.permission.READ_LOGS" />
</manifest>
defaultConfig {
ndk {
// 设置支持的SO库架构
abiFilters 'armeabi' , 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}
//res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- 网络安全配置 -->
<network-security-config>
<!-- 允许明文传输数据 -->
<domain-config cleartextTrafficPermitted="true">
<!-- 将Bugly的域名加入白名单 -->
<domain includeSubdomains="true">android.bugly.qq.com</domain>
</domain-config>
</network-security-config>
//AndroidManifest/xml
<application
...
android:networkSecurityConfig="@xml/network_security_config"
...>
</application>
dependencies:
flutter_push_plugin:
git:
url: https://github.com/cyndibaby905/39_flutter_crash_plugin
//上报数据至Bugly
Future<Null> _reportError(dynamic error, dynamic stackTrace) async {
FlutterCrashPlugin.postException(error, stackTrace);
}
Future<Null> main() async {
//注册Flutter框架的异常回调
FlutterError.onError = (FlutterErrorDetails details) async {
//转发至Zone的错误回调
Zone.current.handleUncaughtError(details.exception, details.stack);
};
//自定义错误提示页面
ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails){
return Scaffold(
body: Center(
child: Text("Custom Error Widget"),
)
);
};
//使用runZone方法将runApp的运行放置在Zone中,并提供统一的异常回调
runZoned<Future<Null>>(() async {
runApp(MyApp());
}, onError: (error, stackTrace) async {
await _reportError(error, stackTrace);
});
}
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
//由于Bugly视iOS和Android为两个独立的应用,因此需要使用不同的App ID进行初始化
if(Platform.isAndroid){
FlutterCrashPlugin.setUp('43eed8b173');
}else if(Platform.isIOS){
FlutterCrashPlugin.setUp('088aebe0d5');
}
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Crashy'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('Dart exception'),
onPressed: () {
//触发同步异常
throw StateError('This is a Dart exception.');
},
),
RaisedButton(
child: Text('async Dart exception'),
onPressed: () {
//触发异步异常
Future.delayed(Duration(seconds: 1))
.then((e) => throw StateError('This is a Dart exception in Future.'));
},
)
],
),
),
);
}
}
int exceptionCount = 0;
Future<Null> _reportError(dynamic error, dynamic stackTrace) async {
exceptionCount++; //累加异常次数
FlutterCrashPlugin.postException(error, stackTrace);
}
Future<Null> main() async {
FlutterError.onError = (FlutterErrorDetails details) async {
//将异常转发至Zone
Zone.current.handleUncaughtError(details.exception, details.stack);
};
runZoned<Future<Null>>(() async {
runApp(MyApp());
}, onError: (error, stackTrace) async {
//拦截异常
await _reportError(error, stackTrace);
});
}
int totalPV = 0;
//导航监听器
class MyObserver extends NavigatorObserver{
@override
void didPush(Route route, Route previousRoute) {
super.didPush(route, previousRoute);
totalPV++;//累加PV
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//设置路由监听
navigatorObservers: [
MyObserver(),
],
home: HomePage(),
);
}
}
double pageException() {
if(totalPV == 0) return 0;
return exceptionCount/totalPV;
}
import 'dart:ui';
var orginalCallback;
void main() {
runApp(MyApp());
//设置帧回调函数并保存原始帧回调函数
orginalCallback = window.onReportTimings;
window.onReportTimings = onReportTimings;
}
//仅缓存最近25帧绘制耗时
const maxframes = 25;
final lastFrames = List<FrameTiming>();
//基准VSync信号周期
const frameInterval = const Duration(microseconds: Duration.microsecondsPerSecond ~/ 60);
void onReportTimings(List<FrameTiming> timings) {
lastFrames.addAll(timings);
//仅保留25帧
if(lastFrames.length > maxframes) {
lastFrames.removeRange(0, lastFrames.length - maxframes);
}
//如果有原始帧回调函数,则执行
if (orginalCallback != null) {
orginalCallback(timings);
}
}
double get fps {
int sum = 0;
for (FrameTiming timing in lastFrames) {
//计算渲染耗时
int duration = timing.timestampInMicroseconds(FramePhase.rasterFinish) - timing.timestampInMicroseconds(FramePhase.buildStart);
//判断耗时是否在Vsync信号周期内
if(duration < frameInterval.inMicroseconds) {
sum += 1;
} else {
//有丢帧,向上取整
int count = (duration/frameInterval.inMicroseconds).ceil();
sum += count;
}
}
return lastFrames.length/sum * 60;
}
class MyHomePage extends StatefulWidget {
int startTime;
int endTime;
MyHomePage({Key key}) : super(key: key) {
//页面初始化时记录启动时间
startTime = DateTime.now().millisecondsSinceEpoch;
}
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
void initState() {
super.initState();
//通过帧绘制回调获取渲染完成时间
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.endTime = DateTime.now().millisecondsSinceEpoch;
int timeSpend = widget.endTime - widget.startTime;
print("Page render time:${timeSpend} ms");
});
}
...
}
flutter: Page render time:548 ms
[外链图片转存失败,源站可能有防盗Travis CI 持续交付流程示意!链机制,建议将保]上https://传(blog.csdniGg.cn/20200913VmHS144110506.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shado,text_aHRwcHM6Ly9ibG9nLmNzZG0ubmV0L0lhbmdfc3R2ZHlfZmlyc3Q=,size_16,color_FFFFFF,t_74#pic_center310)((https://img-blog.csdnimg.cn/20200913210351627.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhbmdfc3R1ZHlfZmlyc3Q=,size_16,color_FFFFFF,t_70#pic_center)]
#.travis.yml
language: dart
script:
- dart dart_sample.dart
os:
- osx
install:
- git clone https://github.com/flutter/flutter.git
- export PATH="$PATH:`pwd`/flutter/bin"
script:
- flutter doctor && flutter test
matrix:
include:
#声明Android运行环境
- os: linux
language: android
dist: trusty
licenses:
- 'android-sdk-preview-license-.+'
- 'android-sdk-license-.+'
- 'google-gdk-license-.+'
#声明需要安装的Android组件
android:
components:
- tools
- platform-tools
- build-tools-28.0.3
- android-28
- sys-img-armeabi-v7a-google_apis-28
- extra-android-m2repository
- extra-google-m2repository
- extra-google-android-support
jdk: oraclejdk8
sudo: false
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- libstdc++6
- fonts-droid
#确保sdkmanager是最新的
before_script:
- yes | sdkmanager --update
script:
- yes | flutter doctor --android-licenses
- flutter doctor && flutter -v build apk
#声明iOS的运行环境
- os: osx
language: objective-c
osx_image: xcode10.2
script:
- flutter doctor && flutter -v build ios --no-codesign
install:
- git clone https://github.com/flutter/flutter.git
- export PATH="$PATH:`pwd`/flutter/bin"
...
#声明构建需要执行的命令
script:
- yes | flutter doctor --android-licenses
- flutter doctor && flutter -v build apk
#声明部署的策略,即上传apk至github release
deploy:
provider: releases
api_key: xxxxx
file:
- build/app/outputs/apk/release/app-release.apk
skip_cleanup: true
on:
tags: true
...
...
deploy:
api_key: ${GITHUB_TOKEN}
...
...
#对发布前的构建产物进行预处理,打包成ipa
before_deploy:
- mkdir app && mkdir app/Payload
- cp -r build/ios/iphoneos/Runner.app app/Payload
- pushd app && zip -r -m app.ipa Payload && popd
#将ipa上传至github release
deploy:
provider: releases
api_key: ${GITHUB_TOKEN}
file:
- app/app.ipa
skip_cleanup: true
on:
tags: true
...
...
#对构建产物进行预处理,压缩成zip格式的组件
before_deploy:
- mkdir .ios/Outputs && mkdir .ios/Outputs/FlutterEngine
- cp FlutterEngine.podspec .ios/Outputs/
- cp -r .ios/Flutter/App.framework/ .ios/Outputs/FlutterEngine/App.framework/
- cp -r .ios/Flutter/engine/Flutter.framework/ .ios/Outputs/FlutterEngine/Flutter.framework/
- pushd .ios/Outputs && zip -r FlutterEngine.zip ./ && popd
deploy:
provider: releases
api_key: ${GITHUB_TOKEN}
file:
- .ios/Outputs/FlutterEngine.zip
skip_cleanup: true
on:
tags: true
...
Dart 代码部分。
class FlutterPluginNetwork {
...
static Future<String> doRequest(url,params) async {
//使用方法通道调用原生接口doRequest,传入URL和param两个参数
final String result = await _channel.invokeMethod('doRequest', {
"url": url,
"param": params,
});
return result;
}
}
Pod::Spec.new do |s|
...
s.dependency 'AFNetworking'
end
dependencies {
implementation "com.squareup.okhttp3:okhttp:4.2.0"
}
@implementation FlutterPluginNetworkPlugin
...
//方法通道回调
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
//响应doRequest方法调用
if ([@"doRequest" isEqualToString:call.method]) {
//取出query参数和URL
NSDictionary *arguments = call.arguments[@"param"];
NSString *url = call.arguments[@"url"];
[self doRequest:url withParams:arguments andResult:result];
} else {
//其他方法未实现
result(FlutterMethodNotImplemented);
}
}
//处理网络调用
- (void)doRequest:(NSString *)url withParams:(NSDictionary *)params andResult:(FlutterResult)result {
//初始化网络调用实例
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//定义数据序列化方式为字符串
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSMutableDictionary *newParams = [params mutableCopy];
//增加自定义参数
newParams[@"ppp"] = @"yyyy";
//发起网络调用
[manager GET:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//取出响应数据,响应Dart调用
NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
result(string);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
//通知Dart调用失败
result([FlutterError errorWithCode:@"Error" message:error.localizedDescription details:nil]);
}];
}
@end
public class FlutterPluginNetworkPlugin implements MethodCallHandler {
...
@Override
//方法通道回调
public void onMethodCall(MethodCall call, Result result) {
//响应doRequest方法调用
if (call.method.equals("doRequest")) {
//取出query参数和URL
HashMap param = call.argument("param");
String url = call.argument("url");
doRequest(url,param,result);
} else {
//其他方法未实现
result.notImplemented();
}
}
//处理网络调用
void doRequest(String url, HashMap<String, String> param, final Result result) {
//初始化网络调用实例
OkHttpClient client = new OkHttpClient();
//加工URL及query参数
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
for (String key : param.keySet()) {
String value = param.get(key);
urlBuilder.addQueryParameter(key,value);
}
//加入自定义通用参数
urlBuilder.addQueryParameter("ppp", "yyyy");
String requestUrl = urlBuilder.build().toString();
//发起网络调用
final Request request = new Request.Builder().url(requestUrl).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
//切换至主线程,通知Dart调用失败
registrar.activity().runOnUiThread(new Runnable() {
@Override
public void run() {
result.error("Error", e.toString(), null);
}
});
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
//取出响应数据
final String content = response.body().string();
//切换至主线程,响应Dart调用
registrar.activity().runOnUiThread(new Runnable() {
@Override
public void run() {
result.success(content);
}
});
}
});
}
}
flutter_plugin_network:
git:
url: https://github.com/cyndibaby905/44_flutter_plugin_network.git
RaisedButton(
child: Text("doRequest"),
//点击按钮发起网络请求,打印数据
onPressed:()=>FlutterPluginNetwork.doRequest("https://jsonplaceholder.typicode.com/posts", {'userId':'2'}).then((s)=>print('Result:$s')),
)
flutter_plugin_network=/Users/hangchen/Documents/flutter/.pub-cache/git/44_flutter_plugin_network-9b4472aa46cf20c318b088573a30bc32c6961777/
#Podfile
target 'iOSDemo' do
pod 'Flutter', :path => 'Flutter'
pod 'flutter_plugin_network', :path => 'flutter_plugin_network'
end
//AppDelegate.m:
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//初始化Flutter入口
FlutterViewController *vc = [[FlutterViewController alloc]init];
//初始化插件
[FlutterPluginNetworkPlugin registerWithRegistrar:[vc registrarForPlugin:@"FlutterPluginNetworkPlugin"]];
//设置路由标识符
[vc setInitialRoute:@"defaultRoute"];
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
}
cd android
./gradlew flutter_plugin_network:assRel
//build.gradle
dependencies {
...
implementation(name: 'flutter-debug', ext: 'aar')
implementation(name: 'flutter_plugin_network-debug', ext: 'aar')
implementation "com.squareup.okhttp3:okhttp:4.2.0"
...
}
//MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View FlutterView = Flutter.createView(this, getLifecycle(), "defaultRoute");
setContentView(FlutterView);
}
}
//build.gradle
dependencies {
...
implementation(name: 'flutter-debug', ext: 'aar')
implementation(name: 'flutter_plugin_network-debug', ext: 'aar')
implementation "com.squareup.okhttp3:okhttp:4.2.0"
...
}