Flutter接入第三方SDK
#Flutter接入第三方SDK
最近开始使用Flutter,发现我们可以写出原生的页面,使用Dart语言完成逻辑的编程。但是假如我们需要接入一个第三方SDK,这个时候应该怎么办呢?
##接入第三方SDK-登录SDK
登录SDK是58出的一款适用于全公司的一套登录体系。登录SDK是以一个AAR的形式提供的。该aar包含classes.jar、资源文件、assets文件、AndroidManifest.xml以及R.txt文件。
##Java的接入方式
首先这是一个全新的挑战,接到手之后完全不知道该怎么弄。
这里我们先回顾一下,在使用Java开发的时候,我们是如何引入AAR的:
1.配置build.gradle文件,添加implementation 'xxx:xxxx:'
2.编译
3.调用
##Flutter的接入方式
那么Flutter的接入方式与Java的方式有什么不同呢?我们也试着按照上面的三种方式来做一次:
###配置AAR & 编译
这个配置AAR的方式有点诡异,需要在File -> new -> new Module -> Import .AAR package,添加依赖之后,它会自动编译,然后你就会发现可以打开了。
然鹅,这种方式,是错误的。因为你把这个AAR引入到Flutter的工程里面,但是并没有引入到android的项目中来,因为我发现调用的时候依然调用不了。
另外,我发现在android目录下有一个MainActivity.java文件,大概率就是在这里添加跟Flutter页面的交互了。
可是我发现这个类编辑之后没有任何代码提示,直觉告诉我这里缺一些东西,缺的是android的编译环境:
答案就是:双开
通过File -> Open的形式,用android studio再打开一个目录引入android工程。
接下来的依赖关系也就变得顺其自然,双开固然麻烦,但是当你用flutter运行的那一刻,你就发现,一切都是值得的。
模块设计
将登录SDK的模块引入,使得Flutter内部可以通过MethodChannel来调用Android的方法
登录SDK的特点:
1.无需实现UI界面;
2.需要设置页面监听;
3.接收回调之后可以及时展示到用户界面上
登录SDK的引入
由于Flutter默认不支持armeabi
的CPU架构,并且由于我们是并行开发,所以改动太多配置文件也会增加同学们的学习成本。所以,这里我们直接让登录SDK提供了支持了64位CPU架构测试AAR。
同时,我们在android目录下修改了如下配置:
android目录下,修改了build.gradle文件:
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
mavenLocal()
maven {
url "http://artifactory.58corp.com:8081/artifactory/android-public"
}
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
app目录下,修改了build.gradle添加了如下依赖这些依赖都是必须的
:
implementation 'com.wuba.wuxian.sdk:login:2.1.6.4.1-test'
implementation 'com.wuba.wuxian.third:weichatsoter:1.3.10'
implementation 'com.wuba.certify:deviceid:2.2.11'
implementation 'com.wuba.xxzl:fastlogin:2.0.2.0'
implementation 'io.reactivex:rxjava:1.3.8'
implementation 'io.reactivex:rxandroid:1.2.1'
Flutter添加调用
在flutter的页面上,开发者可以随便使用UI元素,通过MethodChannel建立回调通道,在UI控件上设置点击事件调起登录并监听登录结果、
通过platform.invokeMethod调起对应的登录方式、setState来设置结果、flutter监听变化后,会刷新并展示;
Flutter的调用
主要是在WubaLoginClientTestComponent
类里面;
///定义一个MethodChannel
static const platform = const MethodChannel('com.wuba.flutter.flutter_hades/login');
///具体的调用方法
child: GestureDetector(
child: new Row(
children: <Widget>[
new Text(
'调起58登录: ' + _loginResult,
style: new TextStyle(
color: const Color(0xff353535), fontSize: 16.0),
),
],
),
onTap: _startWubaLogin,
),
///_loginResult :是登陆的结果;
///_startWubaLogin :是调用的方法;
接收回调刷新UI
///_loginResult :是登陆的结果;
///_startWubaLogin :是调用的方法;
String _loginResult = '';
Future<Null> _startWubaLogin() async {
String loginResult;
try{
loginResult = await platform.invokeMethod('wubaLogin');
} on PlatformException catch (e){
loginResult = "error";
}
setState(() {
_loginResult = loginResult;
});
}
Android接收调用
初始化
登录SDK的初始化可以按需要选取构建项,通过构建者模式来构建(由于第三方登录微信登录需要正式签名,所以引入目前也无法调用,所以暂时先不引入):
LoginSdk.LoginConfig loginConfig = new LoginSdk.LoginConfig()
//根据统一开关,上线后关闭log
.setLogLevel(ILogger.STANDARD_LOG)
//必选,设置product id, 由产品统一约定
.setProductId("58app")
.setLogoResId(R.drawable.wb_new_icon)
//可选,如果集成三方登录,则设置相应的开放平台key
// .setThirdLoginConfig(WubaSettingCommon.QQ_API_KEY, WubaSettingCommon.CONSUMER_KEY_WEIXIN, WubaSettingCommon.CONSUMER_KEY_SINA, WubaSettingCommon.REDIRECT_URL_SINA)
.setNeedInitFaceVerify(true, "Xenh6dVg")
.setIsLoginRelyOnUserInfo(true)
.setGatewayLoginAppId("ef2b526cdb4948b1bf7556f23d0a15da");
//初始化
LoginSdk.register(this, loginConfig);
设置回调
设置通道:
private static final String CHANNEL = "com.wuba.flutter.flutter_hades/login";
接收Flutter发送过来的调用,并调用登录SDK:
LoginClient.register(mLoginCallback);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
// Note: this method is invoked on the main thread.
if(call.method.equals("wubaLogin")){
LoginClient.launch(MainActivity.this, Request.LOGIN);
mResult = result;
} else if(call.method.equals("wubaPhoneLogin")){
LoginClient.launch(MainActivity.this, Request.PHONE_LOGIN);
mResult = result;
} else if(call.method.equals("wubaRegister")){
LoginClient.launch(MainActivity.this, Request.REGISTER);
mResult = result;
} else if(call.method.equals("getUserID")){
result.success(LoginClient.getUserID(MainActivity.this));
} else if(call.method.equals("getUserPPU")){
result.success(LoginClient.getTicket(MainActivity.this, ".58.com", "PPU"));
} else if(call.method.equals("getUserName")){
result.success(LoginClient.getUserName(MainActivity.this));
} else {
result.notImplemented();
}
}
});
页面可以销毁后,给出去回调:
LoginCallback mLoginCallback = new SimpleLoginCallback(){
@Override
public void onLogin58Finished(boolean isSuccess, String msg, LoginSDKBean loginSDKBean) {
super.onLogin58Finished(isSuccess, msg, loginSDKBean);
if(mResult != null){
if(isSuccess){
mResult.success(msg);
}
}
}
};
细说MethodChannel
什么是MethodChannel?
《Flutter中文网》上面描述的标题是:插件开发:Android端API实现,也就是说,MethodChannel是Flutter实现与Android通过API交互的插件。通过上面的例子,我们也可以清楚的回顾它的一些特点,Flutter即时调用,Android得到反馈,Flutter进行UI展示,如下图所示:

它的主要流程是什么?
1.在flutter端:设置一个MethodChannel:static const platform = const MethodChannel('samples.flutter.io/battery');
2.在flutter端:通过invoke方法调用:await platform.invokeMethod('getBatteryLevel');
3.在flutter端:写UI来接收回调
children: [
new RaisedButton(
child: new Text('Get Battery Level'),
onPressed: _getBatteryLevel,
),
new Text(_batteryLevel),
],4.在android端:调用setMethodCallHandler方法:
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
// TODO
}
});5.在android端:实现onMethodCall回调:
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getBatteryLevel")) {
int batteryLevel = getBatteryLevel();
if (batteryLevel != -1) {
result.success(batteryLevel);
} else {
result.error("UNAVAILABLE", "Battery level not available.", null);
}
} else {
result.notImplemented();
}
}
它的原理是什么?
针对上面的流程我们有两个疑问:
-
platform.invokeMethod怎么把对应的method调起来的?
-
result.success是如何回调到
源码阅读
上源码之前,我们先来看个图:
官方说明:
A named channel for communicating with platform plugins using asynchronous method calls.Method calls are encoded into binary before >being sent, and binary results received are decoded into Dart values. The [MethodCodec] used must be compatible with the one used by the platform plugin. This can be achieved by creating a method channel counterpart of this channel on the platform side. The Dart type of arguments and results is
dynamic
, but only values supported by the specified [MethodCodec] can be used. The use of unsupported values should be considered programming errors, and will result in exceptions being thrown. The null value is supported for all codecs. The logical identity of the channel is given by its name. Identically named channels will interfere with each other's communication.
大概的意思:定义一个管道,利用异步方法跟平台插件进行交互。方法里面的参数必须是一个特定独一无二的,不能是特殊字符或者已经定义好的有含义的标识符。
MethodChannel一共有三个参数,它们分别是:String name、StandardMethodCodec codec、binaryMessenger;
其中name是必选参数、后面两个是可选参数;
name:格式通常是:包名/方法名,例如:'com.wuba.flutter.flutter_hades/login';
StandardMethodCodec codec:标准方法编码,有点类似于Java的序列化,里面有两个方法:ByteData encodeMessage(T message);和T decodeMessage(ByteData message);
BinaryMessenger :这个类就是用来追踪管道的message的,分发并记录每一个注册了管道的handler并回调;
platform.invokeMethod
MethodHandler提供了一个方法invokeMethod,这个方法就是调用的入口函数:
@optionalTypeArgs
Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async {
assert(method != null);
final ByteData result = await binaryMessenger.send(
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null) {
throw MissingPluginException('No implementation found for method $method on channel $name');
}
final T typedResult = codec.decodeEnvelope(result);
return typedResult;
}
我们主要是看这个binaryMessenger.send的方法:
@override
Future<ByteData> send(String channel, ByteData message) {
final MessageHandler handler = _mockHandlers[channel];
if (handler != null)
return handler(message);
return _sendPlatformMessage(channel, message);
}
通过Map存储到key-value,key代表的是channel,就是我们刚才设置的MethodChannel中的name,value是handler,这个就相当于track。
接下来调用Flutter的UI顶层的实现方式:
ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message response callback'),
));
}
});
ui.window类似于android里面的window?
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) {
final String error =
_sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
if (error != null)
throw Exception(error);
}
跟到这里,我们发现调用的Native的方法:
String _sendPlatformMessage(String name,
PlatformMessageResponseCallback callback,
ByteData data) native 'Window_sendPlatformMessage';
接下来,我们看c++是怎么写的:
Dart_Handle SendPlatformMessage(Dart_Handle window,
const std::string& name,
Dart_Handle callback,
const tonic::DartByteData& data) {
UIDartState* dart_state = UIDartState::Current();
...
fml::RefPtr<PlatformMessageResponse> response;
if (!Dart_IsNull(callback)) {
response = fml::MakeRefCounted<PlatformMessageResponseDart>(
tonic::DartPersistentValue(dart_state, callback),
dart_state->GetTaskRunners().GetUITaskRunner());
}
if (Dart_IsNull(data.dart_handle())) {
dart_state->window()->client()->HandlePlatformMessage(
fml::MakeRefCounted<PlatformMessage>(name, response));
} else {
const uint8_t* buffer = static_cast<const uint8_t*>(data.data());
// data数据部位null,会走下面这块代码
dart_state->window()->client()->HandlePlatformMessage(
fml::MakeRefCounted<PlatformMessage>(
name, std::vector<uint8_t>(buffer, buffer + data.length_in_bytes()),
response));
}
return Dart_Null();
}
我们看这里有一段代码,叫做HandlePlatformMessage,这段代码在../engine/shell/common/engine.cc
void Engine::HandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) {
if (message->channel() == kAssetChannel) {
HandleAssetPlatformMessage(std::move(message));
} else {
delegate_.OnEngineHandlePlatformMessage(std::move(message));
}
}
void Shell::OnEngineHandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) {
...
task_runners_.GetPlatformTaskRunner()->PostTask(
[view = platform_view_->GetWeakPtr(), message = std::move(message)]() {
if (view) {
view->HandlePlatformMessage(std::move(message));
}
});
}
platform_view_是一个继承了PlatformView类的PlatformViewAndroid对象,该对象在创建AndroidShellHolder对象时被创建。view->HandlePlatformMessage执行以下方法,../engine/shell/platform/android/platform_view_android.cc
void PlatformViewAndroid::HandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) {
JNIEnv* env = fml::jni::AttachCurrentThread();
fml::jni::ScopedJavaLocalRef<jobject> view = java_object_.get(env);
if (view.is_null())
return;
int response_id = 0;
if (auto response = message->response()) {
response_id = next_response_id_++;
pending_responses_[response_id] = response;
}
auto java_channel = fml::jni::StringToJavaString(env, message->channel());
if (message->hasData()) {
fml::jni::ScopedJavaLocalRef<jbyteArray> message_array(
env, env->NewByteArray(message->data().size()));
env->SetByteArrayRegion(
message_array.obj(), 0, message->data().size(),
reinterpret_cast<const jbyte*>(message->data().data()));
message = nullptr;
// This call can re-enter in InvokePlatformMessageXxxResponseCallback.
FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
message_array.obj(), response_id);
} else {
...
}
}
于是,我们最终跟到了JNI的最终方法,它调用的是FlutterJNI.java
中的handlePlatformMessage:
static jmethodID g_handle_platform_message_method = nullptr;
void FlutterViewHandlePlatformMessage(JNIEnv* env,
jobject obj,
jstring channel,
jobject message,
jint responseId) {
env->CallVoidMethod(obj, g_handle_platform_message_method, channel, message,
responseId);
FML_CHECK(CheckException(env));
}
private void handlePlatformMessage(String channel, byte[] message, int replyId) {
if (this.platformMessageHandler != null) {
this.platformMessageHandler.handleMessageFromDart(channel, message, replyId);
}
}
public void handleMessageFromDart(final String channel, byte[] message, final int replyId) {
FlutterNativeView.this.assertAttached();
BinaryMessageHandler handler = (BinaryMessageHandler)FlutterNativeView.this.mMessageHandlers.get(channel);
if (handler != null) {
try {
ByteBuffer buffer = message == null ? null : ByteBuffer.wrap(message);
handler.onMessage(buffer, new BinaryReply() {
private final AtomicBoolean done = new AtomicBoolean(false);
public void reply(ByteBuffer reply) {
if (!FlutterNativeView.this.isAttached()) {
Log.d("FlutterNativeView", "handleMessageFromDart replying ot a detached view, channel=" + channel);
} else if (this.done.getAndSet(true)) {
throw new IllegalStateException("Reply already submitted");
} else {
if (reply == null) {
FlutterNativeView.this.mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
} else {
FlutterNativeView.this.mFlutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());
}
}
}
});
} catch (Exception var6) {
Log.e("FlutterNativeView", "Uncaught exception in binary message listener", var6);
FlutterNativeView.this.mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
} else {
FlutterNativeView.this.mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
}
这里面我们看到这样一段代码, BinaryMessageHandler handler = (BinaryMessageHandler)FlutterNativeView.this.mMessageHandlers.get(channel);
那这个handler是如何根据channel跟踪到的呢?我们已经到Java方法了啊可。
所以,我们试着回想一下,当时是不是在MainActivity里面也设置了个同名的String, 这个字符串有点似曾相识呢。由此猜想,这个handler可能是当时我们自己设进去的,我们来看看如下代码setMethodCallHandler
:
private static final String CHANNEL = "com.wuba.flutter.flutter_hades/login";
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
// Note: this method is invoked on the main thread.
}
});
public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
this.messenger.setMessageHandler(this.name, handler == null ? null : new MethodChannel.IncomingMethodCallHandler(handler));
}
public void setMessageHandler(String channel, BinaryMessageHandler handler) {
this.mNativeView.setMessageHandler(channel, handler);
}
接下来我们继续看这个handler.onMessage()
方法:
public void onMessage(ByteBuffer message, final BinaryReply reply) {
MethodCall call = MethodChannel.this.codec.decodeMethodCall(message);
try {
this.handler.onMethodCall(call, new MethodChannel.Result() {
public void success(Object result) {
reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result));
}
public void error(String errorCode, String errorMessage, Object errorDetails) {
reply.reply(MethodChannel.this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
public void notImplemented() {
reply.reply((ByteBuffer)null);
}
});
} catch (RuntimeException var5) {
Log.e("MethodChannel#" + MethodChannel.this.name, "Failed to handle method call", var5);
reply.reply(MethodChannel.this.codec.encodeErrorEnvelope("error", var5.getMessage(), (Object)null));
}
}
方法中首先将从c++层传递过来的消息通过codec解码为MethodCall对象,然后调用MainActivity中实现的MethodHandler的onMethodCall方法,改方法实现中会执行android的API调用,然后调用result.success()方法,通过reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result));将结果数据编码后进行返回。reply方法中会调用FlutterNativeView.this.mFlutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());方法将响应结果返回。
然后回调给C++层。
最终回调给UI层,调用setState完成整个更新。
总结
1.Flutter调用invokeMethod方法,将code转码和并建立跟踪对应的Messagehandle;
2.在c++层对其进行封装,通过sendPlatformMessage调用JNI方法;
3.根据调用方法找到最终调用已经注册好的Java对象的对应方法;
4.获取回调结果并将结果返回给C++层;
5.C++层获取结果后最终返回给Flutter的setState方法,刷新UI;
预览效果图

接入报错
1.A problem occurred configuring project ':sqflite'.
Could not download groovy-all.jar
如果出现资源找不到,一般都是gradle的配置文件出现了问题,我们可以通过引入配置来解决:在工程->android->build.gradle下,对于buildscript和allprojects中的repositories进行修改,添加如下代码:
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
2.注册跳转页面需要添加路由
注册一个页面,虽然不需要像android原生一样在androidmanifest.xml文件中注册activity,但是在flutter里面,需要注册路由。路由分为静态路由和动态路由,只有注册后,才能完成跳转;
3.This Overlay widget cannot be marked as needing to build because the framework is already in the
process of building widgets
我通过A页面添加了一个GestureDetector的onTap事件跳转进入B页面,但是当我用同样的方法在B页面再添加GestureDetector的时候,在onTap里面添加之后就会报错;
onTap: _onWubaLoginClick(),
看见没,上面代码,原来是多加了一个(),Java里调用方法就是有括号的啊,再说你这个报错可真的是让我好找啊~
4.flutter工程下,无法编译android下的MainActivity.java
新开一个工程,开发flutter的android工程即可
5.善于利用工具
在进行flutter开发的时候,我们难免会遇到各种各样的问题,其实大部分我们都可以在官方文档里面得到解决。
所以有事没事,看看官网:
https://flutterchina.club/platform-channels/
6.引入58的第三方找不到库
其实是没有配置maven仓库,需要
mavenLocal()
maven {
url "http://artifactory.58corp.com:8081/artifactory/android-public"
}
7.java.lang.UnsatisfiedLinkError: dlopen failed: "/data/data/com.wuba.flutter.flutter_hades/files/lib/libcom_wuba_uc_rsa.so" is 32-bit instead of 64-bit
58APP接入的是32位的,那么为啥在这个flutter的工程里面,它需要引入的是64位的呢?
8.libcom_wuba_uc_rsa.so" >= file size: 0 >= 0
java.lang.UnsatisfiedLinkError: dlopen failed: file offset for the library "/data/user/0/com.wuba.flutter.flutter_hades/files/lib/libcom_wuba_uc_rsa.so" >= file size: 0 >= 0
at java.lang.Runtime.load0(Runtime.java:928)
at java.lang.System.load(System.java:1621)
at com.wuba.uc.RsaCryptService.b(RsaCryptService.java:43)
at com.wuba.uc.RsaCryptService.a(RsaCryptService.java:24)
at com.wuba.loginsdk.internal.b.process(InitCommon.java:24)
at com.wuba.loginsdk.external.LoginSdk.register(LoginSdk.java:144)
at com.wuba.loginsdk.external.LoginSdk.register(LoginSdk.java:60)
at com.wuba.flutter.flutter_hades.WubaApplication.registeWubaLogin(WubaApplication.java:31)
at com.wuba.flutter.flutter_hades.WubaApplication.onCreate(WubaApplication.java:15)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1119)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5881)
at android.app.ActivityThread.-wrap1(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1690)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:6701)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:249)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
9.support-v4
java.lang.RuntimeException: Unable to get provider com.wuba.loginsdk.provider.PassportFileProvider: java.lang.ClassNotFoundException: Didn't find class "com.wuba.loginsdk.provider.PassportFileProvider" on path: DexPathList[[zip file "/data/app/com.wuba.flutter.flutter_hades-pyib-R74ajY34PslDyjFsg==/base.apk"],nativeLibraryDirectories=[/data/app/com.wuba.flutter.flutter_hades-pyib-R74ajY34PslDyjFsg==/lib/arm64, /data/app/com.wuba.flutter.flutter_hades-pyib-R74ajY34PslDyjFsg==/base.apk!/lib/arm64-v8a, /system/lib64, /system/vendor/lib64]]
at android.app.ActivityThread.installProvider(ActivityThread.java:6447)
at android.app.ActivityThread.installContentProviders(ActivityThread.java:5950)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5862)
at android.app.ActivityThread.-wrap1(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1690)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:6701)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:249)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.wuba.loginsdk.provider.PassportFileProvider" on path: DexPathList[[zip file "/data/app/com.wuba.flutter.flutter_hades-pyib-R74ajY34PslDyjFsg==/base.apk"],nativeLibraryDirectories=[/data/app/com.wuba.flutter.flutter_hades-pyib-R74ajY34PslDyjFsg==/lib/arm64, /data/app/com.wuba.flutter.flutter_hades-pyib-R74ajY34PslDyjFsg==/base.apk!/lib/arm64-v8a, /system/lib64, /system/vendor/lib64]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:93)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at android.app.ActivityThread.installProvider(ActivityThread.java:6423)
at android.app.ActivityThread.installContentProviders(ActivityThread.java:5950)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5862)
at android.app.ActivityThread.-wrap1(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1690)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:6701)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:249)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
Suppressed: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/support/v4/content/FileProvider;
at java.lang.VMClassLoader.findLoadedClass(Native Method)
at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:738)
at java.lang.ClassLoader.loadClass(ClassLoader.java:363)
... 12 more
Caused by: java.lang.ClassNotFoundException: Didn't find class "android.support.v4.content.FileProvider" on path: DexPathList[[zip file "/data/app/com.wuba.flutter.flutter_hades-pyib-R74ajY34PslDyjFsg==/base.apk"],nativeLibraryDirectories=[/data/app/com.wuba.flutter.flutter_hades-pyib-R74ajY34PslDyjFsg==/lib/arm64, /data/app/com.wuba.flutter.flutter_hades-pyib-R74ajY34PslDyjFsg==/base.apk!/lib/arm64-v8a, /system/lib64, /system/vendor/lib64]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:93)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
... 15 more
10.wlog&soter&两个图片资源找不到
参考文章
https://tech.meituan.com/2018/08/09/waimai-flutter-practice.html
https://www.jianshu.com/p/185c42fe002b