Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

第二章、IPC机制

$
0
0

IPC 机制

IPC场景:只有在多进程情况下才会考虑使用进程间通信。
给四大组件在AndroidMenifest中指定android:process属性来开启多线程模式

使用android:process(进程)会带来的问题
1、静态成员和单例模式完全失效;
2、线程同步机制完全失效
3、SharedPreferences可靠性下降;
4、Application会多次创建;
Binder的工作机制
Binder的工作机制图
Android中的IPC方式
Bundle
文件共享(一般使用文件读写来实现共享,不建议使用系统的SharedPreferences)
Messenger(轻量级IPC,底层依然是AIDL)工作原理(Page70 & 代码)
这里写图片描述
AIDL
4.1. AIDL支持的数据类型:
基本数据类型(int、long、char、boolean、double等);
String和CharSequence;
List只支持ArrayList,里面每个元素都必须被AIDL支持;
Map只支持HashMap,里面每个元素都必须被AIDL支持(包括key和value);
Parcelable 所有实现了Parcelable接口对象;
AIDL接口本身;
4.2. 服务端可以使用CopyOnWriteArrayList和ConcurrentHashMap来进行自动线程同步,客户端拿到的依然是ArrayList和HashMap;
4.3. 服务端和客户端之间做监听器,服务端需要使用RemoteCallbackList,否则客户端的监听器无法收到通知(因为服务端实质还是一份新的序列化后的监听器实例,并不是客户端那份);
dd. 客户端调用远程服务方法时,因为远程方法运行在服务端的binder线程池中,同时客户端线程会被挂起,所以如果该方法过于耗时,而客户端又是UI线程,会导致ANR,所以当确认该远程方法是耗时操作时,应避免客户端在UI线程中调用该方法。同理,当服务器调用客户端的listener方法时,该方法也运行在客户端的binder线程池中,所以如果该方法也是耗时操作,请确认运行在服务端的非UI线程中。另外,因为客户端的回调listener运行在binder线程池中,所以更新UI需要用到handler。
4.4. 客户端通过IBinder.DeathRecipient来监听Binder死亡,也可以在onServiceDisconnected中监听并重连服务端。区别在于前者是在binder线程池中,访问UI需要用Handler,后者则是UI线程。
4.5. 可通过自定义权限在onBind或者onTransact中进行权限验证。

ContentProvider
ContentProvider底层实现也是Binder ,ContentProvider我们都很熟悉了但是它的细节还是很多的,比如CRUD操作、防止SQL注入和权限控制等。
ContentProvider使用表的形式来组织数据无论数据的来源是什么,ContentProvider都会认为是一种表,然后把数据组织成表格
ContentProvider提供的方法
   query:查询
   insert:插入
   update:更新
   delete:删除
   getType:得到数据类型
   onCreate:创建数据时调用的回调函数
每个ContentProvider都有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据。Android所提供的ContentProvider都存放在android.provider包当中

Socket (套接字)一般用于网络通信,AIDL用这种方式会过于繁琐,不建议。

Binder连接池,通过BinderPool的方式将Binder的控制与Service本身解耦,同时只需要维护一份Service即可。这里用到了CountDownLatch,大概解释下用意:线程在await后等待,直到CountDownLatch的计数为0,BinderPool里使用它的目的是为了保证Activity获取BinderPool的时候Service已确定bind完成

作者:Army_Jun 发表于2017/7/28 15:28:45 原文链接
阅读:47 评论:0 查看评论

Flutter实战一Flutter聊天应用(十五)

$
0
0

在上一篇文章《Flutter实战一Flutter聊天应用(十四)》中,我们完成了注册屏幕。为了保持应用程序入口只有一个,即登陆屏幕,用户注册完成之后会返回手机号码、密码到登陆屏幕,让用户点击登陆按钮就可以直接登陆应用程序了。现在我们就来完善一下登陆屏幕。

应用程序的项目已经上传到GitHub上,大家可以直接查看sign_in.dart文件的代码。

我们回到sign_in.dart文件,在_SignInState中修改一下_openSignUp方法,以接收注册屏幕返回的手机号码、登陆密码。同时,使用SnackBar控件在屏幕底部短暂显示轻量级的提示,告知用户注册成功。还会自动填入对应输入框,让用户不需要重复输入。

class _SignInState extends State<SignIn> {
  //...
  static final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
  //...
  void _openSignUp() {
    setState(() {
      Navigator.of(context).push(new MaterialPageRoute<List<String>>(
        builder: (BuildContext context) {
          return new SignUp();
        },
      )).then((onValue) {
        if (onValue != null) {
          _phoneController.text = onValue[0];
          _passwordController.text = onValue[1];
          FocusScope.of(context).requestFocus(new FocusNode());
          _scaffoldKey.currentState.showSnackBar(new SnackBar(
            content: new Text("注册成功!"),
          ));
        }
      });
    });
  }
  //...
}

在用户点击登陆时,我们需要保存用户的登陆信息,用于应用程序启动时判断用户是否已经登陆。关于本地文件的相关操作,可以查看《Flutter进阶—读取与写入文件》。在这里,我们创建了一个LandingInformation文件,以Json格式保存用户的手机号码、用户名称和登陆密码。

//...
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'dart:io';
//...
class _SignInState extends State<SignIn> {
  //...
  Future<Null> _saveLogin(
      String phone, String password, String name, String email) async {
    String dir = (await getApplicationDocumentsDirectory()).path;
    await new File('$dir/LandingInformation').writeAsString(
        '{"phone":"$phone","name":"$name","email":"$email"}');
  }
  //...
}

像注册屏幕一样,我们在_SignInState中增加两个bool类型变量,用于存储手机号码、密码的格式验证。再添加_checkInput方法,用于判断手机号码、密码是否符合格式。要注意的是,我们现在把原来用户名的输入框更改成了手机号码输入框。

class _SignInState extends State<SignIn> {
  //...
  bool _correctPhone = true;
  bool _correctPassword = true;
  //...
  void _checkInput() {
    if (_phoneController.text.isNotEmpty &&
        (_phoneController.text.trim().length < 7 ||
            _phoneController.text.trim().length > 12)) {
      _correctPhone = false;
    } else {
      _correctPhone = true;
    }
    if (_passwordController.text.isNotEmpty &&
        _passwordController.text.trim().length < 6) {
      _correctPassword = false;
    } else {
      _correctPassword = true;
    }
    setState(() {});
  }
  //...
}

当用户点击登陆按钮时,目前只会在控制台打印一条消息,我们需要添加一个_userLogIn方法,作为ShowAwait(等待屏幕,在上一篇文章中添加的通用类型)的回调参数。

_userLogIn方法中,先根据手机号码查找用户数据,如果没有查找到数据,则返回值为0表示手机号码不存在。如果查找到用户数据,再对比登陆密码是否一致,如果不一致,则返回值为1表示手机号码与密码不匹配。登陆密码一致则返回值为2表示手机号码与密码一致。稍后我们会根据返回的数值作出相应的处理。

//...
import 'package:firebase_database/firebase_database.dart';
import 'prompt_wait.dart';
//...
class _SignInState extends State<SignIn> {
  //...
  final reference = FirebaseDatabase.instance.reference().child('users');
  //...
  Future<int> _userLogIn(String phone, String password) async {
    return await reference
        .child(_phoneController.text)
        .once()
        .then((DataSnapshot onValue) {
      if (onValue.value != null) {
        if (onValue.value["password"] == _passwordController.text) {
          _saveLogin(onValue.value["phone"], onValue.value["password"],
              onValue.value["name"], onValue.value["email"]);
          return 2;
        } else {
          return 1;
        }
      } else {
        return 0;
      }
    });
  }
  //...
}

我们现在添加_handleSubmitted方法,作为登陆按钮的回调方法。在该方法中,首先判断手机号码、登陆密码是否完整且格式正确。通过格式验证之后则调用ShowAwait并将_userLogIn方法作为其回调参数,根据返回值作出对应处理,如果返回值为2则进入聊天列表屏幕。

聊天列表屏幕的内容是下一篇文章的内容,大家可以在GitHub上查看group_chat_list.dart文件的代码。

class _SignInState extends State<SignIn> {
  //...
  void _handleSubmitted() {
    FocusScope.of(context).requestFocus(new FocusNode());
    _checkInput();
    if (_phoneController.text == '' || _passwordController.text == '') {
      showMessage(context, "登录信息填写不完整!");
      return;
    } else if (!_correctPhone || !_correctPassword) {
      showMessage(context, "登录信息的格式不正确!");
      return;
    }
    showDialog<int>(
            context: context,
            barrierDismissible: false,
            child: new ShowAwait(
                _userLogIn(_passwordController.text, _phoneController.text)))
        .then((int onValue) {
      if (onValue == 0) {
        showMessage(context, "这个手机号码没有被注册!");
      } else if (onValue == 1) {
        showMessage(context, "手机号码或登陆密码不正确!");
      } else if (onValue == 2) {
        Navigator
            .of(context)
            .push(new MaterialPageRoute<Null>(builder: (BuildContext context) {
          return new GroupChatList();
        }));
      }
    });
  }
  //...
}

最后我们再修改一下_SignInState中的build方法,将上面添加的方法添加到登陆按钮及对应输入框中。

class _SignInState extends State<SignIn> {
  //...
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        key: _scaffoldKey,
        body: new Stack(children: <Widget>[
          new Opacity(
              opacity: 0.3,
              child: new GestureDetector(
                  onTap: () {
                    FocusScope.of(context).requestFocus(new FocusNode());
                    _checkInput();
                  },
                  child: new Container(
                    decoration: new BoxDecoration(
                      image: new DecorationImage(
                        image: new ExactAssetImage(
                            'images/sign_in_background.jpg'),
                        fit: BoxFit.cover,
                      ),
                    ),
                  ))),
          new Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              new Center(
                  child: new Image.asset(
                'images/talk_casually.png',
                width: MediaQuery.of(context).size.width * 0.4,
              )),
              new Container(
                  width: MediaQuery.of(context).size.width * 0.96,
                  child: new Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        new TextField(
                          controller: _phoneController,
                          keyboardType: TextInputType.phone,
                          decoration: new InputDecoration(
                            hintText: '手机号码',
                            errorText: _correctPhone
                                ? null
                                : '号码的长度应该在7到12位之间',
                            icon: new Icon(
                              Icons.phone,
                            ),
                          ),
                          onSubmitted: (value) {
                            _checkInput();
                          },
                        ),
                        new TextField(
                          controller: _passwordController,
                          obscureText: true,
                          keyboardType: TextInputType.number,
                          decoration: new InputDecoration(
                            hintText: '登陆密码',
                            errorText: _correctPassword
                                ? null
                                : '密码的长度应该大于6位',
                            icon: new Icon(
                              Icons.lock_outline,
                            ),
                          ),
                          onSubmitted: (value) {
                            _checkInput();
                          },
                        ),
                      ])),
              new FlatButton(
                child: new Container(
                  decoration: new BoxDecoration(
                    color: Theme.of(context).accentColor,
                  ),
                  child: new Center(
                      child: new Text("登录")),
                ),
                onPressed: () {
                  _handleSubmitted();
                },
              ),
              new Center(
                  child: new FlatButton(
                child: new Text("没有帐户? 注册"),
                onPressed: _openSignUp,
              ))
            ],
          )
        ]));
  }
  //...
}

这里写图片描述

作者:hekaiyou 发表于2017/7/28 16:01:33 原文链接
阅读:42 评论:0 查看评论

Git HEAD detached from XXX (git HEAD 游离) 解决办法

$
0
0

本文 Git 图片主要来自:图解 Git,非常感谢!

读完本文你将了解:

什么是 HEAD

Git 中的 HEAD 可以理解为一个指针,我们可以在命令行中输入 cat .git/HEAD 查看当前 HEAD 指向哪儿,一般它指向当前工作目录所在分支的最新提交。

这里写图片描述

当使用 git checkout < branch_name> 切换分支时,HEAD 会移动到指定分支。

这里写图片描述

但是如果使用的是 git checkout < commit id>,即切换到指定的某一次提交,HEAD 就会处于 detached 状态(游离状态)。

这里写图片描述

HEAD 游离状态的利与弊

HEAD 处于游离状态时,我们可以很方便地在历史版本之间互相切换,比如需要回到某次提交,直接 checkout 对应的 commit id 或者 tag 名即可。

它的弊端就是:在这个基础上的提交会新开一个匿名分支!

这里写图片描述

也就是说我们的提交是无法可见保存的,一旦切到别的分支,游离状态以后的提交就不可追溯了。

这里写图片描述

解决办法就是新建一个分支保存游离状态后的提交:

这里写图片描述

具体解决操作

  1. git branch -v 查看当前领先多少
    • 这里写图片描述
    • 4449a91 指向的是 dev1 的最后一次提交
  2. 新建一个 temp 分支,把当前提交的代码放到整个分支
    • 这里写图片描述
  3. checkout 出要回到的那个分支,这里是 dev1
    • 这里写图片描述
  4. 然后 merge 刚才创建的临时分支,把那些代码拿回来
    • 这里写图片描述
  5. git status 查看下合并结果,有冲突就解决
    • 这里写图片描述
  6. 合并 OK 后就提交到远端
    • 这里写图片描述
  7. 删除刚才创建的临时分支

    • 这里写图片描述
  8. 查看 Log,当前 HEAD 指向本地 dev1 ,和远端 dev1 一致,OK 了!

    • 这里写图片描述

Thanks

https://marklodato.github.io/visual-git-guide/index-zh-cn.html#detached
https://git-scm.com/docs/git-checkout#_detached_head

作者:u011240877 发表于2017/7/28 20:13:44 原文链接
阅读:206 评论:0 查看评论

谈一谈头文件引用(#include,#import,@import,@class)

$
0
0

#include,#import,@import,@class 这四个指令在 ios (OC)开发中比较常见之所以共存,是因为四者有较大的区别,下面来一一详解。

#include

熟悉 C 或者 C++ 的童鞋可能会知道,在 C 和 C++ 里,#include 是非常常见的,用来包含头文件。#include 做的事情其实就是简单的复制粘贴,将目标 .h 文件中的内容一字不落地拷贝到当前文件中,并替换掉这句 include。
注意点:在使用#include的时候要注意处理重复引用(这也是objc中#include与#import的区别)

#import

#import 大部分功能和 #include 是一样的,但是他处理了重复引用的问题,我们在引用文件的时候不用再去自己进行重复引用处理,这使你在递归包含中不会出现问题。总的来说,#import 比起 #include 的好处就是不会引起交叉编译。

@import

@import 是 iOS 7 之后的新特性语法,这种方式叫 Modules(模块导入) 或者 “semantic import(语义导入)” ,是一种更好的头部预处理的执行方式,这 iOS 7 之后你能通过 @import 语法来导入任何的framework,Modules 是一种将所有可执行的 framework 打包在一起,只有在需要时才能导入到源代码中,这种方式比起传统的 #import 更安全和更高效。

举个例子:

#import <SomeLibrary/SomeFile.h>

要将此库作为模块导入,代码将改为:

@import SomeLibrary;

这有助于将代码将 SomeLibrary 框架自动链接到项目中。模块还允许您仅将您真正需要的组件包含在项目中。例如,如果要在 AwesomeLibrary 框架中使用 AwesomeObject 组件,通常您必须导入所有内容才能使用一个。但是,使用模块可以导入要使用的特定对象:

@import AwesomeLibrary.AwesomeObject;

实际开发中上可能并不需要使用 @import 关键字。如果你选择使用”modules”(Xcode5 以后中默认开启,如下图所示),所有的 #import 和 #include 指令都会自动映射使用 @import。尤其对于没有善用 .pch 头文件的开发者或者你的项目中有许多零碎小的源文件时, 使用 @import 导入指定框架下需要的头文件时,Xcode 的编译表现会有提高。
这里写图片描述

@class

主要是用于声明一个类,告诉编译器它后面的名字是一个类的名字,而这个类的定义实现是暂时不用知道的,后面会告诉你.也是因为在 @class 仅仅只是声明一个类,所以在后面的实现文件里面是需要去 #import 这个类,这时候才包含了这个被引用的类的所有信息。一般来说,在 interface 中引用一个类,就用 @class,它会把这个类作为一个类型来使用,而在实现这个 interface 的文件中,如果需要引用这个类的实体变量或者方法之类的,还是需要 import 这个在 @class 中声明的类。

作者:huangfei711 发表于2017/7/29 17:14:41 原文链接
阅读:0 评论:0 查看评论

注解库之Dagger2

$
0
0

Dagger2

Step1 使用

0x00 Dagger2介绍

Dagger is a fully static, compile-time dependency injection framework for both Java and Android. It is an adaptation of an earlier versioncreated by Square and now maintained by Google.
Dagger aims to address many of the development and performance issues that have plagued reflection-based solutions. More details can be found in this talk(slides) by +Gregory Kick.

Dagger2在Github主页上的自我介绍是:“A fast dependency injector for Android and Java“(一个提供给Android和Java使用的快速依赖注射器。)
Dagger2是由谷歌接手开发,最早的版本Dagger1 是由Square公司开发的。

0x000 依赖注入

  • 构造器注入
  • Setter注入
  • 接口注入

0x01 Dagger2相较于Dagger1的优势是什么

  • 更好的性能:相较于Dagger1,它使用的预编译期间生成代码来完成依赖注入,而不是用的反射。大家知道反射对手机应用开发影响是比较大的,因为反射是在程序运行时加载类来进行处理所以会比较耗时,而手机硬件资源有限,所以相对来说会对性能产生一定的影响。

  • 容易跟踪调试:因为dagger2是使用生成代码来实现完整依赖注入,所以完全可以在相关代码处下断点进行运行调试。

0x02 依赖注入的好处

模块间解耦

就拿当前Android非常流行的开发模式MVP来说,使用Dagger2可以将MVP中的V 层与P层进一步解耦,这样便可以提高代码的健壮性和可维护性。

方便单元测试

0x03 配置

gradle 版本高于2.2的


    // Add Dagger dependencies
    dependencies {
     compile 'com.google.dagger:dagger:2.x' 
     annotationProcessor 'com.google.dagger:dagger-compiler:2.x' 
    }

低于2.2的


    apply plugin: 'com.neenbedankt.android-apt'

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
        }
    }

    dependencies {
        apt 'com.google.dagger:dagger-compiler:2.0'
        compile 'com.google.dagger:dagger:2.0'
    }

将apply plugin和dependencies放在app的gradle里,把buildscript放在项目的gradle里即可

0x04 如何使用

Dagger2通过注解来生成代码,有4(基础)+2(辅助)种不通过注解来定义不同的角色,主要的注解如下:

  • @Inject:

主要有两个作用,一个是使用在构造函数上,通过标记函数让Dagger2来使用(Dagger2通过Inject标记可以在需要这个类实例的时候来找到这个构造函数并把相关实例new出来)从而提供依赖,另一个作用就是标记在需要依赖的变量让Dagger2为其提供依赖,但要注意,成员变量要求是包级可见,也就是说@Inject不可以标记为private类型。

  • @Provides:

在Module中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。如果找不到被@Provides注释的方法提供对应参数对象的话,将会自动调用被@Inject注释的构造方法生成相应对象。即Provides注解的优先级要高于inject

  • @Module:

Module类里面的方法专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的依赖。有的人可能有些疑惑,看了上面的@Inject,需要在构造函数上标记才能提供依赖,那么如果我们需要提供的类构造函数无法修改怎么办,比如一些jar包里的类,我们无法修改源码。这时候就需要使用Module了。Module可以给不能修改源码的类提供依赖,当然,能用Inject标注的通过Module也可以提供依赖

  • @Component:

Component从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁,被标注的Component的接口在编译时会产生相应的实例来作为依赖方和需要依赖方的桥梁,它的主要作用就是连接这两个部分。

这些标注看起来比较抽象,为了方便理解,用一张图来标注这4个注解之间的关系

左边的是依赖提供者,比如我们用Module标注的类或者用Injection标注的构造函数,右边的是依赖的需求方,例如我们用inject标注的变量,而Component则是连接两者的桥梁,Component从依赖提供者提供依赖,并把这些依赖注入相关的类中,Dagge正如其名,就像把匕首让依赖能够非常犀利的注入到需要它的地方。

Tips

这里需要说明一个问题,我们有两种方式可以提供依赖,一个是注解了@Inject的构造方法,一个是在Moudle里提供的依赖,Dagger2是按照如下规则选择依赖提供,规则如下:

步骤1:查找Module中是否存在创建该类的方法。
步骤2:若存在创建类方法,查看该方法是否存在参数
步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数

步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

即从注解了@Inject的对象开始,从Module和注解过的构造方法中获得实例,若在获取该实例的过程中需要其他类的实例,则继续获取被需要类的实例对象的依赖,同样是从Module和标注过的构造方法中获取,并不断递归这个过程直到所有被需要的类的实例创建完成,在这个过程中Module的优先级高于注解过的构造方法。

在Moudle中根据返回类型来区分为谁提供依赖,当某个对象需要注入依赖时,Dagger2就会根据Moudle中标记了@Provide的方法的返回值来确定由谁为这个变量提供实例,那么问题来了,如果有两个一样的但会类型,该使用谁呢?这种场景称为依赖迷失,因此引入了@Qualifier来解决这个问题

  • @Qualifier: 当类的类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示。例如:在Android中,我们会需要不同类型的context,所以我们就可以定义 qualifier注解“@perApp”和“@perActivity”,这样当注入一个context的时候,我们就可以告诉 Dagger我们想要哪种类型的context。

Scope的作用,提供局部单例的功能,局部范围是啥,那就是它注解的componnent的生命周期范围内。
我们知道@Singleton注解实际上实现的是一个全局单例模式,在实际开发中我们可能还需要一种局部单例的控件,比如说我们有三个Activity,MainActivity,BActivity和CActivity,我们想让MainActivity和BActivity共享同一个实例,而让CActivity获取另外一个实例,这又该怎么实现呢?在Dagger2中,我们可以通过自定义Scope来实现局部单例。

  • @Scope: Dagger2可以通过自定义注解限定注解作用域,来管理每个对象实例的生命周期。

使用Scope需要注意以下几点

  • component 的 inject 函数不要声明基类参数;
  • Scope 注解必须用在 module 的 provide 方法上,否则并不能达到局部单例的效果;
  • 如果 module 的 provide 方法使用了 scope 注解,那么 component 就必须使用同一个注解,否则编译会失败;
  • 如果 module 的 provide 方法没有使用 scope 注解,那么 component 和 module 是否加注解都无关紧要,可以通过编译,但是没有局部单例效果;
  • 对于直接使用 @Inject 构造函数的依赖,如果把 scope 注解放到它的类上,而不是构造函数上,就能达到局部单例的效果了;

0x06 简单的例子

通常要实现一个完整的依赖注入,需要有一下三个元素 Module、Component、Container

实现Moulde


    @Module // 注明本类是Module
    public class MyModule{
        @Provides  // 注明该方法是用来提供依赖对象的方法
        public B provideB(){
            return new B();
        }
    }

实现Component


    @Component(modules={ MyModule.class}) // 指明Component查找Module的位置 
    public interface MyComponent{ // 必须定义为接口,Dagger2框架将自动生成Component的实现类,对应的类名是Dagger×××××,这里对应的实现类是DaggerMyComponent 
    void inject(A a); // 注入到A(Container)的方法,方法名一般使用inject 
    }

实现Container

A 就是可以被注入依赖关系的容器


    public A{ 
    @Inject //标记b将被注入 
    B b; // 成员变量要求是包级可见,也就是说@Inject不可以标记为private类型。 
    public void init(){ 
        DaggerMyComponent.create().inject(this); // 将实现类注入 
    }

添加多个Moudle

一个Component可以添加多个Module,这样Component获取依赖时候会自动从多个Module中查找获取。添加多个Module有两种方法,一种是在Component的注解@Component(modules={××××,×××})中添加多个modules


    @Component(modules={ModuleA.class,ModuleB.class,ModuleC.class}) 
    public interface MyComponent{
        ...
    }

或者使用@Moudle的include方法(includes={××××,×××})


    @Module(includes={ModuleA.class,ModuleB.class,ModuleC.class}) 
     public class MyModule{ 
    ... 
    } 
    @Component(modules={MyModule.class})
     public interface MyComponent{ 
    ... 
    }

创建Moudle实例

DaggerMyComponent使用了Builder构造者模式。在构建的过程中,默认使用Module无参构造器产生实例。如果需要传入特定的Module实例,可以使用


    DaggerMyComponent.builder()
    .moduleA(new ModuleA()) 
    .moduleB(new ModuleB())
    .build()

@Name @Qualifier

区分返回类型相同,但是参数不同的构造器,通常与Provides方法结合在一起使用


    @Qualifier @Documented //起到文档提示作用 
    @Retention(RetentionPolicy.RUNTIME) //注意注解范围是Runtime级别 
    public @interface ContextLife {
     String value() default "Application"; // 默认值是"Application" 
    }

组件间依赖

假设ActivityComponent依赖ApplicationComponent,当使用ActivityComponent注入Container,如果找不到对应的依赖就会到ApplicaComponent中查找,前提是ApplicationComponent必须显式把ActivityComponent找不到的依赖提供给ActivityComponent。

单例的使用

  • @Singleton

创建某些对象有时候是耗时、浪费资源的或者需要确保其唯一性,这时就需要使用@Singleton注解标注为单例了。

Tips

在java中,单例通常保存在一个静态域中,这样的单例往往要等到虚拟机关闭时,该单例所占用的资源才释放,但是Dagger通过注解创建出来的单例并不保持在静态域上,而是在Component实例中,因此不同的Componet实例提供的对象是不同的

  • 自定义Scope

@Singleton就是一种Scope注解,也是Dagger2唯一自带的Scope注解

看例子中

SecondActivity 的 User 对象的地址和 MainActivity 中的 User 对象地址并不一样啊,这个单例好像失效了啊!事实上并不是这样,那么为什么这个单例“失效”了呢?两个 Activity 中的 Component 对象的地址是并不一样的,这样就好理解了 ——— 由于 Component 对象不是同一个,当然它们注入的对象也不会是同一个。那么我们如何解决这个问题呢?提供以下两个方案:

  • 第一个,我们在 Application 层初始化 UserComponent,然后在 Activity 中直接获取这个 UserComponent 对象,由于 Application 在全局中只会初始化一次 -> 所以 Application 中的 UserComponent 对象只初始化一次 -> 我们每次在 Activity 中获取 Application 中的这个 UserComponent 当然就是同一个了
  • 第二个,我们将 UserComponent 改成抽象类,然后使用单例模式,这样每次 Activity 中获取的 Component 不也是同一个么

使用过程中需要注意——两个拥有依赖关系的 Component 是不能有相同 @Scope 注解的!

  • 子组件

可以使用@Subcomponent注解拓展原有component。Subcomponent其功能效果优点类似component的dependencies。但是使用@Subcomponent不需要在父component中显式添加子component需要用到的对象,只需要添加返回子Component的方法即可,子Component能自动在父Component中查找缺失的依赖。


    //父Component: 
    @Component(modules=××××) public AppComponent{ 
        SubComponent subComponent (); // 这里返回子Component 
    } 
    //子Component: 
    @Subcomponent(modules=××××) public SubComponent{
         void inject(SomeActivity activity); 
    } 
    // 使用子Component 
    public class SomeActivity extends Activity{
     public void onCreate(Bundle savedInstanceState){
     App.getComponent().subCpmponent().inject(this); // 这里调用子Component 
        } 
    }

Tips

@Subcomponent 和 @Component 的实际使用场景定义

Component 依赖
你想让两个 Component 都独立,没有任何关联. 你想很明确的告诉别人我这个 Component 所依赖的 Component.

Subcomponent 依赖
你想让两个 Component 内聚. 你应该并不关心这个 Component 依赖哪个 Component.

Step2 原理分析

依赖注入,我们知道,它本质上就是采用某种工厂模式去创建实例,并为目标类提供实例

连接器Component

  1. 通过Component.Builder创建component、Builder传入component依赖的moudle
  2. Component初始化 为Moudle中的每一个实例创建Provider
  3. 为Provider生成对应的工厂方法
  4. 重载component接口中方法,通过调用Provider

提供者Provider

Moulde中的每个@provider都会自动生成一个Provider相对应的类,结构如下,核心是get方法,在其实现类中,重载get并返回实例


    public interface Provider<T> {
        T get();
    }

工厂生产者Factory

在Moudle中,提供了实例的创建方法,在Dagger2生成的源码中,所有的实例都会生成对应的Factory,注入的时候都是通过Factory来创建实例,本质上还是调用了Moudle中的provider方法。

成员注入器 MembersInjector

在DaggerTasksComponent,已经生成有Provider tasksPresenterProvider; 为了实现注入,还生成了一个注入器 MembersInjector tasksActivityMembersInjector;这个注入器的作用就是往TasksActivity注入@Inject声明的东西,这里只有mTasksPresenter需要注入。

注入器结构如下


    public interface MembersInjector<T> {
      void injectMembers(T instance);
    }

查看DaggerTasksComponent源码可以看出

  • 注入都是通过注入器实现的
  • 植入是发生在调用inject的时候的

Field injection

  1. 如何注入

拥有Fields injection行为的类A,Dagger会为它生成一个注入器A_MembersInjector;
注入器的结构如下:


    public interface MembersInjector<T> {
      void injectMembers(T instance);
    }

注入器A_MembersInjector需要实现方法:injectMembers(T instance),该方法通过Provider注入具体的实例

  1. 何时注入

注入时机有两个

  • 当对应的A_Factory中的get() 方法被调用的时候;
  • 主动调用Component中定义的inject()方法时

Scope

Scope是一种作用域的描述,真正要表达的是类、Component、Module一体的关系。本质上依赖于component的生命周期的

Qualifier

@Qualifier就是一个tag。想象一下,如果在Module中你需要provide两个TasksDataSource,你就需要通过@Qualifier来区分了。

实际上,在Dagger2自动生成的代码中,它会对Qualifier标识的方法生成不同的工厂类,如上就分别对应TasksRepositoryModule_ProvideTasksRemoteDataSourceFactory

TasksRepositoryModule_ProvideTasksLocalDataSourceFactory,最终在引用的时候,就分别通过这两个工厂类提供实例。

参考链接

作者:XSF50717 发表于2017/7/29 16:20:16 原文链接
阅读:59 评论:0 查看评论

注解库之ButterKnife

$
0
0

butterknife解决的问题

项目地址: https://github.com/JakeWharton/butterknife

github原文是这样介绍的

Field and method binding for Android views which uses annotation processing to generate boilerplate code for you.

翻译过来就是:

用注解处理器为程序在编译期生成一些样板代码,用于把一些属性字段和回调方法绑定到 Android 的 View,即专门为Android View设计的绑定注解,专业解决各种findViewById。

如何使用

在主工程中:


    dependencies {
        compile 'com.jakewharton:butterknife:8.5.1'
        annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
    }

在Library中


    buildscript {
      repositories {
        mavenCentral()
       }
      dependencies {
        classpath 'com.jakewharton:butterknife-gradle-plugin:8.7.0'
      }
    }

然后在moudle中


    apply plugin: 'com.android.library'
    apply plugin: 'com.jakewharton.butterknife'

常规使用

Actvity中

对一个成员变量使用@BindView注解,并传入一个View ID, ButterKnife 就能够帮你找到对应的View,并自动的进行转换(将View转换为特定的子类):

资源绑定

绑定资源到类成员上可以使用@BindBool、@BindColor、@BindDimen、@BindDrawable、@BindInt、@BindString。使用时对应的注解需要传入对应的id资源,例如@BindString你需要传入R.string.id_string的字符串的资源id。

Butter Knife提供了bind的几个重载,只要传入跟布局,便可以在任何对象中使用注解绑定

Fragment中



    public class FancyFragment extends Fragment {
        @BindView(R.id.button1)
        Button button1;
        @BindView(R.id.button2)
        Button button2;

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fancy_fragment, container, false);
            ButterKnife.bind(this, view); // TODO Use fields... return view; } }

        }

Adapter中


    public class MyAdapter extends BaseAdapter {
        @Override
        public View getView(int position, View view, ViewGroup parent) {
            ViewHolder holder;
            if (view != null) {
                holder = (ViewHolder) view.getTag();
            } else {
                view = inflater.inflate(R.layout.whatever, parent, false);
                holder = new ViewHolder(view);
                view.setTag(holder);
            }
            holder.name.setText("John Doe"); // etc... return view; } static class ViewHolder { @BindView(R.id.title) TextView name; @BindView(R.id.job_title) TextView jobTitle; public ViewHolder(View view) { 
                ButterKnife.bind(this, view); 
            } 

        }
    }

监听器绑定

监听器能够自动的绑定到特定的执行方法上:


    @OnClick(R.id.submit)
    public void submit(View view) {
      // TODO submit data to server...
    }

进阶使用

重置绑定

Fragment的生命周期与Activity不同。在Fragment中,如果你在onCreateView中使用绑定,那么你需要在onDestroyView中设置所有view为null。为此,ButterKnife返回一个Unbinder实例以便于你进行这项处理。在合适的生命周期回调中调用unbind函数就可完成重置。


    public class FancyFragment extends Fragment {
        @BindView(R.id.button1)
        Button button1;
        @BindView(R.id.button2)
        Button button2;
        private Unbinder unbinder;

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fancy_fragment, container, false);
            unbinder = ButterKnife.bind(this, view); // TODO Use fields... 
            return view;
        }

        @Override
        public void onDestroyView() {
            super.onDestroyView();
            unbinder.unbind();
        }
    }

可选绑定

在默认情况下, @bind和监听器的绑定都是必须的,如果目标view没有找到的话,Butter Knife将会抛出个异常。

如果你并不想使用这样的默认行为而是想创建一个可选的绑定,那么你只需要在变量上使用@Nullable注解或在函数上使用@Option注解。

注意:任何名为@Nullable的注解都可以使用在变量上。但还时强烈建议使用Android注解库中的@Nullable。使用这个库对你的代码有很多好处,关于该库的详情,可以点击此处

注意事项

注意:

  • Activity ButterKnife.bind(this);必须在setContentView();之后,且父类bind绑定后,子类不需要再bind
  • Fragment ButterKnife.bind(this, mRootView);
  • 属性布局不能用private or static 修饰,否则会报错
  • setContentView()不能通过注解实现。
  • ButterKnife已经更新到版本8.0.1了,以前的版本中叫做@InjectView了,而现在改用叫@Bind,更加贴合语义。
  • 在Fragment生命周期中,onDestoryView也需要Butterknife.unbind(this)
  • 在Libbray中使用R2.id.xxx

原理分析

讲到butterknife的原理。这里不得不提一下一般这种注入框架都是运行时注解,即声明注解的生命周期为RUNTIME,然后在运行的时候通过反射完成注入,这种方式虽然简单,但是这种方式多多少少会有性能的损耗。那么有没有一种方法能解决这种性能的损耗呢? 没错,答案肯定是有的,那就是Butterknife用的APT(Annotation Processing Tool)编译时解析技术。千万不要说成反射了。

这里大致介绍下APT原理

声明注解的生命周期为CLASS,然后继承AbstractProcessor类,在编译时编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor#process 方法,对注解进行处理,在注解处理的时候使用javappoet动态生成固定的模板代码(findviewById、onClick)然后在运行时直接调用bind方法完成绑定就可以了。 详细的APT介绍参看http://blog.csdn.net/xsf50717/article/details/54318874

Java Annotation Processing

Annotation processing 是javac中用于编译时扫描和解析Java注解的工具

你可以定义注解,并且自定义解析器来处理他们,Annotation processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法

下面是java编译代码的整个过程,可以帮助我们理解注解解析过程

ButterKnife工作流程

当你编译你的Android工程时,ButterKnife工程中ButterKnifeProcessor类的process()方法会执行以下操作:

  • 开始它会扫描Java代码中所有的ButterKnife注解@Bind、@OnClick、@OnItemClicked等
  • 当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似$$ViewBinder,这个新生成的类实现了ViewBinder接口
  • 这个ViewBinder类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等
  • 最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法

在butterknife源码 butterknife-compiler#ButterKnifeProcessorhttps://github.com/JakeWharton/butterknife/blob/e9cfe921bbb03d40f619d8c86ce49f9e1bc711c5/butterknife-compiler/src/main/java/butterknife/compiler/ButterKnifeProcessor.java

注解处理器

包含以下几个重要的方法

  • init()

初始化 得到Element、Type、Filer等工具类

  • getSupportedAnnotationTypes()

描述注解处理器需要处理的注解

  • process()

扫面分析注解,结合javapoet生成代码

因此process时核心


      @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

        for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
          TypeElement typeElement = entry.getKey();
          BindingSet binding = entry.getValue();

          JavaFile javaFile = binding.brewJava(sdk, debuggable);
          try {
            javaFile.writeTo(filer);
          } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
          }
        }

        return false;
      }

主要做了两件事

  • findAndParseTargets
    获得TypeElement -> BindingSet的映射关系,TypeElement指的是类或接口,在本文所举的栗子中是MainActivity。BindingSet里包含了生成代码时的一些参数。
  • 运用JavaPoet框架来生成代码
    生成的代码类形式为xxxx_ViewBinding

findAndParseTargets


private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    // 建立view与R的id的关系
    scanForRClasses(env);

    //  省略部分代码

    // 解析BindView注解
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

    // 省略部分代码
    // 将Map.Entry<TypeElement, BindingSet.Builder>转化为Map<TypeElement, BindingSet>
    Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
        new ArrayDeque<>(builderMap.entrySet());
    Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
    while (!entries.isEmpty()) {
      Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();

      TypeElement type = entry.getKey();
      BindingSet.Builder builder = entry.getValue();

      TypeElement parentType = findParentType(type, erasedTargetNames);
      if (parentType == null) {
        bindingMap.put(type, builder.build());
      } else {
        BindingSet parentBinding = bindingMap.get(parentType);
        if (parentBinding != null) {
          builder.setParent(parentBinding);
          bindingMap.put(type, builder.build());
        } else {
          // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
          entries.addLast(entry);
        }
      }
    }

    return bindingMap;
  }


  • scanForRClass

用来建立View与id的关系,首先根据element获取到包名,再利用RClassScanner寻找到R文件,在R文件里利用IdScanner寻找到内部类id,在id类里利用VarScanner寻找到tvTitle的id

  • parseBindView

解析各种注解,这里以BindView为例。


private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    // 得到包含注解所属的TypeElement,例如MainActivity
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // isInaccessibleViaGeneratedCode检验enclosingElement(MainActivity)是类、不是private,检验element不是private活着static
    // isBindingInWrongPackage检验enclosingElement的包名是不是系统相关的类
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element);

    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    // 判断element是View的子类或者接口
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      if (elementType.getKind() == TypeKind.ERROR) {
        note(element, "@%s field with unresolved type (%s) "
                + "must elsewhere be generated as a View or interface. (%s.%s)",
            BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
      } else {
        error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
            BindView.class.getSimpleName(), qualifiedName, simpleName);
        hasError = true;
      }
    }

    if (hasError) {
      return;
    }

    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();

    BindingSet.Builder builder = builderMap.get(enclosingElement);
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
    if (builder != null) {
      String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
      // 检查是否绑定过此id
      if (existingBindingName != null) {
        error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
            BindView.class.getSimpleName(), id, existingBindingName,
            enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    } else {
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }

    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);

    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
  }

parseBindView先检测是否有错误,然后将name(变量名,例如tvTitle)、type(类名,例如TextView)、required(是否有@nullable注解)封装成FieldViewBinding放到builder里面。

最后使用JavaPoet框架生成代码,关于javaPoet的使用可以参考http://blog.csdn.net/XSF50717/article/details/54318891

参考链接

作者:XSF50717 发表于2017/7/29 17:19:51 原文链接
阅读:248 评论:0 查看评论

Flutter学习之旅(五)----网络请求获取数据、解析数据和显示等待动画

$
0
0

参考官方文章(英文版)

网络请求是APP开发中至关重要的一步,回想一下Android开发中的Retrofit框架或者Volley框架,iOS开发中AFNetworking框架。如果使用Volley进行网络请求,我们需要new一个StringRequest请求,在里面回调成功和失败的方法,用GsonFormmater等类似工具解析json串,然后将这个请求添加到队列,这中间要给予用户友好的提示,即显示与隐藏加载框,甚至是下拉刷新功能的实现,如果要保持登录状态,还需要将Session信息添加到请求的头部。成熟的网络请求框架像Volley很容易实现这些功能,Flutter中的网络请求是否能够实现这些功能呢,如何实现呢?
这篇文章主要介绍如何在Flutter中进行网络请求,包括基本的网络请求,对服务器返回的json进行解析,构造json,加载框显示与隐藏。

一、完整的网络请求过程

打开InteliJ IDEA,新建一个Flutter工程,在左侧工程结构里面的pubspec.yaml文件里面添加网络请求的依赖

http: '>=0.11.3+12'

将main.dart里面的代码全部删除,然后重新写我们的代码,先导包:

import 'package:flutter/material.dart';//导入系统基础包
import 'package:flutter/services.dart';//导入网络请求相关的包

然后获取http对象

var httpclient=createHttpClient();//获取http对象

用async…await..发送网络请求并读取结果:

_postData() async{
    response=await httpclient.read(url);//发送网络请求,read()表示读取返回的结果,get()表示不读取返回的结果
    print('Response=$response');
  }

上面就是一次完整的网络请求的核心代码,好像并不复杂吧?完整的代码如下,直接复制到main.dart,替换掉原来的代码即可运行:

import 'package:flutter/material.dart';//导入系统基础包
import 'package:flutter/services.dart';//导入网络请求相关的包
void main(){
  runApp(new MaterialApp(home: new ClickEvent(),));
}
class ClickEvent extends StatefulWidget{
  @override
  _ClickEventState createState() {
    return new _ClickEventState();
  }
}
class _ClickEventState extends State<ClickEvent>{
  var httpclient=createHttpClient();//获取http对象
  var url='https://api.github.com/';
  var response;
  _postData() async{
    response=await httpclient.read(url);//发送网络请求,read()表示读取返回的结果,get()表示不读取返回的结果
    print('Response=$response');
  }
  @override
  Widget build(BuildContext context) {
    return new Scaffold(appBar: new AppBar(title: new Text('网络请求'),),
      floatingActionButton: new FloatingActionButton(child: new Text('获取数据'),onPressed: _postData),);//点击后调用网络请求方法
  }
}

运行结果为:

这里写图片描述

上面就是https://api.github.com/这个接口返回的json串,至此我们已经能够从服务器获取网络数据了,怎么样,比想象中简单吧。

二、解析服务器返回的json数据

先导入json相关的包

import 'dart:convert';

如果返回的数据结果类似于:

['foo', { 'bar': 499 }]

可以这样解析

Map data= JSON.decode(response);
data[1]['bar'];

如果返回的数据结构类似于:

{"current_user_url":"https://api.github.com/user"}

可以这样解析:

Map data= JSON.decode(response);
var url1= data['current_user_url'];

解析结果为:

current_user_url:https://api.github.com/user

三、等待动画的显示与关闭

先新建一个对话框(圆圈)控件,用来显示,核心代码是

new CircularProgressIndicator()//这是圆圈对话框,还有条形对话框的。

然后用定时器不断去回调结果,代码如下:

class ShowProgress extends StatefulWidget {
  ShowProgress(this.requestCallback);
  final Future<Null> requestCallback;//这里Null表示回调的时候不指定类型
  @override
  _ShowProgressState createState() => new _ShowProgressState();
}
class _ShowProgressState extends State<ShowProgress> {
  @override
  initState() {
    super.initState();
    new Timer(new Duration(milliseconds: 10), () {//每隔10ms回调一次
      widget.requestCallback.then((Null) {//这里Null表示回调的时候不指定类型
        Navigator.of(context).pop();//所以pop()里面不需要传参,这里关闭对话框并获取回调的值
      });
    });
  }
  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new CircularProgressIndicator(),//获取控件实例
    );
  }
}

点击“获取数据”按钮,将网络请求的方法_postData作为参数传递给ShowProgress显示对话框,下面代码功能主要是展示对话框,核心代码如下:

Future<Null> _myClick()  {
     return showDialog<Null>(
         context: context,
         barrierDismissible: true, // false表示必须点击按钮才能关闭
         child:new ShowProgress(_postData())//将网络请求的方法_postData作为参数传递给ShowProgress显示对话框
     );

完整代码如下,直接复制到main.dart即可运行:

import 'dart:async';
import 'package:flutter/material.dart';//导入系统基础包
import 'package:flutter/services.dart';//导入网络请求相关的包
import 'dart:convert';
void main(){
  runApp(new MaterialApp(home: new ClickEvent(),));
}
class ShowProgress extends StatefulWidget {
  ShowProgress(this.requestCallback);
  final Future<Null> requestCallback;//这里Null表示回调的时候不指定类型
  @override
  _ShowProgressState createState() => new _ShowProgressState();
}

class _ShowProgressState extends State<ShowProgress> {
  @override
  initState() {
    super.initState();
    new Timer(new Duration(milliseconds: 10), () {//每隔10ms回调一次
      widget.requestCallback.then((Null) {//这里Null表示回调的时候不指定类型
        Navigator.of(context).pop();//所以pop()里面不需要传参,这里关闭对话框并获取回调的值
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new CircularProgressIndicator(),
    );
  }
}
////////////上面是对话框控件,下面是按钮控件/////////////////
class ClickEvent extends StatefulWidget{
  @override
  _ClickEventState createState() {
    return new _ClickEventState();
  }
}
class _ClickEventState extends State<ClickEvent>{
   Future<Null> _myClick()  {
     return showDialog<Null>(
         context: context,
         barrierDismissible: true, // false表示必须点击按钮才能关闭
         child:new ShowProgress(_postData())//将网络请求的方法_postData作为参数传递给ShowProgress显示对话框
     );
  }
  var httpclient=createHttpClient();//获取http对象
  var url='https://api.github.com/';
  var response;
  //核心的网络请求方法
  _postData() async{
    response=await httpclient.read(url);//发送网络请求,read()表示读取返回的结果,get()表示不读取返回的结果
    print('Response=$response');
    Map data= JSON.decode(response);
    var url1= data['current_user_url'];
    print('current_user_url:$url1');
  }
  @override
  Widget build(BuildContext context) {
    return new Scaffold(appBar: new AppBar(title: new Text('网络请求'),),
      floatingActionButton: new FloatingActionButton(child: new Text('获取数据'),onPressed: _myClick),);
  }
}

运行结果为:

这里写图片描述

从图中可以看到,收到服务器响应后就关闭了对话框,达到了我们的目的。
上面讲解了一次基本的网络请求,包括获取数据,解析数据,显示等待动画。当然,成熟的网络框架远不止这些,还包括缓存处理,超时处理,异常处理,封装等等,这些我们以后都会涉及到。

如果看完这篇文章您有所收获,不忘点个赞或者关注一下留个言什么的,您的鼓励是我前进的动力,谢谢。

作者:zhangxiangliang2 发表于2017/7/30 11:00:19 原文链接
阅读:80 评论:0 查看评论

一起Talk Android吧(第三十一回:Android中的Activity三)

$
0
0

各位看官们,大家好,上一回中咱们说的是Android中Activity的例子,这一回咱们继续说该例子。闲话休提, 言归正转。让我们一起Talk Android吧!


看官们,好久不见,继上一回说完Acitivity的例子后,有部分看官想继续了解Activity的生命周期的内容,我们就继续接着上一回的话题进行。时间有些长了,如果大家忘记的话可以打开上一回中Acitivity生命周期的图形。我们会结合图形中的内容进行讲解。

看官们,在详细介绍Activity生命周期前,我们先介绍一下Activity的运行状态。它有以下六种运行状态:

  • 初始化(init)
  • 启动(start)
  • 运行(running)
  • 暂停(pause)
  • 停止(stop)
  • 销毁(destroy)

接下来,我们分别介绍这六种状态。

初始化(init)

在这个状态下主要是进行初始化Activity相关的工作,比如加载布局中的控件。该状态对应生命周期中的onCreate方法。当Activity处于这个状态时,就会回调onCreate方法,因此,我们可以该方法中做一些与Activity初始化相关的工作。

启动(start)

在这个状态下,会加载Activity,直到Activity可以被看到。该状态对应的是生命周期中的onStart方法。当Activity处于这个状态时,就会回调onStart方法。

运行(running)

在这个状态下,Activity获取到了焦点,我们可以对Activity进行操作。Activity也会响应我们的操作。该状态对应的是生命周期中的onResume方法。当Activity处于这个状态时,就会回调onResumes方法。因此我们可以在该方法中处理一些响应Activity操作的事情,比如设置事件监听器等。

暂停(pause)

在这个状态下,我们仍然可以看到Activity,但是不能在Activity中获取焦点,我们需要在该状态下保存Activity中的相关数据,以便Activity再次运行时使用。该状态对应的是生命周期中的onPause方法。当Activity处于这个状态时,就会回调onPause方法。因此我们可以在该方法中保存Activity中的相关数据。

停止(stop)

在这个状态下,会移除Activity,直到我们看不到Activity为止。该状态对应的是生命周期中的onStop方法。当Activity处于这个状态时,就会回调onStop方法。

销毁(destroy)

在这个状态下会释放Activity中相关的资源。该状态对应的是生命周期中的onDestroy方法。当Activity处于这个状态时,就会回调onDestroy方法。因此我们可以在该方法中处理一些释放资源相关事情,比如注销广播接收器,销毁服务等。

看官们,关于Activity的状态我们就介绍这些,希望大家能够理它们的含义,我觉得大家可以结合进程状态来理解Activity的状态,因为从进程的角度看Activity也是一个进程。此外,我在这里介绍的状态和官方介绍的状态不一样,官方给出的状态只有三种:运行,暂停,停止。对比来看,官方状态中的运行、暂停和停止与我们介绍中的运行、暂停和停止一致。我们介绍的状态比官方状态多了三种,这三种是我们为了方便大家理解而加上去的,加上这些状态后,每一种状态就会对应一种回调方法,这样可以帮忙大家理解如何去使用不同的回调方法。从知识的准确性上来讲,大家以官方内容为准。

最后说一些个人观点:我感觉官方给的这个状态是不完整的,没有初始化直接就是运行,显然不合理。当然了,官方可能有其它的理由吧。我这也是一家之言,欢迎大家来讨论。

各位看官,关于Android中的Activity的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!


作者:talk_8 发表于2017/7/30 11:32:29 原文链接
阅读:84 评论:0 查看评论

Android 内存查看常用命令

$
0
0

事情的终局强如事情的起头;存心忍耐的,胜过居心骄傲的。—传道书7:8

RAM(random-access memory)即内存的使用情况对系统的性能影响很大,OOM问题、内存泄露、程序卡顿等诸多问题,都跟不合理的内存使用相关,并且这类问题一般都比较隐晦,要解决该类问题,熟悉内存查看的方法很有必要。
本篇博文介绍Android平台上常用的内存观测方法。

procrank与procmem

这两个命令均需要root权限才能运行。
procrank
procrank可以快速的总览当前系统各个进程的内存使用情况,其主要展示了4个指标:

  • Vss(virtual set size): 进程可访问的内存大小,包含了共享内存。Android系统中大量的内存空间会被多个进程共享,比如一个进程在运行期间会调用到其他进程,那么Vss也就将调用到的其它进程的内存统计进来了。
  • Rss(resident set size): 进程实际使用的内存大小,同样也包含了共享内存,但它只将实际用到的共享内存统计入内。
  • Pss(proportional set size): 进程自身内存大小+按比例分配的共享内存大小。
  • Uss(unique set size): 进程自身的内存大小,不包括使用到的其他共享内存。程序退出后,系统就会多出Uss大小的内存出来。

举个例子说明下:假如有进程A,其自身运行期间的私有内存大小为a,进程B运行期间占据了b大小的内存。
A运行期间需要依赖B,则Vss=a+b。
A依赖B,但只是用到了B进程下的部分内存区域,大小为b1。则Rss=a+b1(b1<=b)
除了A以外,还有3个进程也同样在使用B,则Pss=a+b/(1+3)
只统计A进程自身占用的内存,则Uss=a

procrank的使用说明

# procrank -h
Usage: procrank [ -W ] [ -v | -r | -p | -u | -s | -h ]
    -v  Sort by VSS.
    -r  Sort by RSS.
    -p  Sort by PSS.
    -u  Sort by USS.
    -s  Sort by swap.
        (Default sort order is PSS.)
    -R  Reverse sort order (default is descending).
    -c  Only show cached (storage backed) pages
    -C  Only show non-cached (ram/swap backed) pages
    -k  Only show pages collapsed by KSM
    -w  Display statistics for working set only.
    -W  Reset working set of all processes.
    -h  Display this help screen.

其运行结果如下:

  PID       Vss      Rss      Pss      Uss    cmdline
 3062  1735540K  131548K   85448K   78648K    system_server
 3428  1028392K  115896K   74462K   68900K    com.android.systemui
26426  1012992K   83272K   42890K   39664K    com.android.settings
 3806  1001248K   81064K   41639K   38840K    com.android.phone
  353   152208K   40524K   28086K   26516K    /system/bin/cameraserver
 3414   978800K   64344K   27342K   25120K    com.google.android.inputmethod.pinyin
 9885  1643528K   64880K   24821K   22012K    com.android.mms

分析内存问题主要关注的是Uss跟Pss,尤其是Uss.因为Vss和Rss包含了共享内存,单个进程分析内存大小时,会有干扰。

由于procrank会列出整个系统进程的内存信息,而更多时候我们往往只关心特定进程的内存信息。可以用下面的shell语句过滤出我们关心的内容。

while true;do adb shell procrank|grep <proc-keywords>; sleep 6;done

上述命令会每隔6s打印出关心的进程内存信息。

procmem
procrank命令从宏观上给出了进程的内存总体情况,但如果需要详细分析某个进程的内存分配细节,这个时候就需要procmem出场了。
procmem使用说明:

# procmem
Usage: procmem [ -w | -W ] [ -p | -m ] [ -h ] pid
    -w  Displays statistics for the working set only.
    -W  Resets the working set of the process.
    -p  Sort by PSS.
    -m  Sort by mapping order (as read from /proc).
    -h  Hide maps with no RSS.

比如我们查看settings应用的内存分布:

# procmem -m 26426 (26426为settings的pid)
    Vss      Rss      Pss      Uss     ShCl     ShDi     PrCl     PrDi  Name
-------  -------  -------  -------  -------  -------  -------  -------  
  2084K       0K       0K       0K       0K       0K       0K       0K  /dev/ashmem/dalvik-main space (deleted)
194524K       0K       0K       0K       0K       0K       0K       0K  /dev/ashmem/dalvik-main space (deleted)
 12560K   10492K   10492K   10492K       0K       0K   10488K       4K  /dev/ashmem/dalvik-main space 1 (deleted)
  1780K       0K       0K       0K       0K       0K       0K       0K  /dev/ashmem/dalvik-main space 1 (deleted)
182268K       0K       0K       0K       0K       0K       0K       0K  /dev/ashmem/dalvik-main space 1 (deleted)
  7436K    7436K    4277K    4172K    3264K       0K    4172K       0K  /data/dalvik-cache/arm/system@framework@boot.art
 31116K   15260K    1696K     124K   15136K       0K     124K       0K  /data/dalvik-cache/arm/system@framework@boot.oat
     4K       4K       0K       0K       4K       0K       0K       0K  /data/dalvik-cache/arm/system@framework@boot.oat
          0K       0K       0K       0K       0K       0K       0K       0K  [vectors]
-------  -------  -------  -------  -------  -------  -------  -------  
1130688K  148940K  108346K  104332K   44512K      96K   62100K   42452K  TOTAL

中间大量的输出结果省略。

从procmem输出的结果可以看到它给出了内存是如何分配的细节,但面对这茫茫多的输出数据,我们该如何从中寻找问题点呢?

内存异常问题往往伴随着内存泄露,而内存泄露的本质就是申请的内存区域得不到正常释放,表现在procmem的输出结果就是多条同名的记录出现。因此如果发现procmem输出的结果里某条记录出现次数过多,比如五六百次,那么我们应当小心对待了。

dumpsys meminfo

dumpsys meminfo 也是分析内存的一把利器,如果不跟进程名或者进程号,则输出系统整体的内存情况,跟进程名则输出该进程的内存分配情况。

# adb shell dumpsys meminfo 26426
Applications Memory Usage (in Kilobytes):
Uptime: 206814437 Realtime: 374715375

** MEMINFO in pid 26426 [com.android.settings] **
                   Pss  Private  Private     Swap     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap    32062    32016        0        0    67584    27961    39622
  Dalvik Heap     9623     9504        0        0    21384     9096    12288
 Dalvik Other     4857     4824        0        0                           
        Stack      404      404        0        0                           
       Ashmem        2        0        0        0                           
      Gfx dev    40220    40220        0        0                           
    Other dev        6        0        4        0                           
     .so mmap     1258      184       16        0                           
    .jar mmap        8        8        0        0                           
    .apk mmap      865        0      392        0                           
    .ttf mmap       63        0        8        0                           
    .dex mmap     6464        8     6456        0                           
    .oat mmap     1694        0      124        0                           
    .art mmap     4280     4172        0        0                           
   Other mmap      563        4      156        0                           
   EGL mtrack    13056    13056        0        0                           
      Unknown      601      600        0        0                           
        TOTAL   116026   105000     7156        0    88968    37057    51910

 App Summary
                       Pss(KB)
                        ------
           Java Heap:    13676
         Native Heap:    32016
                Code:     7196
               Stack:      404
            Graphics:    53276
       Private Other:     5588
              System:     3870

               TOTAL:   116026      TOTAL SWAP (KB):        0

 Objects
               Views:      985         ViewRootImpl:        3
         AppContexts:       11           Activities:        7
              Assets:        5        AssetManagers:        4
       Local Binders:       83        Proxy Binders:       40
       Parcel memory:      389         Parcel count:       26
    Death Recipients:        0      OpenSSL Sockets:        0
            WebViews:        0

 SQL
         MEMORY_USED:      339
  PAGECACHE_OVERFLOW:      178          MALLOC_SIZE:      117

 DATABASES
      pgsz     dbsz   Lookaside(b)          cache  Dbname
         4      228             95      469/191/4  /data/user_de/0/com.android.settings/databases/search_index.db

横轴的几个重要参数:

  • Pss Total:按比例分配占用内存 (PSS) 总量,与procrank里的Pss含义一样。
  • Private Dirty: 进程私有的内存分配量,当进程退出,将有 Private Dirty大小的内存被系统回收。Private Dirty是已被修改而必须保持在 RAM 中的 RAM 页,相对应的Private Clean指RAM已从某个持久性文件(例如正在执行的代码)映射的 RAM 页,如果一段时间不用,可以移出分页。
  • Heap Size:进程当前可访问到的堆内存大小,包含了共享的其他进程内存。
  • Heap Alloc:进程当前已经占有的堆内存大小。

纵轴的几个重要参数:

  • Unknown:系统无法将其分类到其他更具体的一个项中的内存,当Unknown指持续增大,且不会明显回落,很可能是native的内存泄漏了。
  • AppContexts & Activities:进程中当前活动的应用 Context 和 Activity 对象数量,它们不断增长则表示有内存泄漏。

proc节点下隐藏的内存信息

/proc/[pid]节点下的内容包含了该进程更为详细的信息。不过查看它需要root权限。事实上不光内存信息,有关该进程的各种信息基本都能在该节点下找到。

# cd /proc/26426
# ls
attr   clear_refs coredump_filter exe    limits       maps      mounts     ns        oom_score_adj reclaim   sessionid stat   syscall 
auxv   cmdline    cwd             fd     loginuid     mem       mountstats oom_adj   pagemap       root      smaps     statm  task    
cgroup comm       environ         fdinfo make-it-fail mountinfo net        oom_score personality   schedstat stack     status wchan

以上展现了进程26426的相关信息,它们都被记录在对应的文件节点上。分析内存问题主要看maps节点,它会记录26426进程的详细内存映射关系

# cat maps |head 
12c00000-12e09000 ---p 00000000 00:04 12528      /dev/ashmem/dalvik-main space (deleted)
12e09000-1ec00000 ---p 00209000 00:04 12528      /dev/ashmem/dalvik-main space (deleted)
32c00000-33844000 rw-p 00000000 00:04 12529      /dev/ashmem/dalvik-main space 1 (deleted)
33844000-33a01000 ---p 00c44000 00:04 12529      /dev/ashmem/dalvik-main space 1 (deleted)
33a01000-3ec00000 rw-p 00e01000 00:04 12529      /dev/ashmem/dalvik-main space 1 (deleted)
70e4b000-7158e000 rw-p 00000000 103:0c 497764    /data/dalvik-cache/arm/system@framework@boot.art
7158e000-733f1000 r--p 00000000 103:0c 497763    /data/dalvik-cache/arm/system@framework@boot.oat
733f1000-733f2000 r-xp 01e63000 103:0c 497763    /data/dalvik-cache/arm/system@framework@boot.oat
733f2000-733f3000 r--p 01e64000 103:0c 497763    /data/dalvik-cache/arm/system@framework@boot.oat
733f3000-733f4000 rw-p 01e65000 103:0c 497763    /data/dalvik-cache/arm/system@framework@boot.oat

procmem获取到的信息就跟maps节点记录的一样,maps更加详细的标示出了内存分配的起至位置。

参考链接

http://stevevallay.github.io/blog/2014/11/17/memory-leak/
https://androidzhibinw.github.io/android/app/startup/activity/%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F/%E5%90%AF%E5%8A%A8/%E5%88%86%E6%9E%90/2015/09/21/android-app-startup-process/
http://shooting.logdown.com/posts/318965-android-memory-allocation
https://developer.android.com/studio/profile/investigate-ram.html?hl=zh-cn

作者:azhengye 发表于2017/7/30 22:41:35 原文链接
阅读:170 评论:0 查看评论

一起Talk Android吧(第三十二回:Android中的Activity四)

$
0
0

各位看官们,大家好,上一回中咱们说的是Android中Activity的例子,这一回咱们继续说该例子。闲话休提, 言归正转。让我们一起Talk Android吧!


看官们,介绍完Activity的状态后,我们在这一回中重点对Activity生命周期图形中的箭头进行分析,就是说让图形随着箭头动起来。

我们沿着Activity生命周期从开始到结束这个过程来介绍。首先Activity是没有的,从onCreate方法开始执行后,它会初始化Activity,接着箭头从onCreate流动到onStart。这个时候Activity完成了初始化过程,并且即将以画面的形式呈现在我们面前,等到onStrat方法执行完成后,我们就可以看到Activity的真面目了。

这时箭头从onStart方法流向onResume方法,Activity不但可以被看到,而且还能响应用户对它的操作,比如你可以滑动它或者点击它上面的各个控件,它会很快地做出相应的响应。

onResume方法执行完成后,箭头流向了onPause方法,这个时候Activity还能被看到,不过它不会响应用户的操作,我们称Activity此时正在睡觉,你喊它名字他都听不到,哈哈,看来它是困了呀。

接着箭头流向了onStop方法,此时的Activity进入后台,我们已经看不到它了,因此我们称Activity潜水了,或者说隐身了也是合适的,总之它已经从我们的视野中消失的无影无踪。

Activity虽然消失了,不过它还在系统中占用着资源,此时箭头流向了onDestroy方法,访方法会释放Activity使用的资源,它相当于把系统的资源从隐身的Activity手中收回来,以后就可以给其它Activity使用了。

这时有看官问,还有其它几个箭头没有介绍呢,看官莫急我们在下一回中会给大家介绍的。请放心。

各位看官,关于Android中的Activity的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!


作者:talk_8 发表于2017/7/31 7:27:16 原文链接
阅读:50 评论:0 查看评论

1、volley 官方教程-简介、配置

$
0
0

文章摘要
1、Volley 简介
2、Volley库配置


英文文献

Github Volley下载地址

Volley是HTTP库,使得网络对于Android应用更容易,最重要的是,速度更快。Volley可在GitHub上下载。

Volley具有以下优点:
- 网络请求的自动调度。

  • 多个并发的网络连接。

  • 透明盘和存储器响应缓存与标准的HTTP 高速缓存一致性

  • 支持请求优先级。

  • 取消请求API。您可以取消一个请求,我们也可以设置请求取消的块或范围。

  • 易于定制,例如,重试和退避。

  • 强排序,可以很容易正确填入你的用户界面与数据从网络获取异步。

  • 调试和跟踪工具。

Volley擅长用来填充UI,如获取搜索结果的页面结构化数据RPC式的操作。它与任何协议可轻松集成和出来与原始字符串,图像和JSON支持的开箱。通过您需要的功能提供了内置支持,排球让您摆脱编写样板代码,让您专注于特定于您的应用程序的逻辑。

Volley是不适合大型下载或流操作,因为Volley在分析过程中、在内存中保存所有响应。对于大的下载操作,可以考虑使用DownloadManager类似的替代。

核心的Volley库是在GitHub上开发的,它包含主要的请求发送流程以及一组常用的实用程序,可在Volley“工具箱”中使用。 为您的项目添加Volley的最简单方法是将以下依赖项添加到应用程序的build.gradle文件中:

dependencies {
    ...
    compile 'com.android.volley:volley:1.0.0'
}

您也可以克隆凌空库,并将其设置为库项目:
- 1、Git的键入在命令行下面的克隆库:

git clone https://github.com/google/volley

关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/29 23:47:14 原文链接
阅读:38 评论:0 查看评论

2、volley 官方教程-发送一个简单的请求

$
0
0

文章摘要
1、通过Volley 发送简单请求的案例
2、取消Volley请求


英文文献

可以通过创建一个RequestQueue并传递Request对象来使用Volley。RequestQueue管理用于运行网络操作,读取和写入缓存以及解析响应的工作线程。 请求对原始响应进行解析,并且Volley负责将已解析的响应分派回主线程进行传递。

本文通过Volley 发送一个简单的请求,步骤如下:

一、添加Internet权限

使用Volley,你必须添加 android.permission.INTERNET权限您的应用程序的清单。没有这一点,你的应用程序将无法连接到网络。

<uses-permission android:name="android.permission.INTERNET" />

二、使用newRequestQueue

Volley 提供了一个方便的方法Volley.newRequestQueue,设置了一个 RequestQueue对你来说,使用默认值,并启动队列。例如:

final TextView mTextView = (TextView) findViewById(R.id.text);
...

// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";

// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        // Display the first 500 characters of the response string.
        mTextView.setText("Response is: "+ response.substring(0,500));
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        mTextView.setText("That didn't work!");
    }
});
// Add the request to the RequestQueue.
queue.add(stringRequest);

Volley总是提供在主线程解析的响应。运行在主线程上是方便用于填充UI控件与接收到的数据,你可以随意修改UI直接从您的响应处理控制,但它是许多由库,特别是有关取消请求提供了重要的语义尤为关键。

附:运行效果

三、发送请求

要发送请求,只需构造一个请求,并使用add()将其添加到RequestQueue中,如上所示。一旦你添加了请求,它将通过管道移动,得到服务,并且它的原始响应被解析和传递。

  • 1、当你调用add()时,Volley运行一个缓存处理线程和一个网络调度线程池。
  • 2、当您向队列添加请求时,它由缓存线程获取并分类:如果请求可以从缓存服务,缓存响应将在缓存线程上解析,并且解析的响应将在主线程上传递。
  • 3、如果请求无法从缓存服务,则将其放置在网络队列上。第一个可用的网络线程接收来自队列的请求,执行HTTP事务,解析工作线程上的响应,将响应写入缓存,并将解析的响应发回主线程进行传递。

请注意,诸如阻塞I / O和解析/解码等昂贵的操作在工作线程上完成。您可以从任何线程添加请求,但响应始终在主线程上传递。

  • 图1示出了请求的声明周期

四、取消请求

要取消请求,请在Request对象上调用cancel()。一旦取消,Volley保证您的Response Handler程序将永远不会被调用,请求会从中释放。这在实践中意味着您可以在Activity的onStop()方法中取消所有待处理的请求,并且在来处理响应处理程序时不必对getActivity()== null进行检查,是否调用了onSaveInstanceState()、是否已调用其他释放资源等生命周期方法。

要利用此行为,您通常必须跟踪所有正在运行的请求,以便能够在适当的时间取消它们。有一个更简单的方法:您可以将标记对象与每个请求相关联。然后,您可以使用此标记来提供要取消的请求范围。例如,您可以使用他们代表的活动来标记所有请求,并从onStop()调用requestQueue.cancelAll(this)。同样,您可以使用其各自的选项卡在ViewPager选项卡中标记所有缩略图请求,并在滑动上取消以确保新标签未被来自另一个的请求所阻止。

这是一个使用标记的字符串值的示例:

  • 1、定义你的标签,并把它添加到您的要求。
public static final String TAG = "MyTag";
StringRequest stringRequest; // Assume this exists.
RequestQueue mRequestQueue;  // Assume this exists.

// Set the tag on the request.
stringRequest.setTag(TAG);

// Add the request to the RequestQueue.
mRequestQueue.add(stringRequest);
  • 2、在您的活动的onStop()方法,取消有这个标记的所有请求。
protected void onStop () {
    super.onStop();
    if (mRequestQueue != null) {
        mRequestQueue.cancelAll(TAG);
    }
}

注意:只是取消了请求Request,并不会取消Request Handler。


关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 11:53:37 原文链接
阅读:124 评论:0 查看评论

3、volley 官方教程-建立一个请求队列

$
0
0

文章摘要
1、volley 网络请求队列和缓冲请求队列
2、volley 单例模式


英文文献

一、设定网络请求队列和缓冲请求队列

RequestQueue需要两件事来做它的工作:
- 一个网络来执行请求的传输
- 一个缓存来处理缓存。

在Volley toolbox中有这些可用的标准实现:DiskBasedCache为每一个文件提供了具有内存索引的响应缓存,BasicNetwork根据您首选的HTTP客户端提供网络传输。

BasicNetwork是Volley的默认网络实现,BasicNetwork必须使用HTTP client来完成初始化,通常是HttpURLConnection。

此代码段显示了设置RequestQueue所涉及的步骤:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView mTextView = (TextView) findViewById(R.id.text);

        RequestQueue mRequestQueue;

        // Instantiate the cache 1、初始化Cache
        Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap

        // Set up the network to use HttpURLConnection as the HTTP client.
        //2、建立网络连接
        Network network = new BasicNetwork(new HurlStack());

        // Instantiate the RequestQueue with the cache and network.
        //3、构建初始化RequestQueue
        mRequestQueue = new RequestQueue(cache, network);

        // Start the queue
        //4、启动RequestQueue
        mRequestQueue.start();

        String url = "http://www.example.com";

        // Formulate the request and handle the response.
        //5、初始化并构建Request
        StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        // Do something with the response
                        mTextView.setText("Response is: " + response.substring(0, 500));
                        Log.d("hlwang","CreateRequestQueue onResponse response is:"+response);
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        // Handle error
                        Log.d("hlwang","CreateRequestQueue onErrorResponse error is:"+error);
                    }
                });

        // Add the request to the RequestQueue.
        //6、将Request加入队列
        mRequestQueue.add(stringRequest);
    }

如果您只需要一次性请求,并且不想离开线程池,则可以在需要的地方创建RequestQueue,并在响应或错误返回后调用stop Queue,就像“在发送简单请求”中提到的Volley.newRequestQueue()方法。

但是更常见的用例是将RequestQueue创建为单例模式对象,以保持其在应用程序生命周期中运行:

二、使用Singleton模式

如果您的应用程序不断使用网络,可能最有效的方法是设置一个可以延长应用程序生命周期的RequestQueue实例。

要达到这种目标,有各种方式可以实现。 推荐的方法是实现封装RequestQueue和其他Volley功能的单例类。 另一种方法是在Application.onCreate()中继承Application并设置RequestQueue, 但这种做法是不鼓励的, 静态单例可以以更模块化的方式提供相同的功能。

一个关键的概念是RequestQueue必须用Application context而不是Activity context来实例化。 这样可以确保RequestQueue在您的应用程序生命周期中持续存在,而不是每次重新创建Activity时重新创建(例如,当用户旋转设备时)。

下面是一个提供RequestQueue和ImageLoader功能的单例类的例子:

package hailouwang.demosforapi.volley;

import android.content.Context;
import android.graphics.Bitmap;
import android.util.LruCache;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

/**
 * Created by ifei on 2017/7/26.
 */

public class MySingleton {
    private static MySingleton mInstance;
    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;
    private static Context mCtx;

    private MySingleton(Context context) {
        mCtx = context;
        mRequestQueue = getRequestQueue();

        mImageLoader = new ImageLoader(mRequestQueue,
                new ImageLoader.ImageCache() {
                    private final LruCache<String, Bitmap>
                            cache = new LruCache<String, Bitmap>(20);

                    @Override
                    public Bitmap getBitmap(String url) {
                        return cache.get(url);
                    }

                    @Override
                    public void putBitmap(String url, Bitmap bitmap) {
                        cache.put(url, bitmap);
                    }
                });
    }

    public static synchronized MySingleton getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new MySingleton(context);
        }
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.
            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
        }
        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req) {
        getRequestQueue().add(req);
    }

    public ImageLoader getImageLoader() {
        return mImageLoader;
    }
}

下面是执行的一些例子RequestQueue使用单例类的操作:

// Get a RequestQueue
RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()).
    getRequestQueue();

// ...

// Add a request (in this example, called stringRequest) to your RequestQueue.
MySingleton.getInstance(this).addToRequestQueue(stringRequest);

关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 11:55:00 原文链接
阅读:125 评论:0 查看评论

4、volley 官方教程-中标准请求的使用

$
0
0

文章摘要
1、Request JSON 类型的请求


英文文献

一、标准的volley请求

  • StringRequest。
    指定URL并接收原始字符串作为响应。
  • JsonObjectRequest和JsonArrayRequest(JsonRequest的两个子类)。
    指定URL并分别获取JSON对象或数组。

如果您的预期响应是这些类型之一,则可能不需要实现自定义请求。也就是标准请求。

二、Request JSON

Volley为JSON请求提供了以下类:

  • JsonArrayRequest - 在给定URL检索JSONArray响应正文的请求。
  • JsonObjectRequest - 在给定URL检索JSONObject响应体的请求,允许将可选JSONObject作为请求体的一部分传入。

这两个类都是基于公共基类JsonRequest的。 您使用它们遵循与其他类型的请求相同的基本模式。 例如,此代码段将获取JSON Feed,并将其显示为UI中的文本:

TextView mTxtDisplay;
ImageView mImageView;
mTxtDisplay = (TextView) findViewById(R.id.txtDisplay);
String url = "http://my-json-feed";

JsonObjectRequest jsObjRequest = new JsonObjectRequest
        (Request.Method.GET, url, null, new Response.Listener<JSONObject>() {

    @Override
    public void onResponse(JSONObject response) {
        mTxtDisplay.setText("Response: " + response.toString());
    }
}, new Response.ErrorListener() {

    @Override
    public void onErrorResponse(VolleyError error) {
        // TODO Auto-generated method stub

    }
});

// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);

测试log:

07-26 17:39:49.788 D/hlwang  (32586): VolleySimpleRequest onResponse response is:{"data":{"yesterday":{"date":"25日星期二","high":"高温 30℃","fx":"西南风","low":"低温 22℃,"type":"阵雨"},"city":"北京","aqi":"35","forecast":[{"date":"26日星期三","high":"高温 24","fengli":"微风级","low":"低温 19","fengxiang":"北风","type":"小到中雨"},{"dat温 28","fengli":"微风级","low":"低温 23","fengxiang":"南风","type":""},{"date":"28日星期五","high":"高温 29","fengli":"微风级","low":"低温 22","fengxiang":"南风","t日星期六","high":"高温 29","fengli":"微风级","low":"低温 20","fengxiang":"南风","type":"多云"},{"date":"30日星期天","high":"高温 30","fengli":"微风级","low":"低温 22"晴"}],"ganmao":"相对于今天将会出现大幅度降温,易发生感冒,请注意适当增加衣服,加强自我防护避免感冒。","wendu":"23"},"status":200,"message":"OK"}

附:json api测试接口:1、sojson。2、bejson


关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 13:21:59 原文链接
阅读:26 评论:0 查看评论

5、volley 官方教程-自定义请求

$
0
0

文章摘要
1、volley 自定义Request
2、volley GsonRequest 案例


英文文献

一、自定义Request

大多数请求在toolbox中具有实用型实现; 如果您的请求响应是字符串,图像或JSON,则可能不需要实现自定义请求。对于您需要实现自定义请求的情况,您需要执行以下操作:

  • 1、扩展Request类,其中表示Request期望的解析响应的类型。 因此,如果您的解析响应是字符串,那么,通过扩展Request来创建自定义请求。

  • 2、实现抽象方法parseNetworkResponse()和deliverResponse(),如下面更详细的描述。

二、parseNetworkResponse

对于给定的类型(如字符串,图像或JSON),响应封装了用于传递的解析响应。 以下是parseNetworkResponse()的示例实现:

@Override
protected Response<T> parseNetworkResponse(
        NetworkResponse response) {
    try {
        String json = new String(response.data,
        HttpHeaderParser.parseCharset(response.headers));
    return Response.success(gson.fromJson(json, clazz),
    HttpHeaderParser.parseCacheHeaders(response));
    }
    // handle errors
...
}
  • parseNetworkResponse() 中的一个参数NetworkResponse,它在byte []中包含响应数据,HTTP状态代码和响应Headers。
  • 您的实现必须返回Response,其中包含响应类型对象和缓存元数据或错误信息,比如在解析失败信息。

如果您的协议具有非标准缓存语义,您可以自行构建一个Cache Entry,但大多数请求都是这样的:

return Response.success(myDecodedObject,
        HttpHeaderParser.parseCacheHeaders(response));

Volley从Worker 线程调用parseNetworkResponse()。 这确保大量的数据解析操作(例如将JPEG解码为位图,),不会阻塞UI线程。

三、deliverResponse

Volley使用在主线程上通过parseNetworkResponse()中返回的对象,大多数请求在这里执行回调,例如:

protected void deliverResponse(T response) {
        listener.onResponse(response);

四、示例:GsonRequest

Gson库使用反射将Java对象转换为JSON。 您可以定义与其对应的JSON键具有相同名称的Java对象,将Gson传递给类对象,Gson将为您填写字段。 以下是使用Gson进行解析的Volley请求的完整实现:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView mTextView = (TextView) findViewById(R.id.text);

        //1、获得RequestQueue
        RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()).
                getRequestQueue();

        //2、初始化GSON Request
        String url = "http://www.sojson.com/api/beian/sojson.com";/*http://www.google.com*/
        GsonRequest<ComplanyWebSiteInfo> gsonRequest = new GsonRequest<>(url,ComplanyWebSiteInfo.class,
            null,new Response.Listener<ComplanyWebSiteInfo>(){
                @Override
                public void onResponse(ComplanyWebSiteInfo response) {
                    Log.d("hlwang","CustomVolleyRequest onResponse ComplanyWebSiteInfo is:"+response);
                    mTextView.setText(response.toString());
                }
            },new Response.ErrorListener(){
                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.d("hlwang","CustomVolleyRequest onErrorResponse error:"+error.getMessage());
                    mTextView.setText("That didn't work!");
                }
            });
        //3、加入RequestQueue
        queue.add(gsonRequest);
    }

    class ComplanyWebSiteInfo{
        private String indexUrl;
        private String icp;

        public String getIndexUrl() {
            return indexUrl;
        }

        public void setIndexUrl(String indexUrl) {
            this.indexUrl = indexUrl;
        }

        @Override
        public String toString() {
            return "ComplanyWebSiteInfo{" +
                    "indexUrl='" + indexUrl + '\'' +
                    ", icp='" + icp + '\'' +
                    '}';
        }
    }

    class GsonRequest<T> extends Request<T> {
        private final Gson gson = new Gson();
        private final Class<T> clazz;
        private final Map<String, String> headers;
        private final Response.Listener<T> listener;

        /**
         * Make a GET request and return a parsed object from JSON.
         *
         * @param url URL of the request to make
         * @param clazz Relevant class object, for Gson's reflection
         * @param headers Map of request headers
         */
        public GsonRequest(String url, Class<T> clazz, Map<String, String> headers,
                           Response.Listener<T> listener, Response.ErrorListener errorListener) {
            super(Method.GET, url, errorListener);
            this.clazz = clazz;
            this.headers = headers;
            this.listener = listener;
        }

        @Override
        public Map<String, String> getHeaders() throws AuthFailureError {
            return headers != null ? headers : super.getHeaders();
        }

        @Override
        protected void deliverResponse(T response) {
            listener.onResponse(response);
        }

        @Override
        protected Response<T> parseNetworkResponse(NetworkResponse response) {
            try {
                String json = new String(
                        response.data,
                        HttpHeaderParser.parseCharset(response.headers));
                return Response.success(
                        gson.fromJson(json, clazz),
                        HttpHeaderParser.parseCacheHeaders(response));
            } catch (UnsupportedEncodingException e) {
                return Response.error(new ParseError(e));
            } catch (JsonSyntaxException e) {
                return Response.error(new ParseError(e));
            }
        }
    }

附:演示效果:


关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 13:25:06 原文链接
阅读:28 评论:0 查看评论

6、volley 源码解析之工作流程综述

$
0
0

文章摘要
1、volley 中的工作线程
2、volley 工作步骤
3、RequestQueue初始化以及初始化逻辑


附:获取Volley源代码
Demos案例源码:https://github.com/HailouWang/DemosForApi

感悟:

Volley的源代码真的值得去读,其中包含了很多非常好的处理逻辑,例如:Volley对其工作流程的架构、线程缓存的处理、网络请求数据的解析以及处理、其中用到的设计模式等。

当我们明确了volley要解决的开发痛点后,volley提供了松耦合的架构实现,我们可以很方便的在其框架实现内,为其进行功能扩展。引用设计模式的一句话:一切为了松耦合的设计而努力。

简介:

volley有两个主要的民工,CacheDispatcher以及NetworkDispatcher,也是两个线程,管理并处理Request任务。

volley为了保证大批量的网络请求以及数据解析不会影响到主线程的用户交互,使用了很多线程以及线程封装技巧。包括这里的Cache。

在用户发起网络请求后,volley就将用户的请求,丢到了本文介绍的缓存进程,缓存线程如果没有能力处理,就丢给网络线程,并告诉它,老大需要数据结果,你赶紧去网络上去拿。老大只要结果,不要过程。

主线程很忙,ResponseDelivery负责传递消息,伴君如伴虎,为了防止打扰到主线程的工作,ResponseDelivery也可以有一个线程,在目前的源码里,ResponseDelivery充分利用Handler的MessageQueue优势,管理并小心的将结果传递给主线程。

1、volley 中,存在三类线程:

  • 1、主线程。将获取的数据刷新到UI,发送网络请求等。
  • 2、缓存进程(Cache Thread)。管理缓存数据,主线程的请求,优先从主线程中获取数据,其次将请求分发到网络中。
  • 3、网络线程(Network Thread)。用户可以制定网络线程的数量,默认是4个。定义了从网络获取数据的流程,但不包括网络同步逻辑。这样子的设计,可以更好的释放线程和网络解析逻辑之间的耦合。
for (int i = 0; i < DEFAULT_NETWORK_THREAD_POOL_SIZE; i++) {
    NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
      mCache, mDelivery);
    mDispatchers[i] = networkDispatcher;
    networkDispatcher.start();
}

2、volley 工作步骤

之前的Demos中发送一个简单的请求,无论是通过RequestQueue还是通过ImageLoader,大致的使用步骤是一样的,大体包括以下步骤:

  • 2.1、初始化RequestQueue或者ImageLoader。
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";
  • 2.2、初始化Request
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        // Display the first 500 characters of the response string.
        mTextView.setText("Response is: "+ response.substring(0,500));
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        mTextView.setText("That didn't work!");
    }
});
  • 2.3、 将Request 加入到RequestQueue。
// Add the request to the RequestQueue.
queue.add(stringRequest);

后两个步骤,是对RequestQueue的使用,所以,我们将重点放在第一部分,即:RequestQueue的创建流程。

3、RequestQueue初始化

RequestQueue会对Request进行管理,它的作用,更多的是作为一个工具类,通过add方法,将Request分发给Cache线程以及网络线程。

  • 3.1、首先通过newRequestQueue方法获得RequestQueue对象。
    public static RequestQueue newRequestQueue(Context context) {
        //1、初始化Network对象。Network的意义是通过performRequest方法解析Request,生成Response对象。
        HttpStack stack;
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        Network network = new BasicNetwork(stack);
        //2、初始化RequestQueue,共计两个参数:第一个参数是Cache缓冲区,可选,即:可不缓冲。
        //第二个参数是network对象,也是关系解析网络数据的主要劳工。
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

Cache类图
- 3.2、RequestQueue的实例初始化,调用start后,线程会跑起来。

    /**
     * Creates the worker pool. Processing will not begin until {@link #start()} is called.
     *创建一个工作池,非start方法调用前,不会开始执行
     * @param cache A Cache to use for persisting responses to disk
     * 1、缓存者。将相应数据持久化到硬盘
     * @param network A Network interface for performing HTTP requests
     * 2、网络处理者。处理HTTP请求的Network 接口
     * @param threadPoolSize Number of network dispatcher threads to create
     * 3、网络请求分发者。默认4个分发线程池
     * @param delivery A ResponseDelivery interface for posting responses and errors
     * 4、响应传递者。运行在主线程,传递响应数据,以及错误日志信息,实现来自:Handler的封装
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

4、RequestQueue将Request抛给工作线程

RequestQueue通过add方法,将Request抛给工作线程,工作线程包含我们上面说的Cache线程、Network线程。

//1、将Request加入到mCurrentRequests,因为本工具类还需要管理Request,例如:cancel等。
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
    mCurrentRequests.add(request);
}

// Process requests in the order they are added.
//2、包装Request。为其赋予SequenceNumber
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");

// If the request is uncacheable, skip the cache queue and go straight to the network.
//3、如果Request不需要缓存,则直接加入到Network线程
if (!request.shouldCache()) {
    mNetworkQueue.add(request);
    return request;
}

// Insert request into stage if there's already a request with the same cache key in flight.
//4、如果请求(Request)不是在等待中(即:mWaitingRequests中),那么就先抛给缓冲线程(mCacheQueue)。
synchronized (mWaitingRequests) {
    String cacheKey = request.getCacheKey();
    if (mWaitingRequests.containsKey(cacheKey)) {
        // There is already a request in flight. Queue up.
        Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
        if (stagedRequests == null) {
            stagedRequests = new LinkedList<>();
        }
        stagedRequests.add(request);
        mWaitingRequests.put(cacheKey, stagedRequests);
        if (VolleyLog.DEBUG) {
            VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
        }
    } else {
        // Insert 'null' queue for this cacheKey, indicating there is now a request in
        // flight.
        mWaitingRequests.put(cacheKey, null);
        mCacheQueue.add(request);
    }

缓冲区(RequestQueue)在获得Request后,接着如何处理,请关注:volley 源码解析之缓存线程工作流程


关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 13:26:12 原文链接
阅读:31 评论:0 查看评论

7、volley 源码解析之缓存线程工作流程

$
0
0

文章摘要
1、volley 缓存线程运行流程
2、volley 实现分解步骤


附:获取Volley源代码
Demos案例源码:https://github.com/HailouWang/DemosForApi

简介:

volley有两个主要的民工,CacheDispatcher以及NetworkDispatcher,也是两个线程,管理并处理Request任务。

volley为了保证大批量的网络请求以及数据解析不会影响到主线程的用户交互,使用了很多线程以及线程封装技巧。包括这里的Cache。

在用户发起网络请求后,volley就将用户的请求,丢到了本文介绍的缓存进程,缓存线程如果没有能力处理,就丢给网络线程,并告诉它,老大需要数据结果,你赶紧去网络上去拿。老大只要结果,不要过程。

主线程很忙,ResponseDelivery负责传递消息,伴君如伴虎,为了防止打扰到主线程的工作,ResponseDelivery也可以有一个线程,在目前的源码里,ResponseDelivery充分利用Handler的MessageQueue优势,管理并小心的将结果传递给主线程。

那么,本文就来介绍下缓存线程的工作原理:

一、CacheDispatcher线程

  • 1、Volley中有三个线程,CacheDispatcher是其中的缓存线程。

  • 2、CacheDispatcher缓冲线程的目的是在缓冲池中,执行分流,将Request请求分发出去。
    线程循环运行,线程原料来自mCacheQueue,在主线程可通过RequestQueue.add方法将请求加入mCacheQueue。

  • 3、可以将工作流程简单归纳为以下几步:

    • 1、线程循环运行,线程原料来自mCacheQueue。
    • 2、优先从缓冲区获得数据,如果缓存区中存在数据,则直接返回数据给主线程。
    • 3、如果缓存区【没有命中数据】或者【缓存数据过期】,则将请求(Request)分发给NetworkDispatcher(网络线程),网络线程会重新同步数据。

附:流程图

  • Volley Cache Thread流程图.png

二、实现分析

1、线程循环运行,获得Request对象

while (true) {
  try {
    // Get a request from the cache triage queue, blocking until
    // at least one is available.
    //1、hlwang:CacheDispatcher原料来自mCacheQueue,第一步,获得Request
    final Request<?> request = mCacheQueue.take();
    request.addMarker("cache-queue-take");
    ... ...
  }
}

2、如果Request被用户取消了,则不再需要继续执行了

// If the request has been canceled, don't bother dispatching it.
//2、hlwang:如果request已取消,已经不需要继续了
if (request.isCanceled()) {
    request.finish("cache-discard-canceled");
    continue;
}

3、优先检查缓存区,如果数据没有命中(即:数据不存在),则交给Network线程去同步数据

// Attempt to retrieve this item from cache.
//3、hlwang:如果在缓存中,不存在数据,说明是新数据,则:交给mNetworkQueue去同步新数据。
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
    request.addMarker("cache-miss");
    // Cache miss; send off to the network dispatcher.
    mNetworkQueue.put(request);
    continue;
}

4、如果缓存数据过期了,依旧交给Network线程去同步数据

// If it is completely expired, just send it to the network.
//4、hlwang:如果缓存过期,那么说明数据太旧了,交给mNetworkQueue去同步新数据。
if (entry.isExpired()) {
    request.addMarker("cache-hit-expired");
    request.setCacheEntry(entry);
    mNetworkQueue.put(request);
    continue;
}

5、缓存的数据被命中,则解析缓存数据,构建Response对象

// We have a cache hit; parse its data for delivery back to the request.
//5、wanghailu:我们命中了一条缓存数据(w找到了一个保质期内的缓存hl),解析数据并构建响应对象Response。
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
        new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");

6、如果缓存数据,不需要刷新,则将响应数据,通过Delivery回调给用户

if (!entry.refreshNeeded()) {
    // Completely unexpired cache hit. Just deliver the response.
    //6、如果entry数据不需要刷新,则使用mDelivery将响应传递出去
    mDelivery.postResponse(request, response);
} 

7、缓存的数据需要再次更新,那么现将缓存数据返回给用户,接着通过主线程发起同步数据请求

    // Soft-expired cache hit. We can deliver the cached response,
    // but we need to also send the request to the network for
    // refreshing.
    //7、虽然被缓存命中,但数据轻微过期。我们可以将缓存响应数据传递分发,
    //但我们同样需要将请求发送到mNetworkQueue去刷新、更新。
    request.addMarker("cache-hit-refresh-needed");
    request.setCacheEntry(entry);

    // Mark the response as intermediate.
    //7.1、更新response状态为  媒介
    response.intermediate = true;

    // Post the intermediate response back to the user and have
    // the delivery then forward the request along to the network.
    //7.2、主线程分发
    mDelivery.postResponse(request, response, new Runnable() {
        @Override
        public void run() {
            try {
                mNetworkQueue.put(request);
            } catch (InterruptedException e) {
                // Not much we can do about this.
            }
        }

关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 13:30:57 原文链接
阅读:31 评论:0 查看评论

8、volley 源码解析之网络线程工作流程

$
0
0

文章摘要
1、volley 网络线程工作原理
2、volley 实现 分解原理


附:获取Volley源代码
Demos案例源码:https://github.com/HailouWang/DemosForApi

简介:

volley有两个主要的民工,CacheDispatcher以及NetworkDispatcher,也是两个线程,管理并处理Request任务。

volley为了保证大批量的网络请求以及数据解析不会影响到主线程的用户交互,使用了很多线程以及线程封装技巧。包括这里的Cache。

在用户发起网络请求后,volley就将用户的请求,丢到了本文介绍的缓存进程,缓存线程如果没有能力处理,就丢给网络线程,并告诉它,老大需要数据结果,你赶紧去网络上去拿。老大只要结果,不要过程。

主线程很忙,ResponseDelivery负责传递消息,伴君如伴虎,为了防止打扰到主线程的工作,ResponseDelivery也可以有一个线程,在目前的源码里,ResponseDelivery充分利用Handler的MessageQueue优势,管理并小心的将结果传递给主线程。

那么,本文就来介绍下网络线程的工作原理:

1、NetworkDispatcher 网络线程

1、Volley中有三个线程,NetworkDispatcher是其中的网络线程。

2、NetworkDispatcher网络线程的目的是同步网络数据、保存网络数据。

网络线程,只负责同步网络数据,不负责解析。解析工作交给请求来做,因为不同的请求,解析的方法、流程多不同,将这些工作交给发起方也就是Request来处理。

线程循环运行,线程原料来自mNetworkQueue,其中的来源主要包括两部分:
- a)、主线程可通过RequestQueue.add方法将请求加入mNetworkQueue。
- b)、mCacheQueue发现缓存数据已过期、或者需要再次从网络上来同步。

3、可以将工作流程简单归纳为以下几步:

  • a)、线程循环运行,线程原料来自mNetworkQueue。
  • b)、同步网络数据,并将网络返回的原始数据,封装成NetworkResponse。
  • c)、调用请求发起方(Request)的解析函数,得到result。

附:流程图

2、实现分析

1、线程循环运行,获得Request对象

while (true) {
  try {
    // Get a request from the cache triage queue, blocking until
    // at least one is available.
    //1、hlwang:CacheDispatcher原料来自mCacheQueue,第一步,获得Request
    final Request<?> request = mCacheQueue.take();
    request.addMarker("cache-queue-take");
    ... ...
  }
}

2、如果Request被用户取消了,则不再需要继续执行了

// If the request has been canceled, don't bother dispatching it.
//2、hlwang:如果request已取消,已经不需要继续了
if (request.isCanceled()) {
    request.finish("cache-discard-canceled");
    continue;
}

3、同步网络,得到原始数据,保存在NetworkResponse。

// Perform the network request.
//3、网络处理requests,并返回NetworkResponse
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");

4、不需要将响应回调给主线程的情况。

// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
//4、如果服务器返回状态码 = 304,同时该请求Request已经传递过,则不需要再次响应传递。
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
    request.finish("not-modified");
    continue;
}

5、如何解析原始数据,只有请求方才知道,故:解析工作交给请求方

// Parse the response here on the worker thread.
//5、解析请求响应数据,得到Response数据
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");

6、缓存操作,如果数据需要缓存,那么调用传递的缓存接口来缓存。

// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
//6、是否需要缓存并含有缓存数据,如果是,将cache数据加入到mCache
if (request.shouldCache() && response.cacheEntry != null) {
    mCache.put(request.getCacheKey(), response.cacheEntry);
    request.addMarker("network-cache-written");
}

7、通过mDelivery,将数据返回给主线程。

// Post the response back.
//7、将reponse 返回
request.markDelivered();
mDelivery.postResponse(request, response);

关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 13:32:33 原文链接
阅读:27 评论:0 查看评论

9、volley 源码解析之消息分发工的工作流程

$
0
0

文章摘要
1、volley 消息传递工 工作原理


附:获取Volley源代码
Demos案例源码:https://github.com/HailouWang/DemosForApi

简介:

volley有两个主要的民工,CacheDispatcher以及NetworkDispatcher,也是两个线程,管理并处理Request任务。

volley为了保证大批量的网络请求以及数据解析不会影响到主线程的用户交互,使用了很多线程以及线程封装技巧。包括这里的Cache。

在用户发起网络请求后,volley就将用户的请求,丢到了本文介绍的缓存进程,缓存线程如果没有能力处理,就丢给网络线程,并告诉它,老大需要数据结果,你赶紧去网络上去拿。老大只要结果,不要过程。

主线程很忙,ResponseDelivery负责传递消息,伴君如伴虎,为了防止打扰到主线程的工作,ResponseDelivery也可以有一个线程,在目前的源码里,ResponseDelivery充分利用Handler的MessageQueue优势,管理并小心的将结果传递给主线程。

那么,本文就来介绍下消息传递工(ResponseDelivery)的工作原理:

1、传递工人(ResponseDelivery)初始化。

在初始化RequestQueue工具类时,会初始化传递工。传递工使用Handler来管理队列,Handler的Looper来自主线程。

    /**
     * Creates the worker pool. Processing will not begin until {@link #start()} is called.
     *创建一个工作池,非start方法调用前,不会开始执行
     * @param cache A Cache to use for persisting responses to disk
     * 1、缓存者。将相应数据持久化到硬盘
     * @param network A Network interface for performing HTTP requests
     * 2、网络处理者。处理HTTP请求的Network 接口
     * @param threadPoolSize Number of network dispatcher threads to create
     * 3、网络请求分发者。默认4个分发线程池
     * @param delivery A ResponseDelivery interface for posting responses and errors
     * 4、响应传递者。传递响应数据,以及错误日志信息
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

    /**
     * Creates the worker pool. Processing will not begin until {@link #start()} is called.
     *
     * @param cache A Cache to use for persisting responses to disk
     * @param network A Network interface for performing HTTP requests
     * @param threadPoolSize Number of network dispatcher threads to create
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

在这里,原生提供了一种默认的实现:

new ExecutorDelivery(new Handler(Looper.getMainLooper()))

在这段代码中,需要传入一个Handler对象,对象中的Loop来自主线程,可以这么说,Handler工作在主线程中。

备注:我们也可以创建自己的传递工(ResponseDelivery),原生提供的实现中,类图如下:

2、传递工人(ResponseDelivery)接收回调任务。

我们之前介绍过了,volley有两个默默工作的劳工,它们就是CacheDispatcher以及NetworkDispatcher,劳工们在做完工作后,是没有权利告诉主线程的,因为这是传递工人的工作。

NetworkDispatcher或者CacheDispatcher通过如下方式告诉ResponseDelivery,需要向主线程传递结果。

mDelivery.postResponse(request, response);

无论是Response还是Error,所有的这些响应都发给了ResponseDelivery中的线程池。

3、传递工人对接收到的任务的处理

    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }
    @Override
    public void postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

    @Override
    public void postError(Request<?> request, VolleyError error) {
        request.addMarker("post-error");
        Response<?> response = Response.error(error);
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
    }

线程池会将得到的消息,包装一个Runnable,利用Handler发送给主线程。

// If this request has canceled, finish it and don't deliver.
//1、如果请求已经取消,则不必传递
if (mRequest.isCanceled()) {
    mRequest.finish("canceled-at-delivery");
    return;
}

// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
    //2、如果Response没有错误,则分发result
    mRequest.deliverResponse(mResponse.result);
} else {
    //3、如果Reponse有错误,则分发error
    mRequest.deliverError(mResponse.error);
}

// If this is an intermediate response, add a marker, otherwise we're done
// and the request can be finished.
//4、如果 响应属于媒介,标记marker
if (mResponse.intermediate) {
    mRequest.addMarker("intermediate-response");
} else {
    mRequest.finish("done");
}

4、传递工人将消息回调给主线程。

通过3中可以看到,发往主线程的回调,是通过mRequest.deliverResponse来实现的。

/**
 * Subclasses must implement this to perform delivery of the parsed
 * response to their listeners.  The given response is guaranteed to
 * be non-null; responses that fail to parse are not delivered.
 * @param response The parsed response returned by
 * {@link #parseNetworkResponse(NetworkResponse)}
 */
abstract protected void deliverResponse(T response);

通过Request#deliverResponse,我们看到,这是一个Abstract方法,不同的实现,传递方式也不同。这里介绍下原生的实现:
StringRequest.java

@Override
protected void deliverResponse(String response) {
    mListener.onResponse(response);
}

关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 13:33:17 原文链接
阅读:31 评论:0 查看评论

编程路上,对于迷失者的一些小小建议

$
0
0

前几天,在半梦半醒中写了一篇《编程路上,送给处于迷茫中的你和自己》,没想到还挺受欢迎,同时收到了一些朋友的留言和感谢,意外之余也挺开心。

这里写图片描述

大多人都会经历的迷茫

其实这也都难免的,现在计算机技术更新那么快,日新月异,各种技术、各种语言爆发式增长,我一个好朋友在小日本(没有鄙视的意思,习惯这么称呼了)从事开发工作,经常和我说想转行,做不下去了,公司一会让她学PHP,一会让她做HTML+CSS,过一段时间又是jsp,说不定哪一天又是让她做数据库,每次打电话都要诉苦一会,做为过来人,我也很明白她的苦楚,刚工作的一两年太累了,一个女生在异国他乡做着这样的工作,确实很不容易。最初不懂她那边情况,建议让她好好学一门,喜欢哪门技术就走哪条路子,可现实是残酷的,白天还要上班,上班期间做的可能是另外一门语言,下班时候已经头昏脑热了,吃个饭、散散步时间就不早了,哪有那么多时间再去学习别的知识,当一门语言熟悉了点后,项目更换了,又要接手其它语言。工作一年了,总是在几门语言中徘徊,而且对编程兴趣不大,目前做着类似于产品经理的事情,我曾推荐她,如果真的做不下去了,还不如早点转产品,这职位挺合适她。

建议:迷茫不可怕,可怕的是不知道接下来的路该怎么走。如果目前这份工作真的让自己不开心了,如果真的觉得自己做不下去了,转行要趁早。既然铁了心走下去,跪着也要走完自己选择的路,每个人都会经历这种迷茫,不妨把手头能做的事情做得更好,能学的东西学得更好。

这里写图片描述

贪多嚼不烂

中国有句古话叫做“贪多嚼不烂”,这句话在软件学习中也挺适用的,在最初工作的时候,我加了好多群,静静地看着群里那些人讨论各种技术,从前期学习角度来说,个人觉得QQ群是一个很好的平台,非常适合新人和学生,同样一个知识点,不同的人会有不同的想法和解释,总有一种解释适合你,实在看不懂去问,一般都能找到自己想要的答案。我是科班出生,然并卵,之前也提到了,入行的时候,我连最基本的九九乘法表都写不出来,工作时候什么都要自学,自学最大的弊端就是对很多名词不是很理解,遇到一些自己不懂的名词可以去查一下,时间充足的情况下,一天可以多了解几个,重在学会而不在多,第一份工作最大的好处就是时间多,多的怀疑人生,每天就是无脑的看视频和看书,看到烦的时候就去QQ群看消息,每当在QQ群里看到一些不理解或者陌生的名词,我就默默去百度(是的,那时候很少用Google,一方面是不会翻墙,另一方面是觉得百度就够了,当学会翻墙后,才看到质的区别,一个是送外卖,一个是推动人类发展的),有个群聊得多了,慢慢的混成了管理,在里面结识了好几个哥们,其中一个后来成了我很好的同事,现在是个全能型技术大牛,猿粪^_^。

建议:对于类似QQ群这样平台,鱼龙混杂,我也加过一些技术讨论群,群里就是吹吹牛、斗斗图,良禽择木而栖,对于要学的东西,贵在精与会,而不在多。

出门遇贵人

在之前那篇《编程路上,送给处于迷茫中的你和自己》中,从江阴的第一份工作离职到后来去了南京,中间有三个多月的时间没写上去,那时候我先去了上海,其实那时候拿到好几份offer,不知道是不想上班还是面试上瘾,都推掉了,又去了苏州,到了苏州,最多一天面试四家,又拿到了几份offer,后来又去了上海面试,已经过了年后找工作的黄金时间,offer没那么好拿了,继续找了一周工作,没合适的又去了苏州,入职金阊区的一家公司,不到一个月便换了工作,去了相城的一家公司,这家公司时间也不长就离职了,但这家公司给了我很大的收获,短短的二十多天时间里,我遇到职业生涯的第一位贵人,他叫Z汉生(他也是我入行以来最感谢的两个人之一),是做java的,对于我认知的java世界,就没发现有什么问题他不会的,上篇《线程池原理》就是出自他分享给我的博客,认识他的三年多,一直如此,神一样的存在,他很喜欢把自己会的教给别人,再加上本身就是学霸级别的,跟他共事的那段时间,感觉飞一般的进步。好景不长,因为种种原因,我们都从那家公司走了,他去了上海,我去了南京。

对于很多人来说,可能一生都不会遇到汉生那样的贵人相助,这件事,我一直感到很幸运,有时候在我自我感觉很良好的时候,每当和汉生大神一起吃饭,总感觉自己像个刚入行的小学生一样,坐在那里默默的听着老师讲着信手拈来的课。昨天再看《深入理解java虚拟机》的时候,下载XX笔记,很惊喜,大神给我分享好几篇我最近正想学的东西,(^__^) 嘻嘻……

建议:伯乐难寻,或许我也不是千里马,但是遇到这样亦师亦友的伙伴,还是要好好珍惜,很宝贵的一笔财富。很多时候,别人没必要对你好,只是认为值得才会不求回报的付出,感谢生命中指点过我的每个人。

这里写图片描述

提高自学能力

工作的几年里面,通过面试和被面试,还有各种聊天工具上沟通过的开发人员数不胜数,发现好多工作好几年的,说出的话和工作年限完全不符合,面向对象的六大基本原则更是不清晰,就不说代码质量了,对于学习设计模式,这些都是基础课程,一味地control C和control V没什么意思,那是刚工作该做的事,而对于开发的工作生涯,设计模式相当于兵家的《孙子兵法》和《三十六计》,可以使人更加聪明的写代码,基础的有《大话设计模式》、《head first 设计模式》,讲的深一点的有《设计模式之禅》、《java与模式》,如果对C#代码无障碍,个人更推荐《大话设计模式》和《java与模式》,这几本书我都有,做过对比,《大话设计模式》更通俗易懂,入门经典,《java与模式》讲的更全面更深入,可以更上一层楼,当然,另外两本书也都是良心之作,挺好。

现在专业APP也很多,csdn、简书、开源中国、博客园等,还有更加方便的微信公众号,上下班路上或者晚上睡觉前都可以逛一逛,看不懂的,看看热闹也挺好,扩展下知识面。

对于处于迷茫期的新人,很多人都有去培训的想法,为啥培训就一定能学好?既然培训能学好,为啥自学就学不好?是因为花了钱心疼还是因为有人指点才能学好?工作以后主要靠自学而不是被培训,公司更看重一个人的自学能力,Android路上,我是一路自学走过来的,对于这条路的辛苦知根知底,现在部分培训机构无德,不想着好好教学生知识,总是教学生怎么吹牛,背面试宝典,忽悠到高工资然后给培训机构好招人,被坑的却是企业,基本的职业道德都没有,满满的嫌弃,但还是有些培训机构挺不错的,我最初入门的时候看的也是培训机构流出来的视频,质量相当的高。

建议:自学的态度,很大一部分决定一个人的高度,战胜别人容易,战胜自己太难。

这里写图片描述

总结

建议已经给了好几条了,最后就想说一句话,基础才是重中之重,坚实的基础才能建造宏伟的建筑。

微信扫我^_^

这里写图片描述

作者:pangpang123654 发表于2017/7/30 19:49:10 原文链接
阅读:193 评论:2 查看评论
Viewing all 5930 articles
Browse latest View live