Commit 48bab908 authored by 李增强's avatar 李增强

Initial commit

parents
.DS_Store
.dart_tool/
.packages
.pub/
pubspec.lock
build/
## 0.3.0+2
* 修复无限检查更新的问题
## 0.3.0
* 使用MethodCallHandler获取upgradeInfo
* 新增checkUpgradeCount参数
* checkUpgrade不再返回UpgradeInfo,isManual默认改为true,删除useCache参数
## 0.2.9
* nativecrashreport SDK升级到3.7.5
* 升级Android embedding v2
* 修改FileProvider与第三库冲突 [@linyf0721](https://github.com/linyf0721)
## 0.2.8
* android应用更新SDK升级到1.4.2
## 0.2.7
* 异常上报新增debugUpload字段,默认false
* 异常上报新增uploadException方法
* 新增setAppChannel方法
## 0.2.6
* android:networkSecurityConfig改为android:usesCleartextTraffic
## 0.2.5
* 优化iOS和android异常上报控制台排版格式
## 0.2.4+1
* 修复自动适配模式时,debug时网络请求会上报的问题
## 0.2.4
* 自动适配debug、release模式。debug下只打印异常不上报,release只上报,不打印
* 混淆合并,自带flutter和bugly相关混淆规则
## 0.2.3+1
* 修复useCache取值不正确的问题
## 0.2.3
* 优化checkUpgrade方法,等待网络请求更新策略完成后再返回UpgradeInfo(注意点见方法注释)
* 升级com.tencent.bugly:crashreport_upgrade:1.4.1
## 0.2.2
* 新增初始化channel参数,见注释
* 新增setUserTag、putUserData方法,见注释
## 0.2.1+1
* 新增setUserId方法,用于crash用户标识
## 0.2.0+1
* 优化UpgradeInfo获取机制,优先获取网络策略,网络策略没有来得及拉取时,取本地策略
## 0.2.0
* 优化UpgradeInfo获取机制
## 0.1.9+2
* 解决The 'Pods-Runner' target has transitive dependencies that include static binaries
## 0.1.9+1
* 获取更新信息增加非空判断
## 0.1.9
* 解决Android 9.0上联网失败的问题
## 0.1.8
* 增加异常上报过滤
## 0.1.7
* 优化格式化异常信息上报时的逻辑
## 0.1.6+2
* 修改iOS为返回InitResultInfo实体
## 0.1.6+1
* 删除.idea及优化逻辑严谨性
## 0.1.6
* sdk自带6.0以上动态权限,删除插件中的权限动态申请
* 修改为返回InitResultInfo实体
## 0.1.5+1
* 升级 crashreport_upgrade:1.3.7,适配8.0通知栏,以及androidx的应用升级弹窗说明
## 0.1.4+2
* fix crash when FileProvider not find
## 0.1.4+1
* migrate to androidx
Copyright (c) <2019> <21527312@qq.com>
"Anti 996" License Version 1.0 (Draft)
Permission is hereby granted to any individual or legal entity
obtaining a copy of this licensed work (including the source code,
documentation and/or related items, hereinafter collectively referred
to as the "licensed work"), free of charge, to deal with the licensed
work for any purpose, including without limitation, the rights to use,
reproduce, modify, prepare derivative works of, distribute, publish
and sublicense the licensed work, subject to the following conditions:
1. The individual or the legal entity must conspicuously display,
without modification, this License and the notice on each redistributed
or derivative copy of the Licensed Work.
2. The individual or the legal entity must strictly comply with all
applicable laws, regulations, rules and standards of the jurisdiction
relating to labor and employment where the individual is physically
located or where the individual was born or naturalized; or where the
legal entity is registered or is operating (whichever is stricter). In
case that the jurisdiction has no such laws, regulations, rules and
standards or its laws, regulations, rules and standards are
unenforceable, the individual or the legal entity are required to
comply with Core International Labor Standards.
3. The individual or the legal entity shall not induce, metaphor or force
its employee(s), whether full-time or part-time, or its independent
contractor(s), in any methods, to agree in oral or written form, to
directly or indirectly restrict, weaken or relinquish his or her
rights or remedies under such laws, regulations, rules and standards
relating to labor and employment as mentioned above, no matter whether
such written or oral agreement are enforceable under the laws of the
said jurisdiction, nor shall such individual or the legal entity
limit, in any methods, the rights of its employee(s) or independent
contractor(s) from reporting or complaining to the copyright holder or
relevant authorities monitoring the compliance of the license about
its violation(s) of the said license.
THE LICENSED WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN ANY WAY CONNECTION WITH THE
LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE LICENSED WORK.
\ No newline at end of file
# flutter_bugly
[![pub package](https://img.shields.io/pub/v/flutter_bugly.svg)](https://pub.dartlang.org/packages/flutter_bugly)
[![Gitter](https://badges.gitter.im/flutter_developer/community.svg)](https://gitter.im/flutter_developer/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
腾讯Bugly flutter应用更新插件
## 支持Android/iOS 运营统计、原生异常上报、flutter异常上报、应用更新
---
一、引入
--
```yaml
//因为大部分主流插件都已升级androidx,所以pub库升级androidx
//版本更新弹窗问题见下面说明
//androidx
dependencies:
flutter_bugly: lastVersion
//support
dependencies:
flutter_bugly:
git:
url: git://github.com/crazecoder/flutter_bugly.git
ref: dev
```
二、项目配置
---
在android/app/build.gradle的android下加入
```gradle
lintOptions {
//如打包出现Failed to transform libs.jar to match attributes
checkReleaseBuilds false
}
defaultConfig {
ndk {
//设置支持的SO库架构
abiFilters 'armeabi-v7a'//, 'arm64-v8a', 'x86', 'x86_64'
}
}
```
三、使用
----
```dart
import 'package:flutter_bugly/flutter_bugly.dart';
//使用flutter异常上报
void main()=>FlutterBugly.postCatchedException((){
runApp(MyApp());
});
FlutterBugly.init(androidAppId: "your android app id",iOSAppId: "your iOS app id");
```
四、release打包(Android)
-----
64-bit
```
flutter build apk --release --target-platform android-arm64
```
32-bit(目前配合armeabi-v7a可以打出32位64位通用包)
```
flutter build apk --release --target-platform android-arm
```
五、支持属性(Android)
-----
```dart
String channel, //自定义渠道标识
bool autoCheckUpgrade = true,//自动检查更新开关
bool autoInit = true,//自动初始化
bool autoDownloadOnWifi = false,//设置Wifi下自动下载
bool enableNotification = false,//通知栏
bool showInterruptedStrategy = true, //设置开启显示打断策略
bool canShowApkInfo = true, //设置是否显示弹窗中的apk信息
int initDelay = 0, //延迟初始化,单位秒
int upgradeCheckPeriod = 0, //升级检查周期设置,单位秒
//手动检查更新
checkUpgrade({
bool isManual = false,//用户手动点击检查,非用户点击操作请传false
bool isSilence = false,//是否显示弹窗等交互,[true:没有弹窗和toast] [false:有弹窗或toast]
})
FlutterBugly.setUserId("user id");
FlutterBugly.putUserData(key: "key", value: "value");
int tag = 9527;
FlutterBugly.setUserTag(tag);
```
六、自定义弹窗(Android)
------
通过FlutterBugly.getUpgradeInfo()获取更新策略信息填入自定义flutter widget,手动弹窗
UpgradeInfo参数:
```java
String id = "";//唯一标识
String title = "";//升级提示标题
String newFeature = "";//升级特性描述
long publishTime = 0;//升级发布时间,ms
int publishType = 0;//升级类型 0测试 1正式
int upgradeType = 1;//升级策略 1建议 2强制 3手工
int popTimes = 0;//提醒次数
long popInterval = 0;//提醒间隔
int versionCode;
String versionName = "";
String apkMd5;//包md5值
String apkUrl;//APK的CDN外网下载地址
long fileSize;//APK文件的大小
String imageUrl; // 图片url
```
七、说明(Android)
-------
异常上报说明
1、flutter异常上报不属于崩溃,所以如需查看flutter的异常上报,请在【错误分析】tab页查看
![](https://github.com/crazecoder/flutter_bugly/blob/1ff1928b3215a8fa1c8fb99c3071692da322e278/screenshot/crash.png)
2、iOS的异常上报没有过多测试,如出现问题请issue
目前已知问题
~~1、第一次接受到更新策略之后,不会弹窗,即使手动检查更新也不会,需要退出app之后再进入,才会有弹窗(已解决)~~
~~2、官方没有适配8.0的notification,所以如果需要用到notification的时候请关闭后(默认关闭),自己写相关业务逻辑,或者直接把gradle里的targetSdkVersion设成26以下(方法见示例)~~ 官方已适配
~~3、请勿在targetSdkVersion 26以上设置autoDownloadOnWifi = true,会导致在8.0以上机型更新策略没有反应~~
4、因为版本更新弹窗封装进sdk,使用的是support包,所以使用androidx包时,请配合FlutterBugly.getUpgradeInfo()或者FlutterBugly.checkUpgrade()【两种方法区别见方法注释】方法自定义弹窗界面 [弹窗示例](https://github.com/crazecoder/flutter_bugly/commit/6052890cee63ec1e433501e1149852878fd234de)或者[有下载打开安装的完整示例](https://github.com/crazecoder/testsocket/blob/master/lib/ui/home.dart)
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
group 'com.crazecoder.flutterbugly'
version '1.0-SNAPSHOT'
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.2'
}
}
rootProject.allprojects {
repositories {
google()
jcenter()
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'proguard-rules.pro'
}
lintOptions {
disable 'InvalidPackage'
}
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
//注释掉原有bugly的仓库
//compile 'com.tencent.bugly:crashreport:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.3.2
implementation 'com.tencent.bugly:crashreport_upgrade:1.4.2'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如1.2.0
implementation 'com.tencent.bugly:nativecrashreport:3.7.5' //其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.2.0
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
}
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
\ No newline at end of file
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
##########flutter相关不混淆############
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
###########bugly##############
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
-keep class com.crazecoder.flutterbugly.bean.** { *; }
\ No newline at end of file
rootProject.name = 'flutter_bugly'
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.crazecoder.flutterbugly">
<application
android:usesCleartextTraffic="true"
tools:targetApi="n">
<provider
android:name="com.tencent.bugly.beta.utils.BuglyFileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>
\ No newline at end of file
package com.crazecoder.flutterbugly;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.crazecoder.flutterbugly.bean.BuglyInitResultInfo;
import com.crazecoder.flutterbugly.utils.JsonUtil;
import com.crazecoder.flutterbugly.utils.MapUtil;
import com.tencent.bugly.Bugly;
import com.tencent.bugly.beta.Beta;
import com.tencent.bugly.beta.UpgradeInfo;
import com.tencent.bugly.beta.upgrade.UpgradeListener;
import com.tencent.bugly.crashreport.CrashReport;
import java.util.HashMap;
import java.util.Map;
import io.flutter.BuildConfig;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
/**
* FlutterBuglyPlugin
*/
public class FlutterBuglyPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {
private Result result;
private boolean isResultSubmitted = false;
private static MethodChannel channel;
@SuppressLint("StaticFieldLeak")
private static Activity mActivity;
private FlutterPluginBinding flutterPluginBinding;
/**
* Plugin registration.
*/
public static void registerWith(Registrar registrar) {
channel = new MethodChannel(registrar.messenger(), "crazecoder/flutter_bugly");
FlutterBuglyPlugin plugin = new FlutterBuglyPlugin();
channel.setMethodCallHandler(plugin);
mActivity = registrar.activity();
}
@Override
public void onMethodCall(final MethodCall call, final Result result) {
isResultSubmitted = false;
this.result = result;
if (call.method.equals("initBugly")) {
if (call.hasArgument("appId")) {
if (call.hasArgument("autoInit")) {
Beta.autoInit = false;
}
if (call.hasArgument("enableHotfix")) {
Beta.enableHotfix = call.argument("enableHotfix");
}
if (call.hasArgument("autoCheckUpgrade")) {
Beta.autoCheckUpgrade = call.argument("autoCheckUpgrade");
}
if (call.hasArgument("autoDownloadOnWifi")) {
Beta.autoDownloadOnWifi = call.argument("autoDownloadOnWifi");
}
if (call.hasArgument("initDelay")) {
int delay = call.argument("initDelay");
Beta.initDelay = delay * 1000;
}
if (call.hasArgument("enableNotification")) {
Beta.enableNotification = call.argument("enableNotification");
}
if (call.hasArgument("upgradeCheckPeriod")) {
int period = call.argument("upgradeCheckPeriod");
Beta.upgradeCheckPeriod = period * 1000;
}
if (call.hasArgument("showInterruptedStrategy")) {
Beta.showInterruptedStrategy = call.argument("showInterruptedStrategy");
}
if (call.hasArgument("canShowApkInfo")) {
Beta.canShowApkInfo = call.argument("canShowApkInfo");
}
Beta.canShowUpgradeActs.add(mActivity.getClass());
/*在application中初始化时设置监听,监听策略的收取*/
Beta.upgradeListener = new UpgradeListener() {
@Override
public void onUpgrade(int ret, UpgradeInfo strategy, boolean isManual, boolean isSilence) {
Map<String, Object> data = new HashMap<>();
data.put("upgradeInfo", JsonUtil.toJson(MapUtil.deepToMap(strategy)));
channel.invokeMethod("onCheckUpgrade", data);
}
};
String appId = call.argument("appId").toString();
Bugly.init(mActivity.getApplicationContext(), appId, BuildConfig.DEBUG);
if (call.hasArgument("channel")) {
String channel = call.argument("channel");
if (!TextUtils.isEmpty(channel))
Bugly.setAppChannel(mActivity.getApplicationContext(), channel);
}
result(getResultBean(true, appId, "Bugly 初始化成功"));
} else {
result(getResultBean(false, null, "Bugly appId不能为空"));
}
} else if (call.method.equals("setUserId")) {
if (call.hasArgument("userId")) {
String userId = call.argument("userId");
Bugly.setUserId(mActivity.getApplicationContext(), userId);
}
result(null);
} else if (call.method.equals("setUserTag")) {
if (call.hasArgument("userTag")) {
Integer userTag = call.argument("userTag");
if (userTag != null)
Bugly.setUserTag(mActivity.getApplicationContext(), userTag);
}
result(null);
} else if (call.method.equals("putUserData")) {
if (call.hasArgument("key") && call.hasArgument("value")) {
String userDataKey = call.argument("key");
String userDataValue = call.argument("value");
Bugly.putUserData(mActivity.getApplicationContext(), userDataKey, userDataValue);
}
result(null);
} else if (call.method.equals("checkUpgrade")) {
boolean isManual = false;
boolean isSilence = false;
if (call.hasArgument("isManual")) {
isManual = call.argument("isManual");
}
if (call.hasArgument("isSilence")) {
isSilence = call.argument("isSilence");
}
Beta.checkUpgrade(isManual, isSilence);
} else if (call.method.equals("getUpgradeInfo")) {
UpgradeInfo strategy = Beta.getUpgradeInfo();
result(strategy);
} else if (call.method.equals("postCatchedException")) {
postException(call);
result(null);
} else {
result.notImplemented();
isResultSubmitted = true;
}
}
private void postException(MethodCall call) {
String message = "";
String detail = null;
if (call.hasArgument("crash_message")) {
message = call.argument("crash_message");
}
if (call.hasArgument("crash_detail")) {
detail = call.argument("crash_detail");
}
if (TextUtils.isEmpty(detail)) return;
CrashReport.postException(8, "Flutter Exception", message, detail, null);
}
private void result(Object object) {
if (result != null && !isResultSubmitted) {
if (object == null) {
result.success(null);
} else {
result.success(JsonUtil.toJson(MapUtil.deepToMap(object)));
}
isResultSubmitted = true;
}
}
private BuglyInitResultInfo getResultBean(boolean isSuccess, String appId, String msg) {
BuglyInitResultInfo bean = new BuglyInitResultInfo();
bean.setSuccess(isSuccess);
bean.setAppId(appId);
bean.setMessage(msg);
return bean;
}
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
this.flutterPluginBinding = binding;
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
}
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
if (mActivity != null) {
return;
}
mActivity = binding.getActivity();
channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "crazecoder/flutter_bugly");
channel.setMethodCallHandler(this);
mActivity.getApplication().registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
mActivity = activity;
}
@Override
public void onActivityStarted(Activity activity) {
mActivity = activity;
}
@Override
public void onActivityResumed(Activity activity) {
mActivity = activity;
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
@Override
public void onDetachedFromActivityForConfigChanges() {
}
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
}
@Override
public void onDetachedFromActivity() {
flutterPluginBinding = null;
}
}
\ No newline at end of file
package com.crazecoder.flutterbugly.bean;
/**
* Note of this class.
*
* @author crazecoder
* @since 2019/3/4
*/
public class BuglyInitResultInfo {
private String message;
private String appId;
private boolean isSuccess;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
}
package com.crazecoder.flutterbugly.callback;
import com.tencent.bugly.beta.UpgradeInfo;
public interface UpgradeCallback {
void onUpgrade(UpgradeInfo strategy);
}
package com.crazecoder.flutterbugly.utils;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Map;
/**
* Note of this class.
*
* @author crazecoder
* @since 2018/12/28
*/
public class JsonUtil {
public static String toJson(Map<String, Object> map) {
JSONObject jsonObject = new JSONObject();
try {
for (Map.Entry<String, Object> entry : map.entrySet()) {
jsonObject.put(entry.getKey(), entry.getValue());
}
} catch (JSONException e) {
e.printStackTrace();
}
return jsonObject.toString();
}
}
package com.crazecoder.flutterbugly.utils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class MapUtil {
public static Map<String, Object> deepToMap(Object bean) {
Map<String, Object> map = new LinkedHashMap<>();
try {
putValues(bean, map, null);
} catch (IllegalAccessException x) {
throw new IllegalArgumentException(x);
}
return map;
}
private static void putValues(Object bean,
Map<String, Object> map,
String prefix)
throws IllegalAccessException {
if (bean == null) return;
Class<?> cls = bean.getClass();
for (Field field : cls.getDeclaredFields()) {
if (field.isSynthetic() || Modifier.isStatic(field.getModifiers()))
continue;
field.setAccessible(true);
Object value = field.get(bean);
String key;
if (prefix == null) {
key = field.getName();
} else {
key = prefix + "." + field.getName();
}
if (isValue(value)) {
map.put(key, value);
} else {
putValues(value, map, key);
}
}
}
private static final Set<Class<?>> VALUE_CLASSES =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
Object.class, String.class, Boolean.class,
Character.class, Byte.class, Short.class,
Integer.class, Long.class, Float.class,
Double.class
// etc.
)));
private static boolean isValue(Object value) {
return value == null
|| value instanceof Enum<?>
|| VALUE_CLASSES.contains(value.getClass());
}
}
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false" />
<domain-config cleartextTrafficPermitted="true">
<!--for bugly sdk-->
<domain includeSubdomains="true">android.bugly.qq.com</domain>
</domain-config>
</network-security-config>
\ No newline at end of file
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_mintegral","path":"C:\\\\Users\\\\qiaomeng\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\git\\\\flutter_mintegral-1f956b73ac9183798f780538f3fb2521dbce502f\\\\","dependencies":[]},{"name":"flutter_bugly","path":"D:\\\\android_project\\\\flutter_bugly-0.3.0+2\\\\","dependencies":[]}],"android":[{"name":"flutter_mintegral","path":"C:\\\\Users\\\\qiaomeng\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\git\\\\flutter_mintegral-1f956b73ac9183798f780538f3fb2521dbce502f\\\\","dependencies":[]},{"name":"flutter_bugly","path":"D:\\\\android_project\\\\flutter_bugly-0.3.0+2\\\\","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"flutter_mintegral","dependencies":[]},{"name":"flutter_bugly","dependencies":[]}],"date_created":"2021-06-02 19:20:59.012932","version":"1.22.4"}
\ No newline at end of file
# Miscellaneous
*.class
*.lock
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# Visual Studio Code related
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b
channel: stable
project_type: app
# flutter_bugly_example
Demonstrates how to use the flutter_bugly plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.io/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
lintOptions {
disable 'InvalidPackage'
//如打包出现Failed to transform libs.jar to match attributes
checkReleaseBuilds false
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.crazecoder.flutterbuglyexample"
minSdkVersion 16
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
//设置支持的SO库架构
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
buildTypes {
release {
minifyEnabled true
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
debug {
minifyEnabled false
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#指定代码的压缩级别
-optimizationpasses 5
#包明不混合大小写
-dontusemixedcaseclassnames
#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses
#优化 不优化输入的类文件
-dontoptimize
#预校验
-dontpreverify
# 混淆时所采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#保护注解
-keepattributes *annotation*
-keep class * extends java.lang.annotation.Annotation { *; }
-keep interface * extends java.lang.annotation.Annotation { *; }
-dontwarn javax.annotation.**
-keep class javax.annotation.** { *; }
# -------------系统类不需要混淆 --------------------------
#-keep public class * extends android.app.Application
#-keep public class * extends android.app.Service
#-keep public class * extends android.content.BroadcastReceiver
#-keep public class * extends android.content.ContentProvider
#-keep public class * extends android.app.backup.BackupAgentHelper
#-keep public class * extends android.preference.Preference
-keep class com.google.android.material.** {*;}
-keep class androidx.** {*;}
-keep public class * extends androidx.**
-keep interface androidx.** {*;}
-dontwarn com.google.android.material.**
-dontnote com.google.android.material.**
-dontwarn androidx.**
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#反射相关的类
-keepclasseswithmembernames class android.support.design.widget.** {
*;
}
\ No newline at end of file
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.crazecoder.flutterbuglyexample"
>
<!-- The INTERNET permission is required for development. Specifically,
flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:label="flutter_bugly_example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
package com.crazecoder.flutterbuglyexample;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<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>
</style>
</resources>
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.2'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
#Wed Jul 22 04:26:10 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
#
# NOTE: This podspec is NOT to be published. It is only used as a local source!
#
Pod::Spec.new do |s|
s.name = 'Flutter'
s.version = '1.0.0'
s.summary = 'High-performance, high-fidelity mobile apps.'
s.description = <<-DESC
Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS.
DESC
s.homepage = 'https://flutter.io'
s.license = { :type => 'MIT' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
s.ios.deployment_target = '8.0'
s.vendored_frameworks = 'Flutter.framework'
end
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=D:\dev\flutter"
export "FLUTTER_APPLICATION_PATH=D:\android_project\flutter_bugly-0.3.0+2\example"
export "FLUTTER_TARGET=lib\main.dart"
export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build\ios"
export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
export "FLUTTER_FRAMEWORK_DIR=D:\dev\flutter\bin\cache\artifacts\engine\ios"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=false"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.packages"
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
pods_ary = []
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) { |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
pods_ary.push({:name => podname, :path => podpath});
else
puts "Invalid plugin specification: #{line}"
end
}
return pods_ary
end
target 'Runner' do
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
# Flutter Pods
generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
if generated_xcode_build_settings.empty?
puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
end
generated_xcode_build_settings.map { |p|
if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
symlink = File.join('.symlinks', 'flutter')
File.symlink(File.dirname(p[:path]), symlink)
pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
end
}
# Plugin Pods
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.map { |p|
symlink = File.join('.symlinks', 'plugins', p[:name])
File.symlink(p[:path], symlink)
pod p[:name], :path => File.join(symlink, 'ios')
}
pod 'Bugly'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0910"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Original</string>
</dict>
</plist>
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>flutter_bugly_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bugly/flutter_bugly.dart';
import 'update_dialog.dart';
void main() => FlutterBugly.postCatchedException(
() => runApp(MyApp()),
);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String _platformVersion = 'Unknown';
GlobalKey<UpdateDialogState> _dialogKey = new GlobalKey();
@override
void initState() {
super.initState();
FlutterBugly.init(
androidAppId: "your app id",
iOSAppId: "your app id",
).then((_result) {
setState(() {
_platformVersion = _result.message;
print(_result.appId);
});
});
FlutterBugly.onCheckUpgrade.listen((_upgradeInfo) {
_showUpdateDialog(_upgradeInfo.newFeature, _upgradeInfo.apkUrl,
_upgradeInfo.upgradeType == 2);
});
FlutterBugly.setUserId("user id");
FlutterBugly.putUserData(key: "key", value: "value");
int tag = 9527;
FlutterBugly.setUserTag(tag);
//autoCheckUpgrade为true时,可以不用调用
// if (mounted) _checkUpgrade();
}
@override
void dispose() {
FlutterBugly.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: GestureDetector(
onTap: () {
if (Platform.isAndroid) {
_checkUpgrade();
}
},
child: Center(
child: Text('init result: $_platformVersion\n'),
),
),
);
}
void _showUpdateDialog(String version, String url, bool isForceUpgrade) {
showDialog(
barrierDismissible: false,
context: context,
builder: (_) => _buildDialog(version, url, isForceUpgrade),
);
}
void _checkUpgrade() {
print("获取更新中。。。");
FlutterBugly.checkUpgrade();
}
Widget _buildDialog(String version, String url, bool isForceUpgrade) {
return WillPopScope(
onWillPop: () async => isForceUpgrade,
child: UpdateDialog(
key: _dialogKey,
version: version,
onClickWhenDownload: (_msg) {
//提示不要重复下载
},
onClickWhenNotDownload: () {
//下载apk,完成后打开apk文件,建议使用dio+open_file插件
},
));
}
//dio可以监听下载进度,调用此方法
void _updateProgress(_progress) {
setState(() {
_dialogKey.currentState.progress = _progress;
});
}
}
import 'package:flutter/material.dart';
class UpdateDialog extends StatefulWidget {
final key;
final version;
final Function onClickWhenDownload;
final Function onClickWhenNotDownload;
UpdateDialog({
this.key,
this.version,
this.onClickWhenDownload,
this.onClickWhenNotDownload,
});
@override
State<StatefulWidget> createState() => new UpdateDialogState();
}
class UpdateDialogState extends State<UpdateDialog> {
var _downloadProgress = 0.0;
@override
Widget build(BuildContext context) {
var _textStyle =
new TextStyle(color: Theme.of(context).textTheme.body1.color);
return new AlertDialog(
title: new Text(
"有新的更新",
style: _textStyle,
),
content: _downloadProgress == 0.0
? new Text(
"版本${widget.version}",
style: _textStyle,
)
: new LinearProgressIndicator(
value: _downloadProgress,
),
actions: <Widget>[
new FlatButton(
child: new Text(
'更新',
style: _textStyle,
),
onPressed: () {
if (_downloadProgress != 0.0) {
widget.onClickWhenDownload("正在更新中");
return;
}
widget.onClickWhenNotDownload();
// Navigator.of(context).pop();
},
),
new FlatButton(
child: new Text('取消'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
}
set progress(_progress) {
setState(() {
_downloadProgress = _progress;
if (_downloadProgress == 1) {
Navigator.of(context).pop();
_downloadProgress = 0.0;
}
});
}
}
name: flutter_bugly_example
description: Demonstrates how to use the flutter_bugly plugin.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_mintegral:
git:
url: https://gitee.com/crazecoder/flutter_mintegral.git
flutter_bugly:
path: ../
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.io/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.io/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.io/custom-fonts/#from-packages
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
//import 'package:flutter_test/flutter_test.dart';
//import 'package:flutter_bugly_example/main.dart';
void main() {
// testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
// await tester.pumpWidget(MyApp());
//
// // Verify that platform version is retrieved.
// expect(
// find.byWidgetPredicate(
// (Widget widget) => widget is Text &&
// widget.data.startsWith('Running on:'),
// ),
// findsOneWidget,
// );
// });
}
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/build" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_bugly/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_bugly/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_bugly/build" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_bugly/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_bugly/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_bugly/example/build" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/Flutter/flutter_assets/packages" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
<orderEntry type="library" name="Flutter for Android" level="project" />
</component>
</module>
\ No newline at end of file
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
#import <Flutter/Flutter.h>
@interface FlutterBuglyPlugin : NSObject<FlutterPlugin>
@end
#import "FlutterBuglyPlugin.h"
#import <Bugly/Bugly.h>
@implementation FlutterBuglyPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"crazecoder/flutter_bugly"
binaryMessenger:[registrar messenger]];
FlutterBuglyPlugin* instance = [[FlutterBuglyPlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"initBugly" isEqualToString:call.method]) {
NSString *appId = call.arguments[@"appId"];
BOOL b = [self isBlankString:appId];
if(!b){
BuglyConfig * config = [[BuglyConfig alloc] init];
NSString *channel = call.arguments[@"channel"];
BOOL isChannelEmpty = [self isBlankString:channel];
if(!isChannelEmpty){
config.channel = channel;
}
[Bugly startWithAppId:appId config:config];
NSLog(@"Bugly appId: %@", appId);
NSDictionary * dict = @{@"message":@"Bugly 初始化成功",@"appId":appId, @"isSuccess":@YES};
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
NSString * json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
result(json);
}else{
NSDictionary * dict = @{@"message":@"Bugly appId不能为空", @"isSuccess":@NO};
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
NSString * json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
result(json);
}
}else if([@"postCatchedException" isEqualToString:call.method]){
NSString *crash_detail = call.arguments[@"crash_detail"];
NSString *crash_message = call.arguments[@"crash_message"];
if (crash_detail == nil || crash_detail == NULL) {
crash_message = @"";
}
if ([crash_detail isKindOfClass:[NSNull class]]) {
crash_message = @"";
}
NSArray *stackTraceArray = [crash_detail componentsSeparatedByString:@""];
NSDictionary *data = call.arguments[@"crash_data"];
if(data == nil){
data = [NSMutableDictionary dictionary];
}
[Bugly reportExceptionWithCategory:5 name:crash_message reason:@" " callStack:stackTraceArray extraInfo:data terminateApp:NO];
result(nil);
}else if([@"setUserId" isEqualToString:call.method]){
NSString *userId = call.arguments[@"userId"];
if (![self isBlankString:userId]) {
[Bugly setUserIdentifier:userId];
}
result(nil);
}else if([@"setUserTag" isEqualToString:call.method]){
NSNumber *userTag = call.arguments[@"userTag"];
if (userTag!=nil) {
NSInteger anInteger = [userTag integerValue];
[Bugly setTag:anInteger];
}
result(nil);
}else if([@"putUserData" isEqualToString:call.method]){
NSString *key = call.arguments[@"key"];
NSString *value = call.arguments[@"value"];
if (![self isBlankString:key]&&![self isBlankString:value]){
[Bugly setUserValue:value forKey:key];
}
result(nil);
}else {
result(FlutterMethodNotImplemented);
}
}
- (BOOL) isBlankString:(NSString *)string {
if (string == nil || string == NULL) {
return YES;
}
if ([string isKindOfClass:[NSNull class]]) {
return YES;
}
if ([[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length]==0) {
return YES;
}
return NO;
}
@end
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'flutter_bugly'
s.version = '0.0.1'
s.summary = 'A new Flutter bugly plugin.'
s.description = <<-DESC
A new Flutter bugly plugin.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.dependency 'Bugly'
s.static_framework = true
s.ios.deployment_target = '8.0'
end
library flutter_bugly;
export 'src/flutter_bugly.dart';
export 'src/bean/upgrade_info.dart';
\ No newline at end of file
class InitResultInfo {
String message = "";
String appId = "";
bool isSuccess = false;
InitResultInfo.fromJson(Map<String, dynamic> json)
: message = json['message'],
appId = json['appId'],
isSuccess = json['isSuccess'];
}
class UpgradeInfo {
String id = "";
String title = "";
String newFeature = "";
int publishTime = 0;
int publishType = 0;
int upgradeType = 1;//2为强制更新
int popTimes = 0;
int popInterval = 0;
int versionCode;
String versionName = "";
String apkMd5;
String apkUrl;
int fileSize;
String imageUrl;
int updateType;
UpgradeInfo.fromJson(Map<String, dynamic> json)
: id = json['id'],
title = json['title'],
newFeature = json['newFeature'],
publishTime = json['publishTime'],
publishType = json['publishType'],
upgradeType = json['upgradeType'],
popTimes = json['popTimes'],
popInterval = json['popInterval'],
versionCode = json['versionCode'],
versionName = json['versionName'],
apkMd5 = json['apkMd5'],
apkUrl = json['apkUrl'],
fileSize = json['fileSize'],
imageUrl = json['imageUrl'],
updateType = json['updateType'];
}
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'dart:convert';
import 'bean/upgrade_info.dart';
import 'bean/init_result_info.dart';
class FlutterBugly {
FlutterBugly._();
static const MethodChannel _channel =
const MethodChannel('crazecoder/flutter_bugly');
static final _onCheckUpgrade = StreamController<UpgradeInfo>.broadcast();
static int _checkUpgradeCount = 0;
static int _count = 0;
///初始化
static Future<InitResultInfo> init({
String androidAppId,
String iOSAppId,
String channel, //自定义渠道标识
bool autoCheckUpgrade = true,
bool autoInit = true,
bool autoDownloadOnWifi = false,
bool enableHotfix = false,
bool enableNotification = false, //未适配androidx
bool showInterruptedStrategy = true, //设置开启显示打断策略
bool canShowApkInfo = true, //设置是否显示弹窗中的apk信息
int initDelay = 0, //延迟初始化,单位秒
int upgradeCheckPeriod = 0, //升级检查周期设置,单位秒
int checkUpgradeCount = 1, //UpgradeInfo为null时,再次check的次数,经测试1为最佳
}) async {
assert((Platform.isAndroid && androidAppId != null) ||
(Platform.isIOS && iOSAppId != null));
_channel.setMethodCallHandler(_handleMessages);
_checkUpgradeCount = checkUpgradeCount;
Map<String, Object> map = {
"appId": Platform.isAndroid ? androidAppId : iOSAppId,
"channel": channel,
"autoCheckUpgrade": autoCheckUpgrade,
"autoDownloadOnWifi": autoDownloadOnWifi,
"enableHotfix": enableHotfix,
"enableNotification": enableNotification,
"showInterruptedStrategy": showInterruptedStrategy,
"canShowApkInfo": canShowApkInfo,
"initDelay": initDelay,
"upgradeCheckPeriod": upgradeCheckPeriod,
};
final String result = await _channel.invokeMethod('initBugly', map);
Map resultMap = json.decode(result);
var resultBean = InitResultInfo.fromJson(resultMap);
return resultBean;
}
static Future<Null> _handleMessages(MethodCall call) async {
switch (call.method) {
case 'onCheckUpgrade':
UpgradeInfo _info = _decodeUpgradeInfo(call.arguments["upgradeInfo"]);
if (_info != null && _info.apkUrl != null) {
_count = 0;
_onCheckUpgrade.add(_info);
} else {
if (_count < _checkUpgradeCount) {
_count++;
checkUpgrade(isManual:false,);
}
}
break;
}
}
///自定义渠道标识 android专用
static Future<Null> setAppChannel(String channel) async {
Map<String, Object> map = {
"channel": channel,
};
await _channel.invokeMethod('setAppChannel', map);
}
///设置用户标识
static Future<Null> setUserId(String userId) async {
Map<String, Object> map = {
"userId": userId,
};
await _channel.invokeMethod('setUserId', map);
}
///设置标签
///userTag 标签ID,可在网站生成
static Future<Null> setUserTag(int userTag) async {
Map<String, Object> map = {
"userTag": userTag,
};
await _channel.invokeMethod('setUserTag', map);
}
///设置关键数据,随崩溃信息上报
static Future<Null> putUserData(
{@required String key, @required String value}) async {
assert(key != null && key.isNotEmpty);
assert(value != null && value.isNotEmpty);
Map<String, Object> map = {
"key": key,
"value": value,
};
await _channel.invokeMethod('putUserData', map);
}
///获取本地更新策略,即上次未更新的策略
static Future<UpgradeInfo> getUpgradeInfo() async {
final String result = await _channel.invokeMethod('getUpgradeInfo');
var info = _decodeUpgradeInfo(result);
return info;
}
///检查更新
///return 更新策略信息
static Future<Null> checkUpgrade({
bool isManual = true,
bool isSilence = false,
}) async {
if (!Platform.isAndroid) return null;
if (isManual) _count = 0;
Map<String, Object> map = {
"isManual": isManual, //用户手动点击检查,非用户点击操作请传false
"isSilence": isSilence, //是否显示弹窗等交互,[true:没有弹窗和toast] [false:有弹窗或toast]
};
await _channel.invokeMethod('checkUpgrade', map);
}
///异常上报
static void postCatchedException<T>(
T callback(), {
FlutterExceptionHandler handler, //异常捕捉,用于自定义打印异常
String filterRegExp, //异常上报过滤正则,针对message
bool debugUpload = false,
}) {
bool _isDebug = false;
assert(_isDebug = true);
// This captures errors reported by the Flutter framework.
FlutterError.onError = (details) {
Zone.current.handleUncaughtError(details.exception, details.stack);
};
Isolate.current.addErrorListener(new RawReceivePort((dynamic pair) {
var isolateError = pair as List<dynamic>;
var _error = isolateError.first;
var _stackTrace = isolateError.last;
Zone.current.handleUncaughtError(_error, _stackTrace);
}).sendPort);
// This creates a [Zone] that contains the Flutter application and stablishes
// an error handler that captures errors and reports them.
//
// Using a zone makes sure that as many errors as possible are captured,
// including those thrown from [Timer]s, microtasks, I/O, and those forwarded
// from the `FlutterError` handler.
//
// More about zones:
//
// - https://api.dartlang.org/stable/1.24.2/dart-async/Zone-class.html
// - https://www.dartlang.org/articles/libraries/zones
runZoned<Future<Null>>(() async {
callback();
}, onError: (error, stackTrace) {
_filterAndUploadException(
debugUpload,
_isDebug,
handler,
filterRegExp,
FlutterErrorDetails(exception: error, stack: stackTrace),
);
});
}
static void _filterAndUploadException(
debugUpload,
_isDebug,
handler,
filterRegExp,
FlutterErrorDetails details,
) {
if (!_filterException(
debugUpload,
_isDebug,
handler,
filterRegExp,
details,
)) {
uploadException(
message: details.exception.toString(),
detail: details.stack.toString());
}
}
static bool _filterException(
bool debugUpload,
bool _isDebug,
FlutterExceptionHandler handler,
String filterRegExp,
FlutterErrorDetails details) {
//默认debug下打印异常,不上传异常
if (!debugUpload && _isDebug) {
handler == null
? FlutterError.dumpErrorToConsole(details)
: handler(details);
return true;
}
//异常过滤
if (filterRegExp != null) {
RegExp reg = new RegExp(filterRegExp);
Iterable<Match> matches = reg.allMatches(details.exception.toString());
if (matches.length > 0) {
return true;
}
}
return false;
}
///上报自定义异常信息,data为文本附件
///Android 错误分析=>跟踪数据=>extraMessage.txt
///iOS 错误分析=>跟踪数据=>crash_attach.log
static Future<Null> uploadException(
{@required String message, @required String detail, Map data}) async {
var map = {};
map.putIfAbsent("crash_message", () => message);
map.putIfAbsent("crash_detail", () => detail);
if (data != null) map.putIfAbsent("crash_data", () => data);
await _channel.invokeMethod('postCatchedException', map);
}
static UpgradeInfo _decodeUpgradeInfo(String jsonStr) {
if (jsonStr == null || jsonStr.isEmpty) return null;
Map resultMap = json.decode(jsonStr);
var info = UpgradeInfo.fromJson(resultMap);
return info;
}
static Stream<UpgradeInfo> get onCheckUpgrade => _onCheckUpgrade.stream;
static void dispose() {
_count = 0;
_onCheckUpgrade.close();
}
}
name: flutter_bugly
description: Flutter plugin for Tencent Bugly, Crash monitoring, Crash analysis, exception reporting, application update, data statistics, etc
version: 0.3.0+2
author: crazecoder <21527312@qq.com>
homepage: https://github.com/crazecoder/flutter_bugly
environment:
sdk: ">=2.1.0 <3.0.0"
flutter: ">=1.12.0 <2.0.0"
dependencies:
flutter:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# This section identifies this Flutter project as a plugin project.
# The androidPackage and pluginClass identifiers should not ordinarily
# be modified. They are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
android:
package: com.crazecoder.flutterbugly
pluginClass: FlutterBuglyPlugin
ios:
pluginClass: FlutterBuglyPlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.io/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.io/assets-and-images/#resolution-aware.
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.io/custom-fonts/#from-packages
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment