理解 Flutter 中的 Key - 简书

mikel阅读(637)

来源: 理解 Flutter 中的 Key – 简书

概览

Flutter 中,大概大家都知道如何更新界面视图: 通过修改 State 去触发 Widget 重建,触发和更新的操作是 Flutter 框架做的。 但是有时即使修改了 StateFlutter 框架好像也没有触发 Widget 重建,
其中就隐含了 Flutter 框架内部的更新机制,在某些情况下需要结合使用 Key,才能触发真正的“重建”。
下面将从 3 个方面 (When, Where, Which) 说明如何在合理的时间和地点使用合理的 Key。

When: 什么时候该使用 Key

实战例子

需求: 点击界面上一个按钮,然后交换行中的两个色块。

StatelessWidget 实现

使用 StatelessWidget(StatelessColorfulTile) 做 child(tiles):

class PositionedTiles extends StatefulWidget {
  
  State<StatefulWidget> createState() => PositionedTilesState();
}

class PositionedTilesState extends State<PositionedTiles> {
  List<Widget> tiles;

  
  void initState() {
    super.initState();
    tiles = [
      StatelessColorfulTile(),
      StatelessColorfulTile(),
    ];
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
        body: SafeArea(
            child: Center(
                child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: tiles))),
        floatingActionButton: FloatingActionButton(
            child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles));
  }

当点击按钮时,更新 PositionedTilesState 中储存的 tiles:

void swapTiles() {
    setState(() {
      tiles.insert(1, tiles.removeAt(0));
    });
  }
}
class StatelessColorfulTile extends StatelessWidget {
  final Color color = UniqueColorGenaretor.getColor();

  StatelessColorfulTile({Key key}) : super(key: key);

  
  Widget build(BuildContext context) => buildColorfulTile(color);
}
结果

PositionedTiles

成功实现需求 _

StatefulWidget 实现

使用 StatefulWidget(StatefulColorfulTile) 做 child(tiles):

class StatefulColorfulTile extends StatefulWidget {
  StatefulColorfulTile({Key key}) : super(key: key);

  
  State<StatefulWidget> createState() => StatefulColorfulTileState();
}

class StatefulColorfulTileState extends State<StatefulColorfulTile> {
  // 将 Color 储存在 StatefulColorfulTile 的 State StatefulColorfulTileState 中.
  Color color;

  
  void initState() {
    super.initState();
    color = UniqueColorGenaretor.getColor();
  }

  
  Widget build(BuildContext context) => buildColorfulTile(color);
}

修改外部容器 PositionedTilestiles:

  
  void initState() {
    super.initState();
    tiles = [
      StatefulColorfulTile(),
      StatefulColorfulTile(),
    ];
  }
结果
PositionedTiles

貌似没效果 -_-

为什么使用 StatefulWidget 就不能成功更新呢? 需要先了解下面的内容。

Fluuter 对 Widget 的更新原理

在 Flutter 框架中,视图维持在树的结构中,我们编写的 Widget 一个嵌套一个,最终组合为一个 Tree。

StatelessWidget

在第一种使用 StatelessWidget 的实现中,当 Flutter 渲染这些 Widgets 时,Row Widget 为它的子 Widget 提供了一组有序的插槽。对于每一个 Widget,Flutter 都会构建一个对应的 Element。构建的这个 Element Tree 相当简单,仅保存有关每个 Widget 类型的信息以及对子Widget 的引用。你可以将这个 Element Tree 当做就像你的 Flutter App 的骨架。它展示了 App 的结构,但其他信息需要通过引用原始Widget来查找。

StatelessWidget Tree & Element Tree”

当我们交换行中的两个色块时,Flutter 遍历 Widget 树,看看骨架结构是否相同。它从 Row Widget 开始,然后移动到它的子 Widget,Element 树检查 Widget 是否与旧 Widget 是相同类型和 Key。 如果都相同的话,它会更新对新 widget 的引用。在我们这里,Widget 没有设置 Key,所以Flutter只是检查类型。它对第二个孩子做同样的事情。所以 Element 树将根据 Widget 树进行对应的更新。

swap之后

当 Element Tree 更新完成后,Flutter 将根据 Element Tree 构建一个 Render Object Tree,最终开始渲染流程。

类似这样的渲染流程

StatefulWidget

当使用 StatefulWidget 实现时,控件树的结构也是类似的,只是现在 color 信息没有存储控件自身了,而是在外部的 State 对象中。

StatefulWidget Tree & Element Tree

现在,我们点击按钮,交换控件的次序,Flutter 将遍历 Element 树,检查 Widget 树中 Row 控件并且更新 Element 树中的引用,然后第一个 Tile 控件检查它对应的控件是否是相同类型,它发现对方是相同的类型; 然后第二个 Tile 控件做相同的事情,最终就导致 Flutter 认为这两个控件都没有发生改变。Flutter 使用 Element 树和它对应的控件的 State 去确定要在设备上显示的内容, 所以 Element 树没有改变,显示的内容也就不会改变。

swap之后

StatefullWidget 结合 Key

现在,为 StatefulColorfulTile 传递一个 Key 对象:

void initState() {
  super.initState();
  tiles = [
    // 使用 UniqueKey
    StatefulColorfulTile(key: UniqueKey()),
    StatefulColorfulTile(key: UniqueKey()),
  ];
}

再次运行:

PositionedTiles

成功 swap!

添加了 Key 之后的结构:

PositionedTiles

当现在执行 swap 时, Element 数中 StatafulWidget 控件除了比较类型外,还会比较 key 是否相等:

检查比较

只有类型和key 都匹配时,才算找到对应的 Widget。于是在 Widget Tree 发生交换后,Element Tree 中子控件和原始控件对应关系就被打乱了,所以 Flutter 会重建 Element Tree,直到控件们正确对应上。

重建

所以,现在 Element 树正确更新了,最终就会显示交换后的色块。

 
交换完毕

使用场景

如果要修改集合中的控件的顺序或数量,Key 会很有用。

Where: 在哪设置 Key

正常情况下应该在当前 Widget 树的顶级 Widget 中设置。

回到 StatefulColorfulTile 例子中,为每个色块添加一个 Padding,同时 key 还是设置在相同的地方:


void initState() {
  super.initState();
  tiles = [
    Padding(
      padding: const EdgeInsets.all(8.0),
      child: StatefulColorfulTile(key: UniqueKey()),
    ),
    Padding(
      padding: const EdgeInsets.all(8.0),
      child: StatefulColorfulTile(key: UniqueKey()),
    ),
  ];
}
交换时

当点击按钮发生交换之后,可以看到两个色块的颜色会随机改变,但是我的预期是两个固定的颜色彼此交换。

为什么产生问题

当Widget 树中两个 Padding 发生了交换,它们包裹的色块也就发生了交换:

交换

然后 Flutter 将进行检查,以便对 Element 树进行对应的更新: Flutter 的 Elemetn to Widget 匹配算法将一次只检查树的一个层级:

检查
  1. 在第一级,Padding Widget 都正确匹配。
检查
  1. 在第二级,Flutter 注意到 Tile 控件的 Key 不匹配,就停用该 Tile Element,删除 Widget 和 Element 之间的连接
检查
  1. 我们这里使用的 KeyUniqueKey, 它是一个 LocalKey

LocalKey 的意思是: 当 Widget 与 Element 匹配时,Flutter 只在树中特定级别内查找匹配的 Key。因此 Flutter 无法在同级中找到具有该 Key 的 Tile Widget,所以它会创建一个新 Element 并初始化一个新 State。 就是这个原因,造成色块颜色发生随机改变,每次交换相当于生成了两个新的 Widget。

  1. 解决这个问题: 将 Key 设置到上层 Widget Padding

当 Widget 树中两个 Padding 发生交换之后,Flutter 就能根据 PaddingKey 的变化,更新 Element 树中的两个 Padding,从而实现交换。

 
 void initState() {
   super.initState();
   tiles = [
     Padding(
       key: UniqueKey(),
       padding: const EdgeInsets.all(8.0),
       child: StatefulColorfulTile(),
     ),
     Padding(
       key: UniqueKey(),
       padding: const EdgeInsets.all(8.0),
       child: StatefulColorfulTile(),
     ),
   ];
 }
根据 Padding Key

Which: 该使用哪种类型的 Key

Key 的目的在于为每个 Widget 指明一个唯一的身份,使用何种 Key 就要依具体的使用场景决定。

  • ValueKey

例如在一个 ToDo 列表应用中,每个 Todo Item 的文本是恒定且唯一的。这种情况,适合使用 ValueKey,value 是文本。

  • ObjectKey

假设,每个子 Widget 都存储了一个更复杂的数据组合,比如一个用户信息的地址簿应用。任何单个字段(如名字或生日)可能与另一个条目相同,但每个数据组合是唯一的。在这种情况下, ObjectKey 最合适。

  • UniqueKey

如果集合中有多个具有相同值的 Widget,或者如果您想确保每个 Widget 与其他 Widget 不同,则可以使用 UniqueKey。 在我们的例子中就使用了 UniqueKey,因为我们没有将任何其他常量数据存储在我们的色块上,并且在构建 Widget 之前我们不知道颜色是什么。

不要在 Key 中使用随机数,如果你那样设置,那么当每次构建 Widget 时,都会生成一个新的随机数,Element 树将不会和 Widget 树做一致的更新。

  • GlobalKeys

Global Keys有两种用途。

  • 它们允许 Widget 在应用中的任何位置更改父级而不会丢失 State ,或者可以使用它们在 Widget 树 的完全不同的部分中访问有关另一个 Widget 的信息。
    • 比如: 要在两个不同的屏幕上显示相同的 Widget,同时保持相同的 State,则需要使用 GlobalKeys。
    复用 Widget
  • 在第二种情况下,您可能希望验证密码,但不希望与树中的其他 Widget 共享该状态信息,可以使用 GlobalKey<FromState> 持有一个表单 FormState。 Flutter.dev 上有这个例子Building a form with validation

其实 GlobalKeys 看起来有点像全局变量。有也其他更好的方法达到 GlobalKeys 的作用,比如 InheritedWidget、Redux 或 Block Pattern。

总结

如何合理适当的使用 Key:

  1. When: 当您想要保留 Widget 树的状态时,请使用 Key。例如: 当修改相同类型的 Widget 集合(如列表中)时
  2. Where: 将 Key 设置在要指明唯一身份的 Widget 树的顶部
  3. Which: 根据在该 Widget 中存储的数据类型选择使用的不同类型的Key

参考

上文涉及的例子代码: https://github.com/stefanJi/fullter-playgroud

作者:stefanJi
链接:https://www.jianshu.com/p/6e704112dc67
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Flutter数据存储之shared_preferences - 简书

mikel阅读(607)

来源: Flutter数据存储之shared_preferences – 简书

前言

做过Android开发的人都知道,可以利用SharedPreferences这个轻量级的存储类来保存键值对信息,在Flutter中,我们可以使用shared_preferences库来同时支持Android和ios平台。

参考:
1.《Flutter中的本地存储》

  1. 《Flutter知识点:数据存储之SharedPreferences》
  2. shared_preferences 0.4.2

使用介绍

  1. pubspec.yaml文件中添加依赖

shared_preferences: "^0.4.2"

添加的位置如图所示:

添加依赖
  1. 安装依赖库
    执行$ flutter packages get命令
  2. 在相应文件中导入该库

import 'package:shared_preferences/shared_preferences.dart';
  1. 增删改查
    增:

SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(key, value)
prefs.setBool(key, value)
prefs.setDouble(key, value)
prefs.setInt(key, value)
prefs.setStringList(key, value)

其中key就是你存贮的名称,value就是你存储的值
删:

SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove(key); //删除指定键
prefs.clear();//清空键值对

改:

改和增是一样的,只需要再执行一次setXXX()方法即可覆盖之前的数据。

查:

查询操作的几个API

使用示例

image.png

首先我们创建了一个TextField用来获取用户输入,然后我们再下面定义看了连个按钮,每当当即存储按钮都会触发save() 方法,每当点击获取按钮都会触发get()方法。
先来看看save()方法

save() async{
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString(mUserName, _userNameController.value.text.toString());
}

在上面save方法中我们可以看到我们给它加上了async和await关键字,因为SharedPreferences的存贮也是一个轻量级的耗时操作,所以我们也是需要在异步中进行的。
我们使用SharedPreferences.getInstance()方法来实例化SharedPreferences对象,使用它的setString方法来存储用户输入的字符串。

setString(key, value)

接下来来看下get方法

Future<String> get() async {
  var userName;
    SharedPreferences prefs = await SharedPreferences.getInstance();
    userName = await prefs.getString(mUserName);
  return userName;
}

在get方法中我们同样实例化了一个SharedPreferences对象,并且调用SharedPreferences的getString方法来获取我们存入的对象。
getString(key)
key就是我们刚才存入的值,我们通过这个值可以在本地查找到我们存入的对象并返回。
同样的,get方法也是耗时操作,同样需要异步执行,我们使用async和await来使得get方法异步并返回了一个泛型为String的Future对象。

Future<String> userName = get();
                         userName.then((String userName) {
                           Scaffold.of(context).showSnackBar(
                                SnackBar(content: Text("数据获取成功:$userName")));
                         });

我们使用获得的Future对象调用then()方法,当get方法执行完后就会自动触发then()方法里面的操作弹出showSnackBar。
下面给出完整代码:

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  runApp(new MaterialApp(home: new MyApp()));
}



class MyApp extends StatelessWidget {
  final String mUserName = "userName";
  final _userNameController = new TextEditingController();

  
  Widget build(BuildContext context) {
    save() async{
        SharedPreferences prefs = await SharedPreferences.getInstance();
        prefs.setString(mUserName, _userNameController.value.text.toString());
    }

    Future<String> get() async {
      var userName;

        SharedPreferences prefs = await SharedPreferences.getInstance();
         userName = prefs.getString(mUserName);
      return userName;
    }

    return new Builder(builder: (BuildContext context) {
      return new Scaffold(
        appBar:  AppBar(
          title:  Text("SharedPreferences"),
        ),
        body:  Center(
          child: new Builder(builder: (BuildContext context){
            return
                Column(
                  children: <Widget>[
                     TextField(
                      controller: _userNameController,
                      decoration:  InputDecoration(
                          contentPadding: const EdgeInsets.only(top: 10.0),
                          icon:  Icon(Icons.perm_identity),
                          labelText: "请输入用户名",
                          helperText: "注册时填写的名字"),
                    ),
                    RaisedButton(
                        color: Colors.blueAccent,
                        child: Text("存储"),
                        onPressed: () {
                          save();
                          Scaffold.of(context).showSnackBar(
                              new SnackBar(content:  Text("数据存储成功")));
                        }),
                    RaisedButton(
                        color: Colors.greenAccent,
                        child: Text("获取"),
                        onPressed: () {
                          Future<String> userName = get();
                          userName.then((String userName) {
                            Scaffold.of(context).showSnackBar(
                                 SnackBar(content: Text("数据获取成功:$userName")));
                          });
                        }),
                  ],
                );
          }),
        ),
      );
    });
  }
}

键值对文件存放路径

23人点赞

作者:雇个城管打天下
链接:https://www.jianshu.com/p/735b5684e900
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

【WebAPI No.3】API的访问控制IdentityServer4 - YanBigFeg - 博客园

mikel阅读(530)

来源: 【WebAPI No.3】API的访问控制IdentityServer4 – YanBigFeg – 博客园

介绍:

IdentityServer是一个OpenID Connect提供者 – 它实现了OpenID Connect和OAuth 2.0协议。是一种向客户发放安全令牌的软件。

官网给出的功能解释是:

  • 保护您的资源
  • 使用本地帐户存储或通过外部身份提供商对用户进行身份验证
  • 提供会话管理和单点登录
  • 管理和认证客户
  • 向客户发布身份和访问令牌
  • 验证令牌

IdentityServe4的四种模式:

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

以下是IdentityServer的一个大致流程图:

 

注册IdentityServe4认证服务器:

先在ASP.NET core我们选中空模版。因为本身写的业务也不多,只是为了做token的认证处理,所有建这个做测试比较方便。

创建代码示例:

什么时候都不要忘记添加引用哦:

NuGet命令行:Install-Package IdentityServer4

当然你也可以这样:

然后创建config.cs类来处理我们的一些业务:

复制代码
 //定义范围
        #region 定义要保护的资源(webapi)
        public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>
            {
                new ApiResource("FirstAPI", "API接口安全测试")
            };
        }
        #endregion

        #region 定义可访问客户端
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client
                {
                    //客户端id自定义
                    ClientId = "YbfTest",

                    AllowedGrantTypes = GrantTypes.ClientCredentials, ////设置模式,客户端模式

                    // 加密验证
                    ClientSecrets = new List<Secret>
                    {
                        new Secret("secret".Sha256())
                    },

                    // client可以访问的范围,在GetScopes方法定义的名字。
                    AllowedScopes = new List<string>
                    {
                        "FirstAPI"
                    }
                }
            };
        } 
        #endregion
复制代码

以上就是一个基本的处理类了。然后我们开始在Startup.cs 配置IdentityServer4

复制代码
 public void ConfigureServices(IServiceCollection services)
        {
            services.AddIdentityServer()
                 .AddDeveloperSigningCredential()
                 .AddInMemoryApiResources(Config.GetApiResources())  //配置资源               
                 .AddInMemoryClients(Config.GetClients());//配置客户端
        }
复制代码

复制代码
 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //将IddiTyServer添加到管道中。
            app.UseIdentityServer();

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
复制代码

这样就可以启动项目了,确认项目启动完成后,还要确认服务是否开启成功:在地址后增加地址:/.well-known/openid-configuration 例如:

出现以上结果就是启动成功了。

当然你也可以使用postMan测试工具测试:

需要输入

  • grant_type为客户端授权client_credentials,
  • client_idConfig中配置的ClientId
  • Client_Secret为Config中配置的Secret

例如:

 创建webAPI资源服务器

这个比较简单了,首先创建一个简单的webapi程序即可。

还是老规矩咯iuput,什么时候都不要忘记引用:

通过nuget添加即可:IdentityServer4.AccessTokenValidation

然后点击确定安装就可以了。

主要认证注册服务:

在Startup类里面的ConfigureServices方法里面添加注册

复制代码
        public void ConfigureServices(IServiceCollection services)
        {
            //注册IdentityServer 
            services.AddAuthentication(config => {
                config.DefaultScheme = "Bearer"; //这个是access_token的类型,获取access_token的时候返回参数中的token_type一致
            }).AddIdentityServerAuthentication(option => {
                option.ApiName = "FirstAPI"; //资源名称,认证服务注册的资源列表名称一致,
                option.Authority = "http://127.0.0.1:5000"; //认证服务的url
                option.RequireHttpsMetadata = false; //是否启用https

            });
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }
复制代码

然后在在Startup的Configure方法里配置Authentication中间件:

           //配置Authentication中间件
            app.UseAuthentication();

然后添加一个控制器进行验证测试:

我这里写了一个获取value值简单检测一下。

复制代码
 // GET api/values
        [HttpGet]
        [Authorize]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }
复制代码

这里注意要添加[Authorize]特性。用来做验证是否有权限的。没有的话,以上做的没有意义。需要引用命名空间:using Microsoft.AspNetCore.Authorization;

看一下正确的请求结果:

如果不传token值请求:

 

 

注意这里就会返回401的未授权状态。

创建Client(客户端)

上面我们使用的是postman请求的以演示程序是否创建成功,这里我们假设一个用户的使用客户端,这里我们创建一个控制台来模拟一下真实的小场景。

既然是控制台就没什么好说的直接上代码main函数:

复制代码
Task.Run(async () =>
            {
                //从元数据中发现终结点,查找IdentityServer
                var disco = await DiscoveryClient.GetAsync("http://127.0.0.1:5000");
                if (disco.IsError)
                {
                    Console.WriteLine(disco.Error);
                    return;
                }

                //向IdentityServer请求令牌
                var tokenClient = new TokenClient(disco.TokenEndpoint, "YbfTest", "YbfTest123");//请求的客户资源
                var tokenResponse = await tokenClient.RequestClientCredentialsAsync("FirstAPI");//运行的范围

                if (tokenResponse.IsError)
                {
                    Console.WriteLine(tokenResponse.Error);
                    return;
                }

                Console.WriteLine(tokenResponse.Json);

                //访问Api
                var client = new HttpClient();
                //把令牌添加进请求
                //client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",tokenResponse.AccessToken);
                //client.SetBearerToken(tokenResponse.AccessToken);
                client.SetToken("Bearer", tokenResponse.AccessToken);

                var response = await client.GetAsync("http://localhost:42033/api/values");
                if (!response.IsSuccessStatusCode)
                {
                    Console.WriteLine(response.StatusCode);
                }
                else
                {
                    var content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine(JArray.Parse(content));
                }
            });

            Console.ReadLine();
复制代码

这里主要介绍一下请求资源时添加令牌主要有三种形式,我都在代码给出,根据api资源的注册形式选择适合的。api的注册我也写了两种形式。主要的区别就是token前面的Bearer,如果想写成自定义的和默认成Bearer就是这里的区分。

自定义就要使用:

client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",tokenResponse.AccessToken);

如果全局默认的形式就不比每次请求都要添加所以可以写成:

client.SetBearerToken(tokenResponse.AccessToken);

运行项目之后出现成功界面:

 传送门

WebApi系列文章目录介绍

Flutter网络请求库DIO入门文档 - 掘金

mikel阅读(650)

来源: Flutter网络请求库DIO入门文档 – 掘金

一、dart:io提供的HttpClient

1、支持常用的Http操作,比如get,post等

2、异步操作,在io.dart中有相关描述

  • This library allows you to work with files, directories,
  • sockets, processes, HTTP servers and clients, and more.
  • Many operations related to input and output are asynchronous
  • and are handled using [Future]s or [Stream]s, both of which
  • are defined in the [dart:async
  • library](../dart-async/dart-async-library.html).
  • To use the dart:io library in your code:
  • import 'dart:io';
    复制代码

3、网络调用通常遵循如下步骤:

创建 client. 构造 Uri. 发起请求, 等待请求,同时您也可以配置请求headers、 body。 关闭请求, 等待响应. 解码响应的内容. 4、示例,(httpbin.org 这个网站能测试 HTTP 请求和响应的各种信息,比如 cookie、ip、headers 和登录验证等,且支持 GET、POST 等多种方法)

_httpTest() async {
    var url = 'https://httpbin.org/ip';
    var httpClient = new HttpClient();
 
    String result;
    try {
      var request = await httpClient.getUrl(Uri.parse(url));
      var response = await request.close();
      if (response.statusCode == HttpStatus.ok) {
        var json = await response.transform(utf8.decoder).join();
        var data = jsonDecode(json);
        result = data['origin'];
        print(result);
      } else {
        result =
            'Error getting IP address:\nHttp status ${response.statusCode}';
      }
    } catch (exception) {
      result = 'Failed getting IP address';
    }
  }
输出结果:122.70.159.214

复制代码

二、dio

dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等

1、安装

dependencies:

dio: ^3.0.10

2、常用请求

以下实验基于Charles的MapLocal,json文件内容为{“animal”:”dog”}

– get

Response response;
Dio dio = _dio();
response = await dio.get("http://test?id=1&name=dio1&method=get");
 
// 请求参数也可以通过对象传递,上面的代码等同于:
response = await dio.get("http://test", queryParameters: {"id": 2, "name": "dio2"});
 
print(response.data['animal']);
复制代码

– post

Response response;
Dio dio = _dio();
response = await dio.post("http://test?id=1&name=dio1&method=post");
 
// 请求参数也可以通过对象传递,上面的代码等同于:
response = await dio.post("http://test", queryParameters: {"id": 2, "name": "dio2"});
 
print(response.data['animal']);
 
//监听接收数据进度
response = await dio.post(
    "http://test",
    queryParameters: {"id": 2, "name": "dio2"},
    onReceiveProgress: (int receive, int total) {
      print("$receive $total");
   },
);
复制代码

– 发起多个请求

Dio dio = _dio();
Future.wait([dio.post("http://test/test1"), dio.get("http://test/test2")]).then((e) {
   print(e);
}).catchError((e) {});
 
 
结果为[{"animal":"dog"}, {"animal":"dog"}]
复制代码

– 下载文件

Dio dio = _dio();
Response response =
await dio.download("https://www.baidu.com/", "assets/data/test.html");
复制代码

– 上传文件

Response response;
Dio dio = _dio();
FormData formData;
formData = FormData.fromMap({
   "animal": "dog",
});
response = await dio.post("http/test/upload", data: formData);
 
//上传多个文件
formData = FormData.fromMap({
   "animal": "dog",
   "files": [
       await MultipartFile.fromFile("assets/data/test1.json", filename: "test1.json"),
       await MultipartFile.fromFile("assets/data/test2.json", filename: "test2.json"),
     ]
});
response = await dio.post("http/test/upload", data: formData);
复制代码

3、配置dio

Dio dio = Dio();
// 你可以使用默认配置或传递一个可选 BaseOptions参数来创建一个Dio实例 :
// 配置dio实例
dio.options.baseUrl = "https://www.xx.com/api";
dio.options.connectTimeout = 5000; //5s
dio.options.receiveTimeout = 3000;
 
// 或者通过传递一个 `options`来创建dio实例
BaseOptions options = BaseOptions(
  baseUrl: "https://www.xx.com/api",
  connectTimeout: 5000,
  receiveTimeout: 3000,
);
dio = Dio(options);
复制代码

4、请求配置

BaseOptions描述的是Dio实例发起网络请求的的公共配置,而Options类描述了每一个Http请求的配置信息,每一次请求都可以单独配置,单次请求的Options中的配置信息可以覆盖BaseOptions中的配置,下面是BaseOptions的配置项:
{
  /// Http method.
  String method;
 
  /// 请求基地址,可以包含子路径,如: "https://www.google.com/api/".
  String baseUrl;
 
  /// Http请求头.
  Map<String, dynamic> headers;
 
  /// 连接服务器超时时间,单位是毫秒.
  int connectTimeout;
  /// 2.x中为接收数据的最长时限.
  int receiveTimeout;
 
  /// 请求路径,如果 `path` 以 "http(s)"开始, 则 `baseURL` 会被忽略; 否则,
  /// 将会和baseUrl拼接出完整的的url.
  String path = "";
 
  /// 请求的Content-Type,默认值是"application/json; charset=utf-8".
  /// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据,
  /// 可以设置此选项为 `Headers.formUrlEncodedContentType`,  这样[Dio]
  /// 就会自动编码请求体.
  String contentType;
 
  /// [responseType] 表示期望以那种格式(方式)接受响应数据。
  /// 目前 [ResponseType] 接受三种类型 `JSON`, `STREAM`, `PLAIN`.
  ///
  /// 默认值是 `JSON`, 当响应头中content-type为"application/json"时,dio 会自动将响应内容转化为json对象。
  /// 如果想以二进制方式接受响应数据,如下载一个二进制文件,那么可以使用 `STREAM`.
  ///
  /// 如果想以文本(字符串)格式接收响应数据,请使用 `PLAIN`.
  ResponseType responseType;
 
  /// `validateStatus` 决定http响应状态码是否被dio视为请求成功, 返回`validateStatus`
  ///  返回`true` , 请求结果就会按成功处理,否则会按失败处理.
  ValidateStatus validateStatus;
 
  /// 用户自定义字段,可以在 [Interceptor]、[Transformer] 和 [Response] 中取到.
  Map<String, dynamic> extra;
 
  /// Common query parameters
  Map<String, dynamic /*String|Iterable<String>*/ > queryParameters;
}
复制代码

5、响应数据

当请求成功时会返回一个Response对象,它包含如下字段:
{
  /// 响应数据,可能已经被转换了类型, 详情请参考Options中的[ResponseType].
  T data;
  /// 响应头
  Headers headers;
  /// 本次请求信息
  Options request;
  /// Http status code.
  int statusCode;
  /// 是否重定向(Flutter Web不可用)
  bool isRedirect;
  /// 重定向信息(Flutter Web不可用)
  List<RedirectInfo> redirects ;
  /// 真正请求的url(重定向最终的uri)
  Uri realUri;
  /// 响应对象的自定义字段(可以在拦截器中设置它),调用方可以在`then`中获取.
  Map<String, dynamic> extra;
}
复制代码

6、拦截器

我们可以通过继承Interceptor来实现自定义的拦截器

每个 Dio 实例都可以添加任意多个拦截器,他们组成一个队列,拦截器队列的执行顺序是FIFO。通过拦截器你可以在请求之前或响应之后(但还没有被 then 或 catchError处理)做一些统一的预处理操作。
dio.interceptors.add(InterceptorsWrapper(
    onRequest:(RequestOptions options) async {
     // 在请求被发送之前做一些事情
     return options; //continue
     // 如果你想完成请求并返回一些自定义数据,可以返回一个`Response`对象或返回`dio.resolve(data)`。
     // 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义数据data.
     //
     // 如果你想终止请求并触发一个错误,你可以返回一个`DioError`对象,或返回`dio.reject(errMsg)`,
     // 这样请求将被中止并触发异常,上层catchError会被调用。
    },
    onResponse:(Response response) async {
     // 在返回响应数据之前做一些预处理
     return response; // continue
    },
    onError: (DioError e) async {
      // 当请求失败时做一些预处理
     return e;//continue
    }
));
复制代码

7、拦截器中可以进行其他异步操作

dio.interceptors.add(InterceptorsWrapper(
    onRequest:(Options options) async{
        //...If no token, request token firstly.
        Response response = await dio.get("/token");
        //Set the token to headers
        options.headers["token"] = response.data["data"]["token"];
        return options; //continue
    }
));
复制代码

8、Lock/unlock拦截器

你可以通过调用拦截器的 lock()/unlock 方法来锁定/解锁拦截器。一旦请求/响应拦截器被锁定,接下来的请求/响应将会在进入请求/响应拦截器之前排队等待,直到解锁后,这些入队的请求才会继续执行(进入拦截器)。这在一些需要串行化请求/响应的场景中非常实用,后面我们将给出一个示例。
tokenDio = Dio(); //Create a instance to request the token.
tokenDio.options = dio.options;
dio.interceptors.add(InterceptorsWrapper(
    onRequest:(Options options) async {
        // If no token, request token firstly and lock this interceptor
        // to prevent other request enter this interceptor.
        dio.interceptors.requestLock.lock();
        // We use a Dio(to avoid dead lock) instance to request token.
        Response response = await tokenDio.get("/token");
        //Set the token to headers
        options.headers["token"] = response.data["data"]["token"];
        dio.interceptors.requestLock.unlock();
        return options; //continue
    }
));
假设这么一个场景:我们需要给每个请求头中设置token,如果token不存在我们需要先请求token,获取到再继续请求,由于请求token过程是异步的,所以我们需要先锁定拦截器防止其他请求在没有获取到token的情况下进行网络请求,获取到token再解锁
复制代码

9、clear()方法来清空等待队列

dio.interceptors.clear()
复制代码

10、日志(开启后会打印request和response相关信息)

//由于拦截器队列的执行顺序是FIFO,如果把log拦截器添加到了最前面,则后面拦截器对options的更改就不会被打印(但依然会生效), 所以建议把log拦截添加到队尾。
dio.interceptors.add(LogInterceptor(responseBody: false)); //开启请求日志
复制代码

11、DioError

{
  /// Request info.
  RequestOptions request;
 
  /// Response info, it may be `null` if the request can't reach to
  /// the http server, for example, occurring a dns error, network is not available.
  Response response;
 
  /// 错误类型,见下文
  DioErrorType type;
 
  ///原始的error或exception对象,通常type为DEFAULT时存在。
  dynamic error;
}
 
enum DioErrorType {
  /// It occurs when url is opened timeout.
  CONNECT_TIMEOUT,
 
  /// It occurs when url is sent timeout.
  SEND_TIMEOUT,
 
  ///It occurs when receiving timeout.
  RECEIVE_TIMEOUT,
 
  /// When the server response, but with a incorrect status, such as 404, 503...
  RESPONSE,
 
  /// When the request is cancelled, dio will throw a error with this type.
  CANCEL,
   
  /// Default error type, Some other Error. In this case, you can
  /// read the DioError.error if it is not null.
  DEFAULT
}

复制代码

12、CancelToken,取消请求

CancelToken token = CancelToken();
dio.post("/testpost?id=1&name=dio1&method=post",cancelToken: token).catchError((e) {
//我们会发现CancelToken提供了错误类型的判断,即此时CancelToken.isCancel(err)是true
//如
if (CancelToken.isCancel(err)) {
   print("被取消啦");
}
 
 
}).then((data) {
  return data;
});
token.cancel();
 
 
cancel_token.dart中源码也是判断DioErrorType
static bool isCancel(DioError e) {
  return e.type == DioErrorType.CANCEL;
}
复制代码

13、dio和HttpClient关系

HttpClientAdapter是 Dio 和 HttpClient之间的桥梁。2.0抽象出adapter主要是方便切换、定制底层网络库。Dio实现了一套标准的、强大API,而HttpClient则是真正发起Http请求的对象。我们通过HttpClientAdapter将Dio和HttpClient解耦,这样一来便可以自由定制Http请求的底层实现,比如,在Flutter中我们可以通过自定义HttpClientAdapter将Http请求转发到Native中,然后再由Native统一发起请求。再比如,假如有一天OKHttp提供了dart版,你想使用OKHttp发起http请求,那么你便可以通过适配器来无缝切换到OKHttp,而不用改之前的代码。 Dio 使用DefaultHttpClientAdapter作为其默认HttpClientAdapter,DefaultHttpClientAdapter使用dart:io:HttpClient 来发起网络请求。

扩展(适配器模式) 首页定义接口,接口中对要实现的功能加以抽象,然后定义不同的Adapter类来实现这个接口,Adapter类中是对接口中方法的不同实现,上层的调用代码不需要改变就可以随意切换对底层不同的功能调用。

14、设置代理

DefaultHttpClientAdapter 提供了一个onHttpClientCreate 回调来设置底层 HttpClient的代理,我们想使用代理,可以参考下面代码:
import 'package:dio/dio.dart';
import 'package:dio/adapter.dart';
...
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
    // config the http client
    client.findProxy = (uri) {
        //proxy all request to localhost:8888
        return "PROXY localhost:8888";
    };
    // you can also create a HttpClient to dio
    // return HttpClient();
};
复制代码

15、部分源码分析

dio.dart中
 
网络请求最终会调用到_request方法
当Response的泛型类为String且声明的ResponseType不为bytes和stream时
mergeOptions是将Dio的BaseOptions属性结合请求参数Options来生成一个RequestOptions对象,是最终发起网络请求的Options
Future<Response<T>> _request<T>(
    String path, {
    data,
    Map<String, dynamic> queryParameters,
    CancelToken cancelToken,
    Options options,
    ProgressCallback onSendProgress,
    ProgressCallback onReceiveProgress,
  }) async {
    if (_closed) {
      throw DioError(error: "Dio can't establish new connection after closed.");
    }
    options ??= Options();
    if (options is RequestOptions) {
      data = data ?? options.data;
      queryParameters = queryParameters ?? options.queryParameters;
      cancelToken = cancelToken ?? options.cancelToken;
      onSendProgress = onSendProgress ?? options.onSendProgress;
      onReceiveProgress = onReceiveProgress ?? options.onReceiveProgress;
    }
    var requestOptions = mergeOptions(options, path, data, queryParameters);
    requestOptions.onReceiveProgress = onReceiveProgress;
    requestOptions.onSendProgress = onSendProgress;
    requestOptions.cancelToken = cancelToken;
    if (T != dynamic &&
        !(requestOptions.responseType == ResponseType.bytes ||
            requestOptions.responseType == ResponseType.stream)) {
      if (T == String) {
        requestOptions.responseType = ResponseType.plain;
      } else {
        requestOptions.responseType = ResponseType.json;
      }
    }
 
 
 
拦截器会判断
checkIfNeedEnqueue方法的作用就是判断是否有未处理完(判断是否处理完从而加锁是通过Completer实现的)的请求,如果有的话,本次请求需要排队等待之前的请求的完成(值得学习的是由于一次请求返回的是Future,所以这里利用了future.then((Callback))返回的还是一个Future对象的特点,巧妙的实现了多次请求的顺序执行而相互之间不会干扰),这里的then中的Callback回调就是checkIfNeedEnqueue的第二个参数。
 
// Convert the request/response interceptor to a functional callback in which
    // we can handle the return value of interceptor callback.
    Function _interceptorWrapper(interceptor, bool request) {
      return (data) async {
        var type = request ? (data is RequestOptions) : (data is Response);
        var lock =
            request ? interceptors.requestLock : interceptors.responseLock;
        if (_isErrorOrException(data) || type) {
          return listenCancelForAsyncTask(
            cancelToken,
            Future(() {
              return checkIfNeedEnqueue(lock, () {
                if (type) {
                  if (!request) data.request = data.request ?? requestOptions;
                  return interceptor(data).then((e) => e ?? data);
                } else {
                  throw assureDioError(data, requestOptions);
                }
              });
            }),
          );
        } else {
          return assureResponse(data, requestOptions);
        }
      };
    }
 
 
//如果拦截器被锁,接下来的request/response任务会进入一个队列,否则继续执行
 
FutureOr checkIfNeedEnqueue(Lock lock, EnqueueCallback callback) {
    if (lock.locked) {
      return lock.enqueue(callback);
    } else {
      return callback();
    }
  }
 
 
  Future enqueue(EnqueueCallback callback) {
    if (locked) {
      // we use a future as a queue
      return _lock.then((d) => callback());
    }
    return null;
  }
 
 
//真正的网络请求,使用httpClientAdapter
//请求结果拿到后会查看interceptors.responseLock
//会监听是否有取消操作
// Initiate Http requests
  Future<Response<T>> _dispatchRequest<T>(RequestOptions options) async {
    var cancelToken = options.cancelToken;
    ResponseBody responseBody;
    try {
      var stream = await _transformData(options);
      responseBody = await httpClientAdapter.fetch(
        options,
        stream,
        cancelToken?.whenCancel,
      );
      responseBody.headers = responseBody.headers ?? {};
      var headers = Headers.fromMap(responseBody.headers ?? {});
      var ret = Response(
        headers: headers,
        request: options,
        redirects: responseBody.redirects ?? [],
        isRedirect: responseBody.isRedirect,
        statusCode: responseBody.statusCode,
        statusMessage: responseBody.statusMessage,
        extra: responseBody.extra,
      );
      var statusOk = options.validateStatus(responseBody.statusCode);
      if (statusOk || options.receiveDataWhenStatusError) {
        var forceConvert = !(T == dynamic || T == String) &&
            !(options.responseType == ResponseType.bytes ||
                options.responseType == ResponseType.stream);
        String contentType;
        if (forceConvert) {
          contentType = headers.value(Headers.contentTypeHeader);
          headers.set(Headers.contentTypeHeader, Headers.jsonContentType);
        }
        ret.data = await transformer.transformResponse(options, responseBody);
        if (forceConvert) {
          headers.set(Headers.contentTypeHeader, contentType);
        }
      } else {
        await responseBody.stream.listen(null).cancel();
      }
      checkCancelled(cancelToken);
      if (statusOk) {
        return checkIfNeedEnqueue(interceptors.responseLock, () => ret);
      } else {
        throw DioError(
          response: ret,
          error: 'Http status error [${responseBody.statusCode}]',
          type: DioErrorType.RESPONSE,
        );
      }
    } catch (e) {
      throw assureDioError(e, options);
    }
  }
 
 
 
//在io_adapter中
//dio中默认的adapter是DefaultHttpClientAdapter,其中HttpClient _defaultHttpClient,所以dio中的网络请求与底层HttpClient是通过DefaultHttpClientAdapter中对HttpClient网络请求的实现过程
 
class DefaultHttpClientAdapter implements HttpClientAdapter {
  /// [Dio] will create HttpClient when it is needed.
  /// If [onHttpClientCreate] is provided, [Dio] will call
  /// it when a HttpClient created.
  OnHttpClientCreate onHttpClientCreate;
 
  HttpClient _defaultHttpClient;
 
  bool _closed = false;
 
  @override
  Future<ResponseBody> fetch(
    RequestOptions options,
    Stream<List<int>> requestStream,
    Future cancelFuture,
  ) async {
    if (_closed) {
      throw Exception(
          "Can't establish connection after [HttpClientAdapter] closed!");
    }
    var _httpClient = _configHttpClient(cancelFuture, options.connectTimeout);
    Future requestFuture = _httpClient.openUrl(options.method, options.uri);
.....(略)
}
复制代码

16、扩展:为什么需要通过代码设置代理才可以使用charles抓包呢?

因为当我们启动 Charles就是启动了一个HTTP代理服务器,这类工具会通知操作系统,“现在我在系统上创建了一个HTTP代理,IP为XXXXXX端口为XX。这个时候运行在系统上的http客户端再去发送请求的时候,他就不会再去进行DNS解析,去连接目标服务器,而是直接连接系统告诉他代理所在的地址。然后代理服务器会与客户端建立连接,再然后代理服务器根据请求信息再去连接真正的服务器。而在Flutter中的http.dart有如下说明

/**

  • Sets the function used to resolve the proxy server to be used for
  • opening a HTTP connection to the specified [url]. If this
  • function is not set, direct connections will always be used.
  • The string returned by [f] must be in the format used by browser
  • PAC (proxy auto-config) scripts. That is either
  • "DIRECT"
    复制代码
  • for using a direct connection or
  • "PROXY host:port"
    复制代码
  • for using the proxy server [:host:] on port [:port:].
  • A configuration can contain several configuration elements
  • separated by semicolons, e.g.
  • "PROXY host:port; PROXY host2:port2; DIRECT"
    复制代码
  • The static function [findProxyFromEnvironment] on this class can
  • be used to implement proxy server resolving based on environment
  • variables. */ set findProxy(String f(Uri url));

//所以如果我们没有通过代码进行设置,就会直接请求到真正的服务器而不会走代理服务器

作者:wjq821
链接:https://juejin.cn/post/6959736964597678116
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

flutter使用http.dart处理网络请求_Walden_tinghou的博客-CSDN博客_dart 网络请求

mikel阅读(468)

来源: flutter使用http.dart处理网络请求_Walden_tinghou的博客-CSDN博客_dart 网络请求

1.flutter使用http.dart处理网络请求,首先要去这里查看最新版本 http.dart
接着在如图所示的地方填写最新的版本然后导入,这样就可以在项目中使用http.dart了在这里插入图片描述

2.使用http.dart来处理网络请求,首先要在要用的文件中引入头文件,以后就可以使用http来发送请求

import ‘package:http/http.dart’ as http;

在这里插入图片描述

3.使用http处理项目网络请求

Future<List<Chat>> getDatas() async {
//不再是取消连接了!
_cancleConnect = false;
final response = await http
.get(Uri.parse(‘http://rap2api.taobao.org/app/mock/298265/api/chat/list’));
if (response.statusCode == 200) {
//获取相应数据,并转成Map类型!
final responseBody = json.decode(response.body);
//转模型数组 map中遍历的结果需要返回出去
List<Chat> chatList = responseBody[‘chat_list’].map<Chat>((item) {
return Chat.fromJson(item);
}).toList();
return chatList;
} else {
throw Exception(‘statusCode:${response.statusCode}’);
}
}

还可以使用如下代码打印请求返回的数据,便于我们开发调试

print(response.body);

在这里插入图片描述
————————————————
版权声明:本文为CSDN博主「Walden_tinghou」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sunnyboy9/article/details/122968023

dart - 执行 POST 请求时如何解决 Flutter CERTIFICATE_VERIFY_FAILED 错误? - IT工具网

mikel阅读(508)

来源: dart – 执行 POST 请求时如何解决 Flutter CERTIFICATE_VERIFY_FAILED 错误? – IT工具网

我正在 Dart 中发送一个发布请求。当我在 Postman 等 API 测试工具上对其进行测试时,它会给出响应。但是当我运行应用程序时。它给了我以下错误:-

E/flutter ( 6264): HandshakeException: Handshake error in client (OS Error: E/flutter ( 6264):  CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:363))

这是我的函数代码-

Future getAccessToken(String url) async {

    try {
      http.post('url',
          body: {
            "email": "xyz@xyz.example",
            "password": "1234"
          }).then((response) {
        print("Reponse status : ${response.statusCode}");
        print("Response body : ${response.body}");
        var myresponse = jsonDecode(response.body);
        String token = myresponse["token"];
      });
    } catch (e) {
      print(e.toString());
    }

这是完整的错误正文:

E/flutter ( 6264): [ERROR:flutter/shell/common/shell.cc(184)] Dart Error: Unhandled exception: E/flutter ( 6264): HandshakeException: Handshake error in client (OS Error: E/flutter ( 6264):   CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:363)) E/flutter ( 6264): #0      IOClient.send (package:http/src/io_client.dart:33:23) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #1      BaseClient._sendUnstreamed (package:http/src/base_client.dart:169:38) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #2     BaseClient.post (package:http/src/base_client.dart:54:7) E/flutter ( 6264): #3      post.<anonymous closure> (package:http/http.dart:70:16) E/flutter ( 6264): #4      _withClient (package:http/http.dart:166:20) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #5     post (package:http/http.dart:69:5) E/flutter ( 6264): #6
_MyLoginFormState.getAccessToken (package:chart/main.dart:74:7) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #7
_MyLoginFormState.build.<anonymous closure> (package:chart/main.dart:64:29)

 

最佳答案

 

为了让 Flutter/Dart 的新手更清楚,为了在项目中全局启用此选项,您需要执行以下操作:

  1. 在您的 main.dart 文件中,添加或导入以下类:
 class MyHttpOverrides extends HttpOverrides{
  @override
  HttpClient createHttpClient(SecurityContext? context){
    return super.createHttpClient(context)
      ..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
  }
}
  1. 在您的主函数中,在函数定义之后添加以下行:

HttpOverrides.global = MyHttpOverrides();

This评论对解决这个问题很有帮助,请注意…

This should be used while in development mode, do NOT do this when you want to release to production, the aim of this answer is to make the development a bit easier for you, for production, you need to fix your certificate issue and use it properly, look at the other answers for this as it might be helpful for your case.

 

关于dart – 执行 POST 请求时如何解决 Flutter CERTIFICATE_VERIFY_FAILED 错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54285172/

Android模拟器访问本地Web API - 简书

mikel阅读(519)

来源: Android模拟器访问本地Web API – 简书

刚开始做Android APP(使用Android Studio)开发,好多坑啊~ 我在本机同时开发Web API和Android APP,在安卓模拟器里调用Web API的时候okhttp总是报错说无法连接获取数据。

http://localhost/api/testhttp://192.168.0.8/api/test 都无法连接。

折腾了半天,包括修改本机的HOST文件,发现安卓虚拟机访问网络不受本机HOST文件的影响。这个时候才意识到,安卓虚拟机的本地网络跟我的电脑本地网络不同。

查了官方文档:Set up Android Emulator networkinghttps://developer.android.com/studio/run/emulator-networking.html

 

由此得知,在安卓模拟器上访问本地Web API的地址应该这样写:

http://10.0.2.2/api/test

1人点赞

作者:StormerX
链接:https://www.jianshu.com/p/028436e647d5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

android 模拟器访问本地服务器接口api_再回首我心依旧的博客-CSDN博客

mikel阅读(407)

来源: android 模拟器访问本地服务器接口api_再回首我心依旧的博客-CSDN博客

Android 模拟器访问本地api

最近因为公司网络的原因,暂时使用模拟器来进行调试(话说现在都是真机调试,基本上也不怎么使用模拟器了),在访问服务器接口时 遇到点问题。

 

org.apache.http.conn.HttpHostConnectException: Connection to http://192.168.2.23:80 refused

 

 

解决方案如下:

1,使用 win+r  ,调处cmd 命令窗口

 

进入D:\DeveloperSoft\eclipseADT\Android-sdk-windows\platform-tools  路径下

运行adb shell

然后在运行getprop,结果如下:

2、得到模拟器的DNS地址

在结果里可以看到:

[net.dns1]: [10.0.2.3]

[net.dns2]: [10.0.2.4]

3、把dns改成我们自己的DNS

setprop net.dns1 192.168.1.1

 

如图

 

4,启动模拟器,然后进入 Settings->Wireless & networks->Mobile networks->Access Point Names

然后打开出现在列表中的access point;

然后下面这样设置:

– 代理: 你的本机ip地址

– 端口: 端口 一般80/8080

– 用户名: 如有需要添加 否则为null

– 密码: 如有需要添加 否则为null

 

如图所示

 

 

好,这样就可以使用模拟器来访问本机api了。

————————————————

版权声明:本文为CSDN博主「再回首我心依旧」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/wuzhonghua1009/article/details/51200933

报错:flutter: Connection refused, errno = 111, address = 127.0.0.1, port = 38870_TonyChen-9102的博客-CSDN博客

mikel阅读(440)

来源: 报错:flutter: Connection refused, errno = 111, address = 127.0.0.1, port = 38870_TonyChen-9102的博客-CSDN博客

报错:
flutter: Connection refused, errno = 111, address = 127.0.0.1, port = 38870

解决方法:
原因原来是127.0.0.1 和localhost 指向的是虚拟机的地址,并非电脑本地。

https://stackoverflow.com/questions/6760585/accessing-localhostport-from-android-emulator

所以我们只需要修改接口的127.0.0.1或localhost就行了;在Android操作系统中,将本地电脑映射为10.0.2.2;亦或使用电脑ip地址。

I am using Windows 10 as my development platform, accessing 10.0.2.2:port in my emulator is not working as expected, and the same result for other solutions in this question as well.

After several hours of digging, I found that if you add -writable-system argument to the emulator startup command, things will just work.

You have to start an emulator via command line like below:

 emulator.exe -avd <emulator_name> -writable-system

Then in your emulator, you can access your API service running on host machine, using LAN IP address and binding port:

http://192.168.1.2:<port>

Hope this helps you out.

About start emulator from command line: https://developer.android.com/studio/run/emulator-commandline.

I had the same issue when I was trying to connect to my IIS .NET Webservice from the Android emulator.

  1. install npm install -g iisexpress-proxy
  2. iisexpress-proxy 53990 to 9000 to proxy IIS express port to 9000 and access port 9000 from emulator like "http://10.0.2.2:9000"

the reason seems to be by default, IIS Express doesn’t allow connections from network https://forums.asp.net/t/2125232.aspx?Bad+Request+Invalid+Hostname+when+accessing+localhost+Web+API+or+Web+App+from+across+LAN