Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
The preface
Flutter is a single-threaded framework that uses Future + Dio to implement network encapsulation. It still occupies UI threads and can cause page operations to lag when invoked frequently.
Treatment scheme
Use BasicMessageChannel + Retrofit to encapsulate the native layer network plug-in and enable the child thread to request the network without using the UI thread.
use
Add reference
retrofit_plugin:
git:
url: https://github.com/liyufengrex/retrofit_plugin.git
ref: 0.01.
Copy the code
Network configuration initialization:
void main() {
NetWorkConfigBuild.init(); ///Initialize the network configurationrunApp(... Omit...). ; }Copy the code
import 'package:retrofit_plugin/retrofit_plugin.dart';
abstract class NetWorkConfigBuild {
static void init() {
NetWorkConfigManager.addDefault(NetWorkConfig(
baseUrl: "http://api.wpbom.com/".// Public header
headers: {
"Authorization": "Bearer",
},
intercept: _Intercept(),
));
NetWorkConfigManager.addConfig(
"second",
NetWorkConfig(
baseUrl: "https://www.apiopen.top/", headers: {}, intercept: _Intercept(), )); }}///Filter, mainly used for log printing
class _Intercept extends NetworkIntercept {
@override
onResponse(Request request, Response response) {
if (response.success) {
print("log: ${request.toString()} - ${response.toString()}");
} else {
///System error
print("log-error: ${request.toString()} - ${response.toString()}"); }}}Copy the code
Each domainKey corresponds to a separate netWorkConfig configuration, and each configuration generates a separate Retrofit request instance. In the preceding example, two network configurations are added. The corresponding domainkeys are default and second respectively.
Creating a network request
Inherits RequestBaseLoader with a generic return value type
import 'package:retrofit_plugin/retrofit_plugin.dart';
class PostRequest extends RequestBaseLoader<String> {
// The domainKey is overwritten to match the network layer configuration. If the domainKey is not overwritten, the network configuration whose domainKey is default is read by default
@override
String get domainKey => "second";
@override
Method get method => Method.Post;
@override
String get path => "likePoetry";
@override
Map<String.dynamic> get data => {
"name": "Li bai"};@override
Map<String.String> get header => {};
@override
Future<String> execute() {
return request().then((value) {
returnvalue.toString(); }); }}Copy the code
Using network request
_post() async {
//PostRequest is a custom class (need to inherit RequestBaseLoader)
PostRequest().execute().then((value) {
setState(() {
_data = value;
});
});
}
Copy the code
Note: the android manifest file, application label need to add attributes: android: networkSecurityConfig = “@ XML/network_security_config”
Demo has been uploaded. For details, see github.com/liyufengrex… In the example.
Analysis of key code in plug-ins
The native layer does not convert data types and returns strings directly
mRetrofitBuilder.addConverterFactory(ScalarsConverterFactory.create());
mRetrofitBuilder.addConverterFactory(GsonConverterFactory.create());
Copy the code
Retrofit dynamically fills parameters
@POST
fun doPost(
@Url url: String.@HeaderMap headers: Map<String.String>,
@Body params: Map<String.@JvmSuppressWildcards Any>
): Observable<String>
@GET
fun doGet(
@Url url: String.@HeaderMap headers: Map<String.String>,
@QueryMap(encoded=false) params: Map<String.@JvmSuppressWildcards Any>
): Observable<String>
Copy the code
Use BasicMessageChannel for this fragmented and continuous interaction
class RetrofitPlugin : FlutterPlugin.BasicMessageChannel.MessageHandler<Any> {... Override fun onMessage(message: Any? , reply: BasicMessageChannel.Reply<Any> ) { message? .let { val mapParams = itas Map<String, Any>
val method = mapParams["method"] as String // separate post get
val baseUrl = mapParams["baseUrl"] as String // Get the Retrofit instance from baseUrl
val pathUrl = mapParams["pathUrl"] as String
val headers = mapParams["headers"]
val params = mapParams["params"]
doRequest(
method = method,
baseUrl = baseUrl,
pathUrl = pathUrl,
headers = headers as Map<String.String>,
params = params as Map<String, Any>,
reply = reply
)
}
}
private fun doRequest(
method: String,
baseUrl: String,
pathUrl: String,
headers: Map<String.String>,
params: Map<String, Any>,
reply: BasicMessageChannel.Reply<Any>
) {
mRequestLoader.doRequest(
method,
baseUrl,
pathUrl,
headers,
params,
object : IRequestResult {
override fun onSuccess(data: String) {
reply.reply(createResult(true, data))
}
override fun onError(msg: String) {
reply.reply(createResult(false, msg))
}
})
}
// Return the same format
private fun createResult(success: Boolean, data: String): HashMap<String, Any> {
var result = HashMap<String, Any>()
result["success"] = success
result["data"] = data
return result
}
}
Copy the code
The dart layerchannel
encapsulation
class NativeRequestTool {
// The BasicMessageChannel name should correspond to the native end
static const BasicMessageChannel _messageChannel =
const BasicMessageChannel('retrofitRequest', StandardMessageCodec());
static Future<Response> doRequest(Request param) async {
// We normalize the parameter type to map
return_messageChannel .send(param.toJson()) .then((value) => Response.fromNative(value)); }}// We specify the parameter transfer model paradigm
class Request {
final String method;
final String baseUrl;
final String pathUrl;
final Map<String.String> headers;
final Map<String.dynamic> params;
Request({
this.method,
this.baseUrl,
this.pathUrl,
this.headers,
this.params,
}) : assert( baseUrl ! =null, pathUrl ! =null,); HashMap<String.dynamic> toJson() {
return new HashMap<String.dynamic> ().. ['method'] = method .. ['baseUrl'] = baseUrl .. ['pathUrl'] = pathUrl .. ['headers'] = headers ?? {}
..['params'] = params ?? {}; }}// We specify the callback model paradigm
class Response {
final bool success;
final String data; //data returns the result of the request
Response({this.success, this.data});
factory Response.fromNative(dynamic result) {
return Response(
success: result['success'],
data: result['data']); }}Copy the code
Dart layer Network call layer encapsulation
import 'dart:collection';
import 'dart:convert' as JSON;
import 'channel/message_channel_tool.dart';
import 'retrofit_plugin.dart';
///Call the native network library
abstract class RequestBaseLoader<T> {
///Request the path
String get path;
/// Post 或者 Get
Method get method;
///NetConfig key
String get domainKey => null;
///Post the participation
Map<String.dynamic> get data => HashMap();
///header
Map<String.String> get header => HashMap();
NetWorkConfig _getConfig() {
return NetWorkConfigManager.getConfig(domainKey: domainKey);
}
///Obtaining domain name Address
String _getBaseUrl() {
return _getConfig().baseUrl;
}
///The interceptor
NetworkIntercept get intercept => _getConfig().intercept;
///Get the request header
Map<String.String> _getHeaders() {
Map<String.String> headers = _getConfig().headers;
if (headers == null) {
headers = Map(a); }if(header ! =null && header.length > 0) {
header.forEach((key, value) {
headers.putIfAbsent(key, () => value);
});
}
return headers;
}
///Gets the request pass parameter
Map<String.dynamic> _getParams() {
return data ?? {};
}
Request _createReq() {
return Request(
method: method == Method.Post ? "Post" : "Get",
baseUrl: _getBaseUrl(),
pathUrl: path,
headers: _getHeaders(),
params: _getParams(),
);
}
///The request result is MAP
Future<Map<String.dynamic>> request() async {
Request _request = _createReq();
Response response = await NativeRequestTool.doRequest(_request);
// Add response interceptor
if(intercept ! =null) {
intercept.onResponse(_request, response);
}
if (response.success) {
Map<String.dynamic> result;
try {
result = JSON.jsonDecode(response.data);
} catch (_) {
result = {"response": response.data};
}
return result;
} else {
throw Exception(response.data);
}
}
Future<T> execute();
}
enum Method {
Post,
Get,
}
Copy the code
abstract class NetWorkConfigManager {
static HashMap<String, NetWorkConfig> _configs = HashMap();
static void addDefault(NetWorkConfig config) {
addConfig("default", config);
}
static void addConfig(String key, NetWorkConfig config) {
_configs.putIfAbsent(key, () => config);
}
static NetWorkConfig getConfig({String domainKey}) {
var config = _configs[domainKey];
return config == null ? _configs["default"] : config; }}class NetWorkConfig {
final String baseUrl;
final Map<String.String> headers;
final NetworkIntercept intercept;
NetWorkConfig({
this.baseUrl,
this.headers,
this.intercept,
});
}
Copy the code
///Network request interceptor
abstract class NetworkIntercept {
onResponse(Request request, Response response);
}
Copy the code
The plugin code has been uploaded: retrofit_plugin