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

android应用开发-从设计到实现 3-4 静态原型的状态栏

$
0
0

静态原型的状态栏

状态栏Symbol

状态栏似乎很复杂,有wifi信号、手机信号、时间、电量等信息,幸好Sketch原生就自带的现成组件,你可以直接拿过来就用了。当然,你也可以自己一个一个去画,不过既然有了现成的轮子,又何必重复劳动呢。

菜单栏中选择File -> New From Template,在弹出的菜单中选择Material Design。此时会创建一个新的工程文件。

 sketch_new_material_design

与之前空的工程文件不同的是,这里面已经有了两个现成的page,里面的内容就是Material Design会使用到的各种现成的组件。

 sketch_status_bar_symbol

选择Material Design Symbol -> Material/Android/Status bar 360dp black,这就是一个现成的可用的状态栏。这样的组件叫做symbol,它是有多个图形组合后形成的一个通用符号。Symbol可以被不同的page、项目共享使用。

以后只要看到这个图案,就知道这是一个symbol了,

 sketch_symbo

我们也可以创建自己的symbol,不过这个地方还不会用到。随着设计开发的深入,在这个章节的后半部分,我们会进行详细的介绍。

使用状态栏Symbol

使用鼠标右键 + Copy(或者cmd + c)之后,将它粘贴(鼠标右键 + Paste Over或者cmd + v)到我们之前的weather page之中,

 sketch_paste_symbol

如果状态栏粘贴之后,并没有对齐画板的边缘,手动将它移动,对齐就可以了。

此时可以看到,

  1. Pages下面自动多出了一个叫做symbols的页面,它里面放的就是我们刚引进的symbol

  2. 这时的statusbar还是一个独立的部分,并没有隶属于Mobile Portrait画板之下,两者还是平级的关系。

调整状态栏Symbol

接下来我们就开始调整statusbar与Mobile Portrait之间的隶属关系。

  1. 将状态栏展开,选中里面的内容;
  2. 将各个组件拖入到Mobile Portrait之下;
  3. 删除没有内容的状态栏symbol;
  4. 将刚拖入的组件使用工具栏上的Group功能组合起来,并将组合后的组件更名为Statusbar
 sketch_modify_statusbar_parent

选中状态栏,我们可以看到状态栏的尺寸是:24px*360px,刚好是前面讲过的状态栏应该的高度。

修改背景色

将状态栏的背景颜色修改成之前设定的Primary Color:#3F51B5,

 sketch_statusbar_color

本文是《从设计到实现-手把手教你做android应用开发》系列文档中的一篇。感谢您的阅读和反馈,对本文有任何的意见和建议请留言,我都会尽量一一回复。

如果您觉得本文对你有帮助,请推荐给更多的朋友;或者加入我们的QQ群348702074和更多的小伙伴一起讨论;也希望大家能给我出出主意,让这些文档能讲的更好,能最大化的帮助到希望学习开发的伙伴们。

除了CSDN发布的文章,本系列最新的文章将会首先发布到我的专属博客book.anddle.com。大家可以去那里先睹为快。


同时也欢迎您光顾我们在淘宝的网店安豆的杂货铺。店中的积木可以搭配成智能LED灯,相关的配套文档也可以在这里看到。

这些相关硬件都由我们为您把关购买,为大家节省选择的精力与时间。同时也感谢大家对我们这些码农的支持。

最后再次感谢各位读者对安豆的支持,谢谢:)

作者:anddlecn 发表于2017/3/23 16:50:32 原文链接
阅读:210 评论:0 查看评论

android classloader双亲委托模式

$
0
0

概述

ClassLoader的双亲委托模式:classloader 按级别分为三个级别:最上级 : bootstrap classLoader(根类加载器) ; 中间级:extension classLoader (扩展类加载器) 最低级 app classLoader(应用类加载器)。

根(Bootstrap)类加载器:该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等。例如java.lang.Object就是由根类加载器加载的。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承java.lang.ClassLoader类。

扩展(Extension)类加载器:它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。

系统(System)类加载器:也称为应用类加载器,它的父加载器为扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader类的子类。
父子加载器并非继承关系,也就是说子加载器不一定是继承了父加载器。

对于Java来说,java 虚拟机要将被用到的java类文件通过classLoader 加载到JVM内存中,而这个classloader就是bootstrap classloader。而对于APP而言,首先请求app 级来加载,继而请求extension classLoader,最后启动bootstrap classLoader 。

自定义ClassLoader

这里写图片描述

public class MyClassLoader extends ClassLoader {

    //类加载器名称
    private String name;
    //加载类的路径
    private String path = "D:/";
    private final String fileType = ".class";
    public MyClassLoader(String name){
        //让系统类加载器成为该 类加载器的父加载器
        super();
        this.name = name;
    }

    public MyClassLoader(ClassLoader parent, String name){
        //显示指定该类加载器的父加载器
        super(parent);
        this.name = name;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public String toString() {
        return this.name;
    }

    private byte[] loaderClassData(String name){
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        this.name = this.name.replace(".", "/");
        try {
            is = new FileInputStream(new File(path + name + fileType));
            int c = 0;
            while(-1 != (c = is.read())){
                baos.write(c);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    @Override
    public Class<?> findClass(String name){
        byte[] data = loaderClassData(name);
        return this.defineClass(name, data, 0, data.length);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //loader1的父加载器为系统类加载器
        MyClassLoader loader1 = new MyClassLoader("loader1");
        loader1.setPath("D:/lib1/");
        //loader2的父加载器为loader1
        MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
        loader2.setPath("D:/lib2/");
        //loader3的父加载器为根类加载器
        MyClassLoader loader3 = new MyClassLoader(null, "loader3");
        loader3.setPath("D:/lib3/");

        Class clazz = loader2.loadClass("Sample");
        Object object = clazz.newInstance();
    }
}
public class Sample {

    public Sample(){
        System.out.println("Sample is loaded by " + this.getClass().getClassLoader());
        new A();
    }
}
public class A {

    public A(){
        System.out.println("A is loaded by " + this.getClass().getClassLoader());
    }
}

每一个自定义ClassLoader都必须继承ClassLoader这个抽象类,而每个ClassLoader都会有一个parent ClassLoader,我们可以看一下ClassLoader这个抽象类中有一个getParent()方法,这个方法用来返回当前 ClassLoader的parent,注意,这个parent不是指的被继承的类,而是在实例化该ClassLoader时指定的一个 ClassLoader,如果这个parent为null,那么就默认该ClassLoader的parent是bootstrap classloade。

上面讲解了一下ClassLoader的作用以及一个最基本的加载流程,接下来我们说说ClassLoader使用了双亲委托模式进行类加载。

ClassLoader

双亲委托模式

通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

为了更好的理解双亲委托模式,我们先自定义一个ClassLoader,假设我们使用这个自定义的ClassLoader加载 java.lang.String,那么这里String是否会被这个ClassLoader加载呢?

事实上java.lang.String这个类并不会被我们自定义的classloader加载,而是由bootstrap classloader进行加载,为什么会这样?实际上这就是双亲委托模式的原因,因为在任何一个自定义ClassLoader加载一个类之前,它都会先 委托它的父亲ClassLoader进行加载,只有当父亲ClassLoader无法加载成功后,才会由自己加载。而在上面的例子中,因为 java.lang.String是属于java核心API的一个类,所以当使用自定义的classloader加载它的时候,该 ClassLoader会先委托它的父亲ClassLoader进行加载(bootstrap classloader),所以并不会被我们自定义的ClassLoader加载。

我们来看一下ClassLoader的一段源码:

protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{  
         // 首先检查该name指定的class是否有被加载  
         Class c = findLoadedClass(name);  
         if (c == null) {  
             try {  
                 if (parent != null) {  
                     //如果parent不为null,则调用parent的loadClass进行加载  
                     c = parent.loadClass(name, false);  
                 }else{  
                     //parent为null,则调用BootstrapClassLoader进行加载  
                     c = findBootstrapClass0(name);  
                 }  
             }catch(ClassNotFoundException e) {  
                 //如果仍然无法加载成功,则调用自身的findClass进行加载              
                 c = findClass(name);  
             }  
         }  
         if (resolve) {  
             resolveClass(c);  
         }  
         return c;  
    }

使用双亲委托模式优点

那么我们使用双亲委托模式有什么好处呢?

  1. 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
  2. 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。

附:Android ClassLoader简介

作者:xiangzhihong8 发表于2017/3/23 17:20:06 原文链接
阅读:144 评论:0 查看评论

linux驱动开发之module导出符号

$
0
0

前言

驱动开发中,module 是基本的组成,在一个模块中定义的函数,如果想在另一个模块中进行调用,这个时候,就需要进行导出,称为导出符号。

正文

我们所要导出的符号,是在一个模块中,也需要使用 modlue_init 和 module_exit 进行修饰这模块的入口函数。在需要导出符号的地方,使用 EXPORT_SYMBOL_GPL() 或EXPORT_SYMBOL() 将函数导出。在需要调用的地方,使用 extern 进行声明需要的函数。

下边通过一个示例进行分析,这个实例中有三个文件,分别是 export.c export.h used.c ,
在export.c中进行导出符号,在used.c中进行使用导出的符号。

示例

//export.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>

#include "export.h"

 int demo_printf(void)
{
    printk("%s\n",__func__);
    printk("this is demo_printf");
}
//此处将demo_printf 使用EXPORT_SYMBOL_GPL进行导出
EXPORT_SYMBOL_GPL(demo_printf);
//入口函数
static int __init export_init(void)
{
    printk("%s\n",__func__);
    return 0;
}
//出口函数
static void __exit export_exit(void)
{
    printk("%s\n",__func__);
}

module_init(export_init);
module_exit(export_exit);
MODULE_LICENSE("GPL");
//export.h
#ifndef _EXPORT_H
#define _EXPORT_H
//进行声明
extern int demo_printf(void);
struct export_content{
    int val;
    int (*ex_printf)(void);
};
#endif
//used.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>

#include "export.h"

static int __init used_init(void)
{
    printk("%s\n",__func__);
//此处另外一个模块,在这个中,调用另外一个模块中的导出 demo_printf
    demo_printf();

    return 0;
}

static void __exit used_exit(void)
{
    printk("%s\n",__func__);
}

module_init(used_init);
module_exit(used_exit);
MODULE_LICENSE("GPL");

在编译时,导出模块先编译,使用者后编译;
在加载时,导出模块先加载,使用者后加载;
在卸载时,使用者先卸载,导出模块后卸载;

以上是我们自己实现的一个导出模块的demo示例。那么现在让我们在内核中,找到相关的代码,进一步加深我们的认识,下边我们通过一个内核中的实例,来看看别人是怎么使用的;我们先找仅关注框架,不关注具体的内容。

实例

以下实例是用内核中三星提供的一个adc的实例,来研究

//Adc.h (linux-3.0.86\arch\arm\plat-samsung\include\plat)   
//这个文件,是adc通用模块的一个头文件
//使用extern进行声明了导出的s3c_adc_star函数
extern int s3c_adc_start(struct s3c_adc_client *client,
             unsigned int channel, unsigned int nr_samples,
             wait_queue_head_t *pwake);
//Adc.c (linux-3.0.86\arch\arm\plat-samsung)    
//导出模块所在的源文件adc.c
int s3c_adc_start(struct s3c_adc_client *client,
          unsigned int channel, unsigned int nr_samples,
          wait_queue_head_t *pwake)
{
    struct adc_device *adc = adc_dev;
    unsigned long flags;

    BUG_ON(!adc);

    if (client->is_ts && adc->ts_pend)
        return -EAGAIN;

    if (atomic_xchg(&client->running, 1)) {
        WARN(1, "%s: %p is already running\n", __func__, client);
        return -EAGAIN;
    }

    spin_lock_irqsave(&adc->lock, flags);

    client->convert_cb = s3c_convert_done;
    client->wait = pwake;
    client->result = -1;

    client->channel = channel;
    client->nr_samples = nr_samples;

    if (client->is_ts)
        adc->ts_pend = client;
    else
        list_add_tail(&client->pend, &adc_pending);

    if (!adc->cur)
        s3c_adc_try(adc);

    spin_unlock_irqrestore(&adc->lock, flags);

    return 0;
}
//使用EXPORT_SYMBOL_GPL将s3c_adc_start进行导出
EXPORT_SYMBOL_GPL(s3c_adc_start);
//使用者 s3c2410_ts.c
S3c2410_ts.c (linux-3.0.86\drivers\input\touchscreen)   14389   2015/10/29
static void touch_timer_fire(unsigned long data)
{
    unsigned long data0;
    unsigned long data1;
    bool down;

    data0 = readl(ts.io + S3C2410_ADCDAT0);
    data1 = readl(ts.io + S3C2410_ADCDAT1);

    down = get_down(data0, data1);

    if (down) {
        if (ts.count == (1 << ts.shift)) {
            ts.xp >>= ts.shift;
            ts.yp >>= ts.shift;

            if (ts.cal_enable)
                ts_calibrate();

            dev_dbg(ts.dev, "%s: X=%lu, Y=%lu, count=%d\n",
                __func__, ts.xp, ts.yp, ts.count);

            input_report_abs(ts.input, ABS_X, ts.xp);
            input_report_abs(ts.input, ABS_Y, ts.yp);

            input_report_key(ts.input, BTN_TOUCH, 1);
            input_sync(ts.input);

            ts.xp_pre = ts.xp;
            ts.yp_pre = ts.yp;

            ts.xp = 0;
            ts.yp = 0;
            ts.count = 0;
        }
        //此处使用adc.c中导出的s3c_adc_start函数
        s3c_adc_start(ts.client, 0, 1 << ts.shift);
    } else {
        ts.xp = 0;
        ts.yp = 0;
        ts.count = 0;

        input_report_abs(ts.input, ABS_X, ts.xp_pre);
        input_report_abs(ts.input, ABS_Y, ts.yp_pre);

        input_report_key(ts.input, BTN_TOUCH, 0);
        input_sync(ts.input);

        writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);

        ts.request_done = true;
    }
}

以上的代码中,可以看到三星的平台通用的文件中将s3c_adc_start进行了导出,在一个具体的s3c2410_ts.c中进行了使用。利用导出符号,实现了一种分层的效果。

总结

  • 导出模块先编译,使用者后编译
  • 先加载导出者,才能加载使用者
  • 先卸载使用者,才能卸载导出者

  • 合理的利用导出模块,可以实现公用的代码和平台相关的代码的一种分离,代码层次更加清晰。

作者:u013377887 发表于2017/3/23 19:39:52 原文链接
阅读:14 评论:0 查看评论

Jekyll搭建个人博客 韩俊强的博客

$
0
0

 之前写了一篇HEXO搭建个人博客的教程获得了很好评,有很多读者主动给我打赏,在此感谢。

 如果你看过我的文章会发现我现在的博客样式跟之前是有很大的区别的,之前我也是使用 HEXO 搭建的博客,后来发现使用 HEXO 在多台电脑上发布博客,操作起来并不是那么方便,果断就转到了 Jekyll 上,接下来我会讲如何使用 Jekyll 搭建博客,博客模板效果

介绍

 Jekyll 是一个简单的博客形态的静态站点生产机器。它有一个模版目录,其中包含原始文本格式的文档,通过 Markdown (或者 Textile) 以及 Liquid 转化成一个完整的可发布的静态网站,你可以发布在任何你喜爱的服务器上。Jekyll 也可以运行在 GitHub Page 上,也就是说,你可以使用 GitHub 的服务来搭建你的项目页面、博客或者网站,而且是完全免费的

 使用 Jekyll 搭建博客之前要确认下本机环境,Git 环境(用于部署到远端)、Ruby 环境(Jekyll 是基于 Ruby 开发的)、包管理器 RubyGems
  如果你是 Mac 用户,你就需要安装 Xcode 和 Command-Line Tools了。下载方式 Preferences → Downloads → Components。

  Jekyll 是一个免费的简单静态网页生成工具,可以配合第三方服务例如: Disqus(评论)、多说(评论) 以及分享 等等扩展功能,Jekyll 可以直接部署在 Github(国外) 或 Coding(国内) 上,可以绑定自己的域名。Jekyll中文文档Jekyll英文文档Jekyll主题列表

Jekyll 环境配置

安装 jekyll

$ gem install jekyll     

创建博客

$ jekyll new myBlog    

进入博客目录

$ cd myBlog  

启动本地服务

$ jekyll serve

在浏览器里输入: http://localhost:4000,就可以看到你的博客效果了。
这里写图片描述

so easy !

目录结构

 
 Jekyll 的核心其实是一个文本转换引擎。它的概念其实就是: 你用你最喜欢的标记语言来写文章,可以是 Markdown,也可以是 Textile,或者就是简单的 HTML, 然后 Jekyll 就会帮你套入一个或一系列的布局中。在整个过程中你可以设置URL路径, 你的文本在布局中的显示样式等等。这些都可以通过纯文本编辑来实现,最终生成的静态页面就是你的成品了。

一个基本的 Jekyll 网站的目录结构一般是像这样的:

.
├── _config.yml
├── _includes
|   ├── footer.html
|   └── header.html
├── _layouts
|   ├── default.html
|   ├── post.html
|   └── page.html
├── _posts
|   └── 2017-03-23-welcome-to-jekyll.markdown
├── _sass
|   ├── _base.scss
|   ├── _layout.scss
|   └── _syntax-highlighting.scss
├── about.md
├── css
|   └── main.scss
├── feed.xml
└── index.html

这些目录结构以及具体的作用可以参考 官网文档

进入 _config.yml 里面,修改成你想看到的信息,重新 jekyll server ,刷新浏览器就可以看到你刚刚修改的信息了。

到此,博客初步搭建算是完成了,

博客部署到远端

 我这里讲的是部署到 Github Page 创建一个 github 账号,然后创建一个跟你账户名一样的仓库,如我的 github 账户名叫 xiaohange,我的 github 仓库名就叫 xiaohange.github.io,创建好了之后,把刚才建立的 myBlog 项目 push 到 username.github.io仓库里去(username指的是你的github用户名),检查你远端仓库已经跟你本地 myBlog 同步了,然后你在浏览器里输入 username.github.io ,就可以访问你的博客了。

编写文章

  所有的文章都是 _posts 目录下面,文章格式为 mardown 格式,文章文件名可以是 .mardown 或者 .md。

  编写一篇新文章很简单,你可以直接从 _posts/ 目录下复制一份出来 2017-03-23-welcome-to-jekyll副本.markdown ,修改名字为 2017-03-23-article1.markdown ,注意:文章名的格式前面必须为 2017-03-23- ,日期可以修改,但必须为 年-月-日- 格式,后面的 article1 是整个文章的连接 URL,如果文章名为中文,那么文章的连接URL就会变成这样的:http://xiaohange.io/2017/03/%E6%90%AD%E5/ , 所以建议文章名最好是英文的或者阿拉伯数字。 双击 2017-03-23-article1.markdown 打开


---
layout: post
title:  "Welcome to Jekyll!"
date:   2017-03-23 09:29:08 +0800
categories: jekyll update
---

正文...

title: 显示的文章名, 如:title: 我的第一篇文章
date: 显示的文章发布日期,如:date: 2017-03-23
categories: tag标签的分类,如:categories: 随笔

注意:文章头部格式必须为上面的,…. 就是文章的正文内容。

我写文章使用的是 Sublime Text2 编辑器,如果你对 markdown 语法不熟悉的话,可以看看作业部落的教程

使用我的博客模板

虽然博客部署完成了,你会发现博客太简单不是你想要的,如果你喜欢我的模板的话,可以使用我的模板。

首先你要获取的我博客,Github项目地址,进去xiaohange.github.io/ 目录下, 使用命令部署本地服务

$ jekyll server   

如果你本机没配置过任何jekyll的环境,可能会报错

/Users/xxxxxxxx/.rvm/rubies/ruby-2.2.2/lib/ruby/site_ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require': cannot load such file -- bundler (LoadError)
    from /Users/xxxxxxxx/.rvm/rubies/ruby-2.2.2/lib/ruby/site_ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
    from /Users/xxxxxxxx/.rvm/gems/ruby-2.2.2/gems/jekyll-3.3.0/lib/jekyll/plugin_manager.rb:34:in `require_from_bundler'
    from /Users/xxxxxxxx/.rvm/gems/ruby-2.2.2/gems/jekyll-3.3.0/exe/jekyll:9:in `<top (required)>'
    from /Users/xxxxxxxx/.rvm/gems/ruby-2.2.2/bin/jekyll:23:in `load'
    from /Users/xxxxxxxx/.rvm/gems/ruby-2.2.2/bin/jekyll:23:in `<main>'
    from /Users/xxxxxxxx/.rvm/gems/ruby-2.2.2/bin/ruby_executable_hooks:15:in `eval'
    from /Users/xxxxxxxx/.rvm/gems/ruby-2.2.2/bin/ruby_executable_hooks:15:in `<main>'

原因: 没有安装 bundler ,执行安装 bundler 命令


$ gem install bundler

提示:

Fetching: bundler-1.13.5.gem (100%)
Successfully installed bundler-1.13.5
Parsing documentation for bundler-1.13.5
Installing ri documentation for bundler-1.13.5
Done installing documentation for bundler after 5 seconds
1 gem installed

再次执行 $ jekyll server ,提示


Could not find proper version of jekyll (3.1.1) in any of the sources
Run `bundle install` to install missing gems.

跟着提示运行命令

$ bundle install

这个时候你可能会发现 bundle install 运行卡主不动了。

如果很长时间都没任何提示的话,你可以尝试修改 gem 的 source

$ gem sources --remove https://rubygems.org/
$ gem sources -a http://ruby.taobao.org/
$ gem sources -l
*** CURRENT SOURCES ***

http://ruby.taobao.org

再次执行命令 $ bundle install,发现开始有动静了

Fetching gem metadata from https://rubygems.org/...........
Fetching version metadata from https://rubygems.org/..
Fetching dependency metadata from https://rubygems.org/.
。。。
Installing jekyll-watch 1.3.1
Installing jekyll 3.1.1
Bundle complete! 3 Gemfile dependencies, 17 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

bundler安装完成,后再次启动本地服务

$ jekyll server

继续报错

Configuration file: /Users/tendcloud-Caroline/Desktop/xiaohange.github.io/_config.yml
  Dependency Error: Yikes! It looks like you don't have jekyll-sitemap or one of its dependencies installed. In order to use Jekyll as currently configured, you'll need to install this gem. The full error message from Ruby is: 'cannot load such file -- jekyll-sitemap' If you run into trouble, you can find helpful resources at http://jekyllrb.com/help/! 
jekyll 3.1.1 | Error:  jekyll-sitemap

表示 当前的 jekyll 版本是 3.1.1 ,无法使用 jekyll-sitemap

解决方法有两个

1、打开当前目录下的 _config.yml 文件,把 gems: [jekyll-paginate,jekyll-sitemap] 换成 gems: [jekyll-paginate] ,也就是去掉jekyll-sitemap。

2、升级 jekyll 版本,我当前的是 jekyll 3.1.2 。

修改完成后保存配置,再次执行

$ jekyll server

提示

Configuration file: /Users/xiaohange/Desktop/OpenSource/Mine/Page-Blog/xiaohange.github.io-github/_config.yml
            Source: /Users/xiaohange/Desktop/OpenSource/Mine/Page-Blog/xiaohange.github.io-github
       Destination: /Users/xiaohange/Desktop/OpenSource/Mine/Page-Blog/xiaohange.github.io-github/_site
 Incremental build: disabled. Enable with --incremental
      Generating... 
                    done in 0.901 seconds.
 Auto-regeneration: enabled for '/Users/xiaohange/Desktop/OpenSource/Mine/Page-Blog/xioahange.github.io-github'
Configuration file: /Users/xiaohange/Desktop/OpenSource/Mine/Page-Blog/xiaohange.github.io-github/_config.yml
    Server address: http://127.0.0.1:4000/
  Server running... press ctrl-c to stop.

表示本地服务部署成功。

在浏览器输入 127.0.0.1:4000 , 就可以看到blog26.com博客效果了。

修改成你自己的博客

  • 如果你想使用我的模板请把 _posts/ 目录下的文章都去掉。
  • 修改 _config.yml 文件里面的内容为你自己的。

然后使用 git push 到你自己的仓库里面去,检查你远端仓库,在浏览器输入 username.github.io 就会发现,你有一个漂亮的主题模板了。

【 如果想修改博客样式却不知道怎么修改,可以直接在评论里给我提问 】

为什么要是用 Jekyll

使用了 Jekyll 你会发现如果你想使用多台电脑发博客都很方便,只要把远端 github 仓库里的博客 clone 下来,写文章后再提交就可以了,Hexo 由于远端提交的是静态网页,所有无法直接写 Markdown 的文章。如果你想看 Hexo 搭建博客,可以看看我的另一篇HEXO搭建个人博客的教程。

如果你在搭建博客遇到问题,可以在原文博客的评论里给我提问。

后面会继续介绍,在我的博客基础上,如何修改成你自己喜欢的 Style,欢迎继续关注我博客的更新。欢迎加入iOS开发者交流群:446310206

Q&A

问题:最近很多朋友使用我的模板报警告:The CNAME blog26.com is already taken
解决:把CNAME里面的blog26.com修改成你自己的域名,如果你暂时没有域名,CNAME里面就什么都不用谢。(之前没人反馈过这个问题,应该是github page最近才最的限制。)

每周更新关注:http://weibo.com/hanjunqiang 新浪微博!手机加iOS开发者交流QQ群: 446310206

作者:qq_31810357 发表于2017/3/23 11:29:32 原文链接
阅读:230 评论:1 查看评论

Retrofit/OkHttp API接口加固技术实践(下)

$
0
0

作者/Tamic
http://blog.csdn.net/sk719887916/article/details/65448628

上节加固介绍了APi单纯Post用对称加密(Base64 为列子)加密方式,这种加密方式还是存在一定的风险,加密效率虽高,但易破解,本节将介绍怎么用非对称加密 来加解密okhttp的数据,本文采用RSA加密算法为栗子。

对称加密

对称加密是最传统的加密方式,比上非对称加密,缺少安全性,但是它依旧是用的比较多的加密方法。
对称加密采用单密钥加密方式,不论是加密还是解密都是用同一个密钥,即“一把钥匙开一把锁”。对称加密的好处在于操作简单、管理方便、速度快。它的缺点在于密钥在
网络传输中容易被窃听,每个密钥只能应用一次,对密钥管理造成了困难。对称加密的实现形式和加密算法的公开性使它依赖于密钥的安全性,而不是算法的安全性。

对称加密原理以及对称加密算法

对称加密的核心——通信双方共享一个密钥 通信过程: A有明文m,使用加密算法E,密钥key,生成密文c=E(key,m); B收到密文c,使用解密算法D,密钥key,得到明文
m=D(key,c); 比喻: 对称加密是最直观,也是历史最久远的加密手段,类似于加锁和解锁,只不过钥匙的个数非常多(~~2^100),一个人穷其一生也试不完所有可能的钥匙
因此加密密钥能够从解密密钥中推算出来,同时解密密钥也可以从加密密钥中推算出来。而在大多数的对称算法中,加密密钥和解密密钥是相同的,所以也称这种加密算法为秘
密密钥算法或单密钥算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解
密,所以密钥的保密性对通信性至关重要。

主要有DES算法,3DES算法,TDEA算法,Blowfish算法,RC5算法,IDEA算法。

非对称加密

非对称密算法是一种密钥的加密方法。

非对称加密算法需要两个密钥:公钥(publickey)和私钥(privatekey)。公钥与私钥是一对存在,如果用公钥对数据进行加密,只有用对应的私钥才能解密;如果用密钥对数据进行加密,那么只有用对应的公钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。 非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将其中的一把作为公用密钥向其它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另一把专用密钥对加密后的信息进行解密。

另一方面,甲方可以使用乙方的公钥对机密信息进行签名后再发送给乙方;乙方再用自己的私匙对数据进行验签。
甲方只能用其专用密钥解密由其公用密钥加密后的任何信息。 非对称加密算法的保密性比较好,它消除了最终用户交换密钥的需要。
非对称密码体制的特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。对称密码体制中只有一种密钥,并且是非公开的,如果要解密就得让对方知道密钥。所以保证其安全性就是保证密钥的安全,而非对称密钥体制有两种密钥,其中一个是公开的,这样就可以不需要像对称密码那样传输对方的密钥了。这样安全性就大了很多。

列如 :支付宝的加密方式就采用非对称加密方式,支付宝会给客户提供支付宝证书,作为用户验证是否是来自支付宝的数据,防止第三方假冒支付宝,而客户手中持有私钥,用户支付宝发送的数据经过支付宝的公钥进项加密,则支付宝可以采用自己的的私钥进行解密。

工作过程

  • 1.A要向B发送信息,A和B都要产生一对用于加密
  • 2.A的私钥保密,A的公钥告诉B;B的私钥保密,B的公钥告诉A。
  • 3.A要给B发送信息时,A用B的公钥加密信息,因为A知道B的公钥。
  • 4.A将这个数据发给B(已经用B的公钥加密消息)。
  • 5.B收到这个数据后后,B用自己的私钥解密A的消息。其他所有收到这个报文的人都无法解密,因为只有B才有B的私钥。

通俗点可以这么理解:

  • 浏览器向服务器发出请求,询问对方支持的对称加密算法和非对称加密算法;服务器回应自己支持的算法。
  • 浏览器选择双方都支持的加密算法,并请求服务器出示自己的证书;服务器回应自己的证书。
  • 浏览器随机产生一个用于本次会话的对称加密的钥匙,并使用服务器证书中附带的公钥对该钥匙进行加密后传递给服务器;服务器为本次会话保持
  • 该对称加密的钥匙。第三方不知道服务器的私钥,即使截获了数据也无法解密。非对称加密让任何浏览器都可以与服务器进行加密会话。

浏览器使用对称加密的钥匙对请求消息加密后传送给服务器,服务器使用该对称加密的钥匙进行解密;服务器使用对称加密的钥匙对响应消息加密后传送给浏览器,浏览器使用该对称加密的钥匙进行解密。第三方不知道对称加密的钥匙,即使截获了数据也无法解密。对称加密提高了加密速度

数字证书

数字证书就是互联网通讯中标志通讯各方身份信息的一串数字,提供了一种在Internet上验证通信实体身份的方式,数字证书不是数字身份证,而是身份认证机构盖在数字身份证上的一个章或印(或者说加在数字身份证上的一个签名)。它是由权威机构——CA机构,又称为证书授权(Certificate Authority)中心发行的,人们可以在网上用它来识别对方的身份。
数字证书绑定了公钥及其持有者的真实身份,它类似于现实生活中的居民身份证,所不同的是数字证书不再是纸质的证照,而是一段含有证书持有者身份信息并经过认证中心审核签发的电子数据,广泛用在电子商务和移动互联网中。。
通俗讲就是车管所会给每个车辆进行认证颁发车牌,通过车牌我们可以查到所有车辆和驾驶员的信,二数字证书就辨别唯一身份,支付宝等的数字证书就是公开的,这不是支付宝自己决定,而是由国际组织认证,这样不管是哪个用户首先就可以根据浏览器返回的证书辨别支付宝的真伪。

数字签名

数字签名用来,保证信息传输的完整性、发送者的身份认证、防止交易中的抵赖发生。
数字签名是将摘要信息用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息,然后用HASH函数对收到的原文产生一个摘要信息,与解密的摘要信息对比。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改,否则说明信息被修改过,因此数字签名能够验证信息的完整性。如果中途数据被纂改或者丢失。那么对方就可以根据数字签名来辨别是否是来自对方的第一手信息数据。
数字签名是个加密的过程,数字签名验证是个解密的过程。

参数加解密

首先,Android中生成了对称密钥:

public static  SecretKeySpec generateSymmetric() {

    // Set up secret key spec for 128-bit AES encryption and decryption
    SecretKeySpec sks = null;
    try {
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed("any data used as random seed".getBytes());
        KeyGenerator kg = KeyGenerator.getInstance("AES");
        kg.init(128, sr);
        sks = new SecretKeySpec((kg.generateKey()).getEncoded(), "AES");

        System.out.println("AES KEY: " + sks);
    } catch (Exception e) {
        Log.e(TAG, "AES secret key spec error");
    }
    return sks;
}

然后将SecretKeySpec转换为Base64字符串格式:


public static String ConvertSymmetricKeyToString(SecretKeySpec key) {

    String symmetric_key = null;

    symmetric_key = Base64.encodeToString(key.getEncoded(), Base64.DEFAULT);
    return symmetric_key;
}

调用函数:


    SecretKeySpec symmKey = generateSymmetric();


    newSymmetricKeyString = CreateEncryptedXml.ConvertSymmetricKeyToString(symmKey);

用SecretKeySpec加密数据:


private static String encryptDataWithSymmetricKey (SecretKeySpec symmKey, String data) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {


    // encryption
    byte[] toBeCiphred = data.getBytes("UTF-8");
    String encryptedData = null;

    try {
        Cipher c = Cipher.getInstance("AES");
        c.init(Cipher.ENCRYPT_MODE, symmKey);
        byte[] encodedBytes = c.doFinal(toBeCiphred);
        System.out.println("BYTE STRING (ASYMM): " + encodedBytes);
        encryptedData = Base64.encodeToString(encodedBytes, Base64.DEFAULT);

    } catch (Exception e) {
        Log.e(TAG, "AES encryption error");
        throw new RuntimeException(e);
    }


    return encryptedData;
}


 encryptedData = encryptDataWithSymmetricKey(symmKey, text);

使用拦截器:

然后将字符串秘密AES密钥和加密的数据(用这个AES密钥加密)使用okhttp使POST请求。字符串密钥使用的是RSA加密。


public class EncryptionInterceptor implements Interceptor {

       private static final String TAG =   EncryptionInterceptor.class.getSimpleName();

       private static final boolean DEBUG = true;

       @Override
       public Response intercept(Chain chain) throws IOException {


       Request request = chain.request();
       RequestBody oldBody = request.body();
       Buffer buffer = new Buffer();
       oldBody.writeTo(buffer);
       String strOldBody = buffer.readUtf8();
       MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
       String strNewBody = encryptDataWithSymmetricKey(symmKey, text);

       RequestBody body = RequestBody.create(mediaType, strNewBody);
       request = request.newBuilder().header("Content-Type", body.contentType().toString()).header("Content-Length", String.valueOf(body.contentLength())).method(request.method(), body).build();
       return chain.proceed(request);
  }
}

在服务器端可以解密(从RSA)秘密AES密钥,并得到它的字符串表示。在客户端(Android)和服务器端(server)上是一样的。使用这个字符串AES密钥解密数据


  String encryptionMethod = "AES-128-CBC";  

  //decryptedAESKey is an Android AES SecretKeySpec converted  to string
  //in Android the string key was base64_encoded, 服务端解密 decode

  String secretHash = base64Decode(decryptedAESKey);


 // Decrypt
  String decryptedMessage = opensslDecrypt(base64Decode(encryptedData),  encryptionMethod, secretHash);

总结

完整的非对称加密过程

假如现在 你向支付宝 转账(术语数据信息),为了保证信息传送的保密性、真实性、完整性和不可否认性,需要对传送的信息进行数字加密和签名,其传送过程为:

  • 1.首先你要确认是否是支付宝的数字证书,如果确认为支付宝身份后,则对方真实可信。可以向对方传送信息,
  • 2.你准备好要传送的数字信息(明文)计算要转的多少钱,对方支付宝账号等;
  • 3.你 对数字信息进行哈希运算,得到一个信息摘要(客户端主要职责);
  • 4.你 用自己的私钥对信息摘要进行加密得到 你 的数字签名,并将其附在数字信息上;
  • 5.你 随机产生一个加密密钥,并用此密码对要发送的信息进行加密(密文);
  • 6.你用 支付宝的公钥对刚才随机产生的加密密钥进行加密,将加密后的 DES 密钥连同密文一起传送给支付宝;
  • 7.支付宝收到 你 传送来的密文和加密过的 DES 密钥,先用自己的私钥对加密的 DES 密钥进行解密,得到 你随机产生的加密密钥;
  • 8.支付宝 然后用随机密钥对收到的密文进行解密,得到明文的数字信息,然后将随机密钥抛弃;
  • 9.支付宝 用你 的公钥对 你的的数字签名进行解密,得到信息摘要;
  • 10.支付宝用相同的哈希算法对收到的明文再进行一次哈希运算,得到一个新的信息摘要;
  • 11.支付宝将收到的信息摘要和新产生的信息摘要进行比较,如果一致,说明收到的信息没有被修改过。
  • 12 确定收到信息,然后进行向对方进行付款交易,一次非对称密过程结束。在这后面的流程就不属于本次非对称加密的范畴,算支付宝个人的自我流程,也就是循环以上过程。

作者/Tamic
http://blog.csdn.net/sk719887916/article/details/65448628

阅读推荐

Retrofit/OkHttp API接口加固技术实践(上)

作者:sk719887916 发表于2017/3/23 20:24:33 原文链接
阅读:619 评论:0 查看评论

iOS菜鸟笔记3:Hello,iPhone!

$
0
0

Hello,iPhone

先从一个图形界面的Demo开始,记录下一个iOS项目的创建和最简单功能的实现。

新建一个项目

当前最新Xcode版本为8.2.1,包含了Swift3以及iOS 10.2、watchOS 3.1、tvOS 10.1的SDK。

创建一个新的Single View的工程,开发语言选择Objective-C。
Xcode会为你生成下图中的文件。
这里写图片描述

应用的主入口

Supporting Files下面有个main.m文件,这就是主入口了。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

它的主要功能就是调用AppDelegate类。

应用委托类以及app的生命周期

AppDelegate类是应用程序委托对象(要注意委托与代理是不同的),其继承的一系列方法在应用程序的不同阶段会被调用。所以,我们先来了解一下iOS应用的生命周期。

1、Not Running:应用没有运行或被系统终止。
2、Inactive:正在进入前台,但还不能接受事件处理的状态。
3、Active:通常意义的活动状态。
4、Background:后台状态(可以后台运行)。
5、Suspend:挂起,冻结状态,当系统内存不够时将其回收。
这里写图片描述

再来说说回调函数:
willFinishLaunchingWithOptions:—程序加载时首先要执行的函数

didFinishLaunchingWithOptions:—程序显示前的初始化工作

applicationDidBecomeActive:—进入前台并处于活动状态,此时可以做恢复UI的工作

applicationWillResignActive:—程序即将进入非活动状态,此时可以做保存UI的工作

applicationDidEnterBackground:—程序进入后台状态,可以保存数据和释放资源等工作。

applicationWillEnterForeground:—程序进入前台但非活动状态,此时可以恢复数据。

applicationWillTerminate:—程序被终止时调用,可以做释放资源等动作。但程序挂起后不会调用(This method is not called if your app is suspended)。

ViewController类、Main.storyboard与MVC模式

模型、视图、控制器模式:简单的说,就是控制器作为连接视图和模型的中介,视图上显示的内容取决于模型,模型的数据更改了,视图也会随之改变;视图上有用户的输入,模型也会相应改变数据。

ViewController继承自UIViewController,其是所有控制器的根类。
UIView是视图和控件的根类,storyboard是承载视图的地方。
模型可以用一个模型对象去封装,也可以直接做ViewController中处理。

Hello,iPhone!

做一个小demo:点击界面上一个按钮,界面上显示Hello,iPhone!
1、单击storyboard,在View设计界面上添加一个label和一个button(右下角对象库object library)。
2、通过辅助编辑器(Assistant editor),按住ctrl拖拽,将上述两个控件连接到ViewController中。
Label创建一个outlet

@property (weak, nonatomic) IBOutlet UILabel *HelloLabel;

Button创建一个action,直接在点击事件中改变Label的值

- (IBAction)onClick:(id)sender {
    self.HelloLabel.text = @"Hello, iPhone!";
}

用源码方式打开Main.storyboard,可以找到我们拖拽操作的痕迹。
先来看看Label的连接这样的

<label fixedFrame="YES" text="菜鸟笔记" id="Fhn-S6-EU5">

<connections>
     <outlet property="HelloLabel" destination="Fhn-S6-EU5" id="oP3-Bb-3tY"/>
</connections>

再来看看Button,connections的destination恰恰是ViewController的id

<button lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VKk-kX-z7o">
    <rect key="frame" x="114" y="329" width="129" height="77"/>
    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
    <state key="normal" title="Hello">
        <color key="titleShadowColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
    </state>
    <connections>
        <action selector="onClick:" destination="BYZ-38-t0r" eventType="touchUpInside" id="zNx-9T-eAY"/>
    </connections>
</button>

<viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController">    

运行起来吧,效果如下。
这里写图片描述

参考:
1、生命周期:https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html#//apple_ref/doc/uid/TP40007072-CH2-SW1
2、http://www.cnblogs.com/QianChia/p/5771082.html

作者:lincyang 发表于2017/3/23 21:45:37 原文链接
阅读:185 评论:0 查看评论

RecyclerView局部刷新爬坑之路

$
0
0

前几天看到的一篇文章,感觉和我的爬坑经历很像,感觉相见恨晚,所以转载一下。原文地址:安卓易学,爬坑不易—腾讯老司机的RecyclerView局部刷新爬坑之路

有图有真相,首先来对比一下局部刷新前后的效果:

优化之前的效果:

这里写图片描述

优化之后的效果:

这里写图片描述

可以看到,优化之后,列表中的这张大图不在有一闪一闪亮晶晶的效果了! 那么,这是如何做到的呢?这是本文的重点,本文的大纲主要包括:

  1. 分析为什么会闪一下

  2. 对分析的可能造成闪动的问题进行解决

  3. 验证是否解决

一、为什么会闪一下?

我们的需求是大家已经看到了,点击打分,弹出一个对话框,点击一个分数,这时候,通过一些列复杂的转换(当然不是本文的论述的重点),这时候到了要更新列表项了,如是很自然,我们会这么做:

这里写图片描述

因为,操作的那个列表项你是知道他的position,所以你可以这么做,(当然,我之前是直接notifyDataSetChanged的,这个会照成所以不不要的item也会刷新)然而,闪动还是出现了,那么我开始怀疑: 流传甚为广泛的一种说法。

  1. ImageView的宽高不固定导致的(wrap_content)?

  2. 这个是RecyclerView自带的更新动画效果导致的?

  3. 这个是因为图片加载框架(glide 的 animte)的动画效果导致的?

  4. getView中(RecyclerView中是onBindViewHolder)加载图片的时候,设置一个tag,当发现这个imageView的tag和之前的tag一致时就不加载

二、带着思考,就去尝试吧!

1、对于第一种,我的做法是自己写了一个自定义的imageView,重写omMeasure方法,如下:

这里写图片描述

因为我们的这个列表项中的图片是(高=宽)的,因此,我才这么写,这样写也有一个好处,不用在onBindViewHolder中去动态的计算出高度,然后在已layoutParm的方式设置给imageView,相信不少小伙伴都做过了吧!

然而,遗憾的是,他并没有解决闪一下的问题!此时这个闪动的原因显然不在这里,但是这里做的,可以保留下来。

2、对于第二种说法,我参考了这里 http://stackoverflow.com/questions/29331075/recyclerview-blinking-after-notifydatasetchanged 的做法:

这里写图片描述

以及也尝试了这种

这里写图片描述

然而,那种渐变的闪动消失了,但是,取而代之的是一种更加不可接受的闪动,这里就不用gif展示了,因此原因也并不在此处。

3、对于对三种说法,我也去尝试了一下将glide加载改为:

这里写图片描述

然而得到的依然是一个失望的结果,依然没有解决闪动的问题,原因也不在此处。

4、那么,就剩下最后一个猜测了,那么会不会是它呢?那就试试吧,于是代码改为:

这里写图片描述

这里的做法其实就是设置Tag,那么是骡子是马,拉出来溜溜吧,结果更加令人发指,如图:

这里写图片描述

好吧,此时已经有点崩溃了,显然这个也不是我要的结果,那么此时是否应该在静下来想一想,自己对于可能的几种原因做过的一些对策,是否有哪里遗漏了。经过思考,发现并没有!!那么一定是还有其他的原因,没有考虑到!

还是去翻一翻RecyclerView的api吧,我注意到了这个api:

这里写图片描述
这里写图片描述

可以看到这里有一个payload的参数,use null to identify a “full” update这是说如果传null就是全部更新,回过头去看一看我们之前的调用方式:

这里写图片描述

看一下源码,发现

这里写图片描述

实际上,payload这个参数就是传的null,那也就是说如果传一个不为null的参数,就可以对列表项中的具体控件更新了? http://stackoverflow.com/questions/33176336/need-an-example-about-recyclerview-adapter-notifyitemchangedint-position-objec 我了解到这个方法的使用方式是这样的:

这里写图片描述

然来,onBindViewHolder有这么一个重载方式,如是我也这么做了,在下面这个重载中,去更新我想更新的控件:

这里写图片描述

然后,更新的方式变成了这种:

这里写图片描述

是骡子是马,那就在遛一遛吧!

然而,依然是会闪一下!!!这这么会!!!还是调试一下吧,新重载onBindViewHolder方法有没有被执行,一更代码,发现果然没有被执行!

那么,究竟是什么鬼?去网上查了一下,有人给出了一个解决办法: http://stackoverflow.com/questions/32463136/recyclerview-adapter-notifyitemchanged-never-passes-payload-to-onbindviewholde

这里写图片描述

需要重写这个动画,让永远返回true,已达到newHolder和olderHolder是同一个,然而,这真的就是我的救命稻草吗?

那么,是骡子是马,拉出来溜溜吧,然而,并不是马!!进源码看一看

这里写图片描述

发现其实只要我们传入的payload不为空,那么返回的就是true?重写有意义吗?显然,我重载的onBindViewHolder方法并没有执行的原因显然不是这个。

那么,到底,到底问题出在何处?会不会是XrecyclerView的问题?根据调用栈,我看到第一个onBindViewHolder被执行了,往上面跟,发现XrecyclerView的实现果然存在问题!

这里写图片描述

如图,作者仅仅只实现了,不带payload的方法,最后adapter调用的只有不带paylaod的方法!所以,重写一个吧!

这里写图片描述

最后!终于达到了想要的效果了,经过这次爬坑,选择一个开源的框架真滴是需要慎重再慎重。

总结

实际上RecyclerView做局部刷新是非常容易的,其实就是使用好带payload参数的这个notifyItemRangeChanged方法,以及override带payload的这个onBindViewHolder方法,在onBindViewHolder中去刷新你想更新的控件即可,并非是网上传闻的那些原因,当然此处爬坑时间之长,也可能更选用开源控件不当有关,所以,选择开源控件,要谨慎再谨慎!

作者:qq_17766199 发表于2017/3/23 22:44:30 原文链接
阅读:933 评论:5 查看评论

快速使用HEXO搭建个人博客 韩俊强的博客

$
0
0

  经过各种找资料,踩过各种坑,终于使用 hexo 搭建个人博客初步完成了,域名目前用得时 github 的,我的 hexo 是 3.1.1 版本,hexo 不同的版本,很多配置都不一样。好吧,废话不多说了,开始吧。

正文:

 这边教程是针对与Mac的,参考链接,由于原文讲到的hexo是以前的老版本,所以现在的版本配置的时候会有些改动。

 之前是想着写博客,一方面是给自己做笔记,可以提升自己的写作、总结能力,一个技术点我们会使用,并不难,但是要做到让让别人也能听懂我们讲得,还是需要一定的技巧和经验的。很多类似于CSDN、博客园也都可以写文章,但是页面的样式我,不是太喜欢,简书还算好点得。最近看到一些大神们的博客(在我的友情链接里有),貌似都是用hexo写得,我也依葫芦画瓢的搭建了一个。不罗嗦了,直接上搭建步骤。

配置环境

安装Node(必须)

作用:用来生成静态页面的
到Node.js官网下载相应平台的最新版本,一路安装即可。

安装Git(必须)

作用:把本地的hexo内容提交到github上去.
安装Xcode就自带有Git,我就不多说了。

申请GitHub(必须)

作用:是用来做博客的远程创库、域名、服务器之类的,怎么与本地hexo建立连接等下讲。
github账号我也不再啰嗦了,没有的话直接申请就行了,跟一般的注册账号差不多,SSH Keys,看你自己了,可以不配制,不配置的话以后每次对自己的博客有改动提交的时候就要手动输入账号密码,配置了就不需要了,怎么配置我就不多说了,网上有很多教程。

正式安装HEXO 

Node和Git都安装好后,可执行如下命令安装hexo:

$ sudo npm install -g hexo

初始化

创建一个文件夹,如:Blog,cd到Blog里执行hexo init的。命令:

hexo init

好啦,至此,全部安装工作已经完成!

生成静态页面

继续再Blog目录下执行如下命令,生成静态页面

hexo generate (hexo g  也可以)   

本地启动

启动本地服务,进行文章预览调试,命令:

hexo server   

浏览器输入http://localhost:4000
我不知道你们能不能,反正我不能,因为我还有环境没配置好

常见的HEXO配置错误:

ERROR Plugin load failed: hexo-server

原因: Besides, utilities are separated into a standalone module. hexo.util is not reachable anymore.

解决方法,执行命令:$ sudo npm install hexo-server
执行命令hexo server,提示:Usage: hexo<Command> ....

原因:我认为是没有生成本地服务

解决方法,执行命令:$ npm install hexo-server --save

提示:hexo-server@0.1.2 node_modules/hexo-server
.... 

表示成功了[参考](https://hexo.io/zh-cn/docs/server.html)

这个时候再执行:$ hexo-server

得到: INFO Hexo is running at http://0.0.0.0:4000/. Press Ctrl+C to stop.

这个时候再点击http://0.0.0.0:4000,正常情况下应该是最原始的画面,但是我看到的是:
白板和Cannot GET / 几个字
原因: 由于2.6以后就更新了,我们需要手动配置些东西,我们需要输入下面三行命令:

npm install hexo-renderer-ejs --save
npm install hexo-renderer-stylus --save
npm install hexo-renderer-marked --save

这个时候再重新生成静态文件,命令:

hexo generate (或hexo g)

启动本地服务器:

hexo server (或hexo s)

再点击网址http://0.0.0.0:4000 OK终于可以看到属于你自己的blog啦,?,虽然很简陋,但好歹有了一个属于自己的小窝了。参考链接,本地已经简单的设置好了,但是现在域名和服务器都是基于自己的电脑,接下来需要跟github进行关联。

配置Github

建立Repository

建立与你用户名对应的仓库,仓库名必须为【your_user_name.github.io】,固定写法
然后建立关联,我的Blog在本地/Users/xiaohange/Blog,Blog是我之前建的东西也全在这里面,有:

Blog
 |
 |-- _config.yml
 |-- node_modules
 |-- public
 |-- source
 |-- db.json
 |-- package.json
 |-- scaffolds
 |-- themes      
   

现在我们需要_config.yml文件,来建立关联,命令:

vim _config.yml

翻到最下面,改成我这样子的,注意: : 后面要有空格

deploy:
  type: git
  repository: https://github.com/xiaohange/xiaohange.github.io.git
  branch: master

执行如下命令才能使用git部署

npm install hexo-deployer-git --save

网上会有很多说法,有的type是github, 还有repository 最后面的后缀也不一样,是github.com.git,我也踩了很多坑,我现在的版本是hexo: 3.1.1,执行命令hexo -vsersion就出来了,貌似3.0后全部改成我上面这种格式了。
忘了说了,我没用SSH Keys如果你用了SSH Keys的话直接在github里复制SSH的就行了,总共就两种协议,相信你懂的。
然后,执行配置命令:

hexo deploy

 然后再浏览器中输入http://xiaohange.github.io/就行了,我的 github 的账户叫 xiaohange

,把这个改成你 github 的账户名就行了

部署步骤

每次部署的步骤,可按以下三步来进行。

hexo clean
hexo generate
hexo deploy

一些常用命令:

hexo new "postName" #新建文章
hexo new page "pageName" #新建页面
hexo generate #生成静态页面至public目录
hexo server #开启预览访问端口(默认端口4000,'ctrl + c'关闭server)
hexo deploy #将.deploy目录部署到GitHub
hexo help  #查看帮助
hexo version  #查看Hexo的版本

这里有大量的主题列表使用方法里面
都有详细的介绍,我就不多说了。
我这里有几款个人认为不错的主题,免去你们,一个一个的选了,欢迎吐槽我的审美,?
 Cover - A chic theme with facebook-like cover photo
 Oishi - A white theme based on Landscape plus and Writing.
 Sidebar - Another theme based on Light with a simple sidebar
 TKL - A responsive design theme for Hexo. 一个设计优雅的响应式主题
 Tinnypp - A clean, simple theme based on Tinny
 Writing - A small and simple hexo theme based on Light
 Yilia - Responsive and simple style 优雅简洁响应式主题,我用得就是这个。
 Pacman voidy - A theme with dynamic tagcloud and dynamic snow

一些基本路径

 文章在 source/_posts,编辑器可以用 Sublime,支持 markdown 语法。如果想修改头像可以直接在主题的 _config.yml 文件里面修改,友情链接,之类的都在这里,修改名字在 public/index.html 里修改,开始打理你的博客吧,有什么问题或者建议,都可以提出来,我会继续完善的。

Markdown语法参考链接: 作业部落

Q&A

问:如何让文章想只显示一部分和一个 阅读全文 的按钮?
答:在文章中加一个 <!--more--><!--more--> 后面的内容就不会显示出来了。

问:本地部署成功了,也能预览效果,但使用 username.github.io 访问,出现 404 .
答:首先确认 hexo d 命令执行是否报错,如果没有报错,再查看一下你的 github 的 username.github.io 仓库,你的博客是否已经成功提交了,你的 github 邮箱也要通过验证才行。

每周更新关注:http://weibo.com/hanjunqiang 新浪微博!手机加iOS开发者交流QQ群: 446310206
作者:qq_31810357 发表于2017/3/24 9:26:35 原文链接
阅读:121 评论:0 查看评论

Realm_Android使用详解(一)

$
0
0

Realm数据库听说的时候是在2016年初左右吧,还是听一个网易的哥们说起.才了解到的.Realm是一个MVCC的数据库.底层使用C++写的.MVCC指的是多版本并发控制.

本篇文章先去带大家如何使用Realm,下篇介绍Realm的内部规则.

二叉树上图先,看下效果
这里写图片描述

环境支持

  • Android Studio 版本1.5.1 or 更高
  • JDK 版本 7.0 or 更高
  • Android API 版本 9 or 更高 (Android 2.3 and 之上)

我们不在支持eclipse作为IDE;所以请使用Android Studio;

安装使用

通过Gradle plugin安装使用

提示1:添加文件路径是你的project 的 build.gradle 文件.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "io.realm:realm-gradle-plugin:3.0.0"
    } }

文件位置如图所示:
这里写图片描述

提示2:应用realm-android插件app的build.gradle 文件的顶部.

apply plugin: ‘realm-android’

文件位置如图所示:
这里写图片描述

Realm 浏览器

现在开发出了一个mac版本的Ream数据浏览器,用于查看和编辑,这个应用不支持windows和linux,windows的同学可以使用stetho chrom插件进行查看调试.由facebook产出.至于使用方法可以看我的 如何用googleChrome调试Android程序呢?_Stetho.
macRealm浏览器下载地址.github下载地址

代码实现

第一步:全局初始化Realm

package cn.yky.realm;

import android.app.Application;

import io.realm.Realm;

/**  * Created by yukuoyuan on 2017/3/24.  */

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
    } }

第二步:获取当前Realm对象在Activity或者Fragment中

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        //初始化Realm
        realm = Realm.getDefaultInstance();
    }

第三步:我们的自定义的Models必须继承RealmObject,如示例代码

package cn.yky.realm;

import io.realm.RealmObject;

/**
 * Created by yukuoyuan on 2017/3/24.
 * 这是一个Realm 的Modal
 */

public class User extends RealmObject {
    public String Name;
    public int Age;
    public int sex;

}

第四步:进入我们的逻辑层,增删改查如代码所示

   /**
     * 这是一个添加一条数据的方法
     */
    public void add() {
        i = i + 1;
        realm.beginTransaction();
        User user = realm.createObject(User.class); // Create a new object
        user.Name = "王祖贤";
        user.Age = 23 + i;
        user.sex = 0;
        realm.commitTransaction();
    }
 /**
     * 这是一个删除一条数据的方法
     */
    public void delete() {
        realm.beginTransaction();
        RealmResults<User> guests = realm.where(User.class).equalTo("sex", 0).findAll();
        for (User guest : guests) {
            if (guest.Age > 28) {
                guest.deleteFromRealm();
            }
        }
        realm.commitTransaction();
    }
   /**
     * 这是一条更新的方法
     */
    public void updata() {
        realm.beginTransaction();
        RealmResults<User> guests = realm.where(User.class).equalTo("sex", 0).findAll();
        for (User guest : guests) {
            guest.Age = 48;
        }
        realm.commitTransaction();
    }
 /**
     * 这是一个一个查询的方法
     */
    public void query() {
        realm.beginTransaction();
        RealmResults<User> guests = realm.where(User.class).equalTo("sex", 0).findAll();
        realm.commitTransaction();
        tvShow.setText("");
        String show = "";
        for (User guest : guests) {
            show = show + "/\n" + guest.Name + "**" + guest.Age + "**" + guest.sex;
        }
        tvShow.setText(show);
    }

该示例代码我已经上传到github:github机票
后续我会讲解,Realm的内部调用规则,和更加复杂的使用.欢迎后续关注我.谢谢

作者:EaskShark 发表于2017/3/24 11:02:01 原文链接
阅读:141 评论:0 查看评论

RX系列五 | Schedulers线程控制

$
0
0

RX系列五 | Schedulers线程控制


在我们上一篇文章中的,我们的小例子里有这么一段代码

//网络访问
.observeOn(Schedulers.io())

事实上,我们在使用网络操作的时候,便可以控制其运行在哪个线程中,而Schedulers类,有四个方法,分别是

  • Schedulers.immediate();
  • Schedulers.newthread();
  • Schedulers.io();
  • Schedulers.computation();

以及RxAndroid中的AndroidSchedulers有一个

  • AndroidSchedulers.mainThread();

这些都是操作线程的方法,我们都知道RxJava在异步方面很优秀,那我们应该怎么去体现他尼,先来看下这几个函数的具体含义吧

Schedulers.immediate()

作用于当前线程运行,相当于你在哪个线程执行代码就在哪个线程运行

Schedulers.newthread();

运行在新线程中,相当于new Thread(),每次执行都会在新线程中

Schedulers.io();

一看便知,I/O操作,比如文件操作,网络操作等,他和newThread有点类似,不过I/O里面有一个死循环的线程池来达到最大限度的处理逻辑,虽然效率高,但是如果只是一般的计算操作,不建议放在这里,避免重复创建无用线程

Schedulers.computation()

一些需要CPU计算的操作,比如图形,视频等

AndroidSchedulers.mainThread();

指定运行在Android主线程中

我们现在就来模拟这个操作,比如我们去加载一张图片,那么我们使用okhttp的话,代码应该怎么写

  @Override
    public void onClick(final View v) {
        switch (v.getId()) {
            case R.id.load_img:
                Observable.create(new ObservableOnSubscribe<Bitmap>() {
                    //主线程执行
                    @Override
                    public void subscribe(final ObservableEmitter<Bitmap> e1) throws Exception {
                        //进行网络操作 解析图片
                        OkHttpClient client = new OkHttpClient();
                        Request request = new Request.Builder().url(IMG_URL).build();
                        //异步方法
                        client.newCall(request).enqueue(new Callback() {
                            @Override
                            public void onFailure(Call call, IOException e) {
                                e1.onError(e);
                            }

                            @Override
                            public void onResponse(Call call, Response response) throws IOException {
                                if (response.isSuccessful()) {
                                    byte[] bytes = response.body().bytes();
                                    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                                    e1.onNext(bitmap);
                                    e1.onComplete();
                                }
                            }
                        });
                    }
                }).subscribe(new Observer<Bitmap>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        Log.e("MainActivity", "onSubscribe");
                    }

                    @Override
                    public void onNext(Bitmap bitmap) {
                        Log.e("MainActivity", "onNext");

                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e("MainActivity", "onError" + e.toString());
                    }

                    @Override
                    public void onComplete() {
                        Log.e("MainActivity", "onComplete");
                    }
                });
                break;
        }
    }

嗯,可以的,然后我们运行,会出现什么日志尼

com.liuguilin.schedulerssample E/MainActivity: onSubscribe
com.liuguilin.schedulerssample E/MainActivity: android.os.NetworkOnMainThreadException

他提示我们不能在主线程进行网络操作,那好,可以下的结论是subscribe在主线程,那现在我们的Schedulers就派上用场了,我们只需要

.subscribeOn(Schedulers.io())

让其在I/O操作即可,而这些含义,上面也都有提及,那我们现在运行,可以看到如下的打印

com.liuguilin.schedulerssample E/MainActivity: onSubscribe
com.liuguilin.schedulerssample E/MainActivity: onNext
com.liuguilin.schedulerssample E/MainActivity: onComplete

好的,走了onnext说明我们加载是成功的,因为我是加载图片,所有我现在来加载这张图片,这个很简单,我们只需要设置就好了

@Override
public void onNext(Bitmap bitmap) {
    Log.e("MainActivity", "onNext");
    iv_img.setImageBitmap(bitmap);

}

但是当你运行 你就会发现,又出现错误了

CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

这个错误你一定不陌生,这就是在子线程更新UI的错误,其实这里也很好理解,就是我们在网络操作的时候是在子线程中,是吧,所有我们设置图片才会有这样的错误,其实解决的办法,就是将我们的消费者切换成我们的主UI线程就好了,添加一行

.observeOn(AndroidSchedulers.mainThread())

好的,现在我们运行一下看下效果

这里写图片描述

OK,全部的代码就这么点,就一个小知识点,但是一定要清楚他的工作流程

    @Override
    public void onClick(final View v) {
        switch (v.getId()) {
            case R.id.load_img:
                Observable.create(new ObservableOnSubscribe<Bitmap>() {
                    //主线程执行
                    @Override
                    public void subscribe(final ObservableEmitter<Bitmap> e1) throws Exception {
                        //进行网络操作 解析图片
                        OkHttpClient client = new OkHttpClient();
                        Request request = new Request.Builder().url(IMG_URL).build();
                        //异步方法
                        client.newCall(request).enqueue(new Callback() {
                            @Override
                            public void onFailure(Call call, IOException e) {
                                e1.onError(e);
                            }

                            @Override
                            public void onResponse(Call call, Response response) throws IOException {
                                if (response.isSuccessful()) {
                                    byte[] bytes = response.body().bytes();
                                    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                                    e1.onNext(bitmap);
                                    e1.onComplete();
                                }
                            }
                        });
                    }
                }).subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Observer<Bitmap>() {
                            @Override
                            public void onSubscribe(Disposable d) {
                                Log.e("MainActivity", "onSubscribe");
                            }

                            @Override
                            public void onNext(Bitmap bitmap) {
                                Log.e("MainActivity", "onNext");
                                iv_img.setImageBitmap(bitmap);

                            }

                            @Override
                            public void onError(Throwable e) {
                                Log.e("MainActivity", "onError" + e.toString());
                            }

                            @Override
                            public void onComplete() {
                                Log.e("MainActivity", "onComplete");
                            }
                        });
                break;
        }
    }

在线程操作的时候,自己只要清楚使用哪一个Schedulers就好了,这个例子比较简单,但是如果你的逻辑很复杂的话,而且需要来回的线程切换的时候,这个rxjava的优势就出来了,我们以后慢慢的深入吧!

Sample下载:http://download.csdn.net/detail/qq_26787115/9792294

作者:qq_26787115 发表于2017/3/24 11:44:22 原文链接
阅读:87 评论:2 查看评论

Android Volley源码分析(1)

$
0
0

之前的博客中已经记录过Volley的基本使用方法了,
从本篇博客开始,我会比较仔细地分析整个Volley框架的源码。

对于一个APK开发者而言,细致地了解整个Volley源码可能用处不大,
但对于一个Framework开发者而言,阅读和分析源码的能力还是时不时地锻炼一下为好,
况且如此广泛被使用的通信框架,它的源码应该是营养丰富的,仔细看看一定会有所收获的。


1. Volley.newRequestQueue

我们从Volley中RequestQueue的初始化入手,开始进行分析。

//应用利用Volley.java的静态方法,获取RequestQueue,开启使用Volley框架的大门
public static RequestQueue newRequestQueue(Context context) {
    return newRequestQueue(context, null);
}

//最终调用到该函数
//HttpStack是个接口,定义了performRequest函数
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
    //创建一个File文件,指向应用默认的Cache路径,子路径名称为“volley”
    //例如之前博客的demo,最终会建立一个/data/data/stark.a.is.zhang.volleytest/cache/volley的cache文件
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

    String userAgent = "volley/0";
    try {
        String packageName = context.getPackageName();
        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
        //利用应用包名和版本后构造一个userAgent名称,这个将用在Http通信中
        userAgent = packageName + "/" + info.versionCode;
    } catch (NameNotFoundException e) {
    }

    //创建通信使用的协议栈
    //在Volley框架中,协议栈应该是进行通信的实体对象
    //之后分析Volley处理Request的流程时,应该还会遇到
    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 network = new BasicNetwork(stack);

    //创建DiskBasedCache对象,指向之前的cacheDir,默认缓存最大值为5MB
    //注意到构造RequestQueue时,同时传入了缓存对象和网络对象,因此缓存和下载就是RequestQueue的核心功能
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);

    //创建完RequestQueue后,调用其start方法启动
    queue.start();

    return queue;
}

从上述代码来看,即使我们在同一个应用中多次调用newRequestQueue,
得到的多个RequestQueue也将共用同一个缓存文件和userAgent,
即对于网络服务端,一个应用所有的RequestQueue都只是一个客户。

从这里可以看出,同一个应用使用一个RequestQueue就够了。
后文可以看到,RequestQueue底层已经支持并发特性,
多个RequestQueue进一步加大并发力度,对于移动终端而言,用处不大。

在这一部分的最后,我们看看RequestQueue的构造函数:

public RequestQueue(Cache cache, Network network) {
    //DEFAULT_NETWORK_THREAD_POOL_SIZE的值为4
    this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(cache, network, threadPoolSize,
            //ExecutorDelivery用于向主线程传递下载结果
            new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

public RequestQueue(Cache cache, Network network, int threadPoolSize,
        ResponseDelivery delivery) {
    mCache = cache;
    mNetwork = network;
    //创建NetworkDispatcher对应的数组
    mDispatchers = new NetworkDispatcher[threadPoolSize];
    mDelivery = delivery;
}

接下来,我们看看RequestQueue的start方法。


2. RequestQueue的start方法

    .................
    public void start() {
        //如果已经存在处于运行态的CacheDispatcher和NetworkDispatcher,则进行停止操作
        //相当于先复位现有状态
        stop();  // Make sure any currently running dispatchers are stopped.

        // Create the cache dispatcher and start it.
        // 创建新的CacheDispatcher,并启动
        // CacheDispatcher具有cache和network对应的BlockingQueue
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // Create network dispatchers (and corresponding threads) up to the pool size.
        // 默认创建4个NetworkDispatcher
        //这个四个NetworkDispatcher与上文的CacheDispatcher共用network对应的BlockingQueue
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }
    .................

CacheDispatcher与NetworkDispatcher均继承自Thread,我们看看这些线程启动后的情况。

2.1 CacheDispatcher的run方法

@Override
public void run() {
    ..............
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

    // Make a blocking call to initialize the cache.
    // 初始化缓存区,即调用DiskBasedCache的initialize方法
    mCache.initialize();
    ..............

在分析后续流程前,先看看DiskBasedCache在initialize方法中的工作。

2.1.1 DiskBasedCache的initialize方法

    @Override
    public synchronized void initialize() {
        //如果应用对应的cache/volly不存在,就创建对应的目录
        if (!mRootDirectory.exists()) {
            if (!mRootDirectory.mkdirs()) {
                VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
            }
            return;
        }

        //得到目录下所有的文件
        File[] files = mRootDirectory.listFiles();
        if (files == null) {
            return;
        }

        //如果目录下已经存在文件,就对文件进行解析
        for (File file : files) {
            BufferedInputStream fis = null;
            try {
                fis = new BufferedInputStream(new FileInputStream(file));

                //从所有的文件中解析出对应的CacheHeader对象
                //readHeader方法就是从InputStream中,读取CacheHeader所需的字段
                CacheHeader entry = CacheHeader.readHeader(fis);
                entry.size = file.length();

                //文件被放置到LinkedHaspMap中
                //LinkedHaspMap的初始化形式为:new LinkedHashMap<String, CacheHeader>(16, .75f, true);
                //最后的参数表示accessOrder,当为true时,以LRU算法保存对象
                //初始向LinkedHaspMap加入数据时,按加入顺序依次保存
                //一旦访问LinkedHashMap时,被访问的对象将被移动到尾部
                //当LinkedHaspMap满了,需要释放空间时,将从前面开始移除最老的对象
                putEntry(entry.key, entry);
            } catch (IOException e) {
                if (file != null) {
                   file.delete();
                }
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException ignored) { }
            }
        }
    }

至此,DiskBasedCache的initialize方法分析完毕。
容易看出,在该方法中将创建出物理的文件存储目录;
如果该目录已经存在,则形成并保存当前Cache文件的CacheHeader。
DiskBasedCache将以LinkedHashMap来保存CacheHeader,以呈现出LRU算法的特性。

2.1.2 CacheDispatcher的服务端循环

分析完初始化缓存目录后,我们回到CacheDispatcher的run方法,看看后续的代码:

...................
//无限循环
//CacheDispatcher线程类似于一个处理Request的服务端
while (true) {
    try {
        // Get a request from the cache triage queue, blocking until
        // at least one is available.
        // mCacheQueue是一个BlockingQueue,意味着无数据时,将一直阻塞在这里
        final Request<?> request = mCacheQueue.take();
        request.addMarker("cache-queue-take");

        // If the request has been canceled, don't bother dispatching it.
        // 当取出Request后,首先判断是否已经取消
        if (request.isCanceled()) {

            //若Request已取消,则调用其finish接口
            request.finish("cache-discard-canceled");
            continue;
        }

        // Attempt to retrieve this item from cache.
        //根据Request的CacheKey,判断DiskBaseCache中是否已经进行过存储
        Cache.Entry entry = mCache.get(request.getCacheKey());
        if (entry == null) {
            request.addMarker("cache-miss");
            // Cache miss; send off to the network dispatcher.

            // 没有存储的话,就将该Request加入到NetworkQueue中
            // 注意到NetworkDispatcher保存着NetworkQueue的引用,将负责处理
            mNetworkQueue.put(request);
            continue;
        }

        // If it is completely expired, just send it to the network.
        // 如果Request有对应的Cache,但Cache too old
        // 那么也要将Request重新加入到NetworkQueue中
        if (entry.isExpired()) {
            request.addMarker("cache-hit-expired");
            request.setCacheEntry(entry);
            mNetworkQueue.put(request);
            continue;
        }

        // We have a cache hit; parse its data for delivery back to the request.
        request.addMarker("cache-hit");
        // 如果有可用的Cache,那么从Cache中解析出NetworkResponse
        Response<?> response = request.parseNetworkResponse(
                new NetworkResponse(entry.data, entry.responseHeaders));
        request.addMarker("cache-hit-parsed");

        //如果缓存不需要更新
        if (!entry.refreshNeeded()) {
            // Completely unexpired cache hit. Just deliver the response.
            //利用ExecutorDelivery将结果返回给UI线程
            mDelivery.postResponse(request, response);
        } else {
            // 缓存可用,但需要refresh时
            // Soft-expired cache hit. We can deliver the cached response,
            // but we need to also send the request to the network for
            // refreshing.
            request.addMarker("cache-hit-refresh-needed");
            request.setCacheEntry(entry);

            // Mark the response as intermediate.
            response.intermediate = true;

            // Post the intermediate response back to the user and have
            // the delivery then forward the request along to the network.
            // 将结果递交给界面,同时提交Request给NetworkQueue
            mDelivery.postResponse(request, response, new Runnable() {
                @Override
                public void run() {
                    try {
                        mNetworkQueue.put(request);
                    } catch (InterruptedException e) {
                        // Not much we can do about this.
                    }
                }
            });
        }
    } catch(InterruptedException e) {
        // We may have been interrupted because it was time to quit.
        //异常时,除非调用过quit接口,否则继续循环
        if (mQuit) {
            return;
        }
        continue;
    }
}
.......................

至此,CacheDispatcher的服务端介绍完毕。

上面代码的处理逻辑还是清晰易懂的:
当CacheDispatcher得到一个Request时,试着从Cache中获取对应的Response信息。
如果获取失败或着Cache超时,那么将Request加入到NetworkQueue中,等待下载。
如果成功获取到Cache,那么解析出对应的Response,发送给UI线程;
如果Cache还要求refresh,那么将Response递交给UI线程的同时,仍将Request递交给NetworkQueue处理。

2.2 NetworkDispatcher的run方法

现在我们再来看看NetworkDispatcher的run方法:

@Override
public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

    while (true) {
        //要是我写这个框架,我就将这行代码移到mQueue.take下面
        //感觉这样更准确些吧
        long startTimeMs = SystemClock.elapsedRealtime();

        Request<?> request;

        try {
            // Take a request from the queue.
            // 从NetworkQueue中取出Request
            request = mQueue.take();
        } catch (InterruptedException e) {
            // We may have been interrupted because it was time to quit.
            if (mQuit) {
                return;
            }
            continue;
        }

        try {
            request.addMarker("network-queue-take");

            // If the request was cancelled already, do not perform the
            // network request.
            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                continue;
            }

            .............

            // Perform the network request.
            // 交给BasicNetwork的performRequest处理
            // 将与网络端通信,直到获得返回结果
            // 下一篇博客再分析这些细节
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            request.addMarker("network-http-complete");

            // If the server returned 304 AND we delivered a response already,
            // we're done -- don't deliver a second identical response.
            // 当向服务端发送了同样的NetworkRequest,服务端回应not modified
            // 并且这个NetworkRequest已经向UI线程发送过Response时,不再进行后续处理
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                continue;
            }

            // Parse the response here on the worker thread.
            // 由Request的子类处理,解析返回结果
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");

            //如果需要,则将Response中的信息加入到缓存中
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }

            // Post the response back.
            request.markDelivered();

            //向UI线程返回结果
            mDelivery.postResponse(request, response);
        } catch (VolleyError volleyError) {
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            //如果出现的是VolleyError,则根据Request的类型解析VolleyError,再递交给UI线程
            parseAndDeliverNetworkError(request, volleyError);
        } catch (Exception e) {
            //出异常时,直接封装异常并递交给UI线程
            VolleyLog.e(e, "Unhandled exception %s", e.toString());
            VolleyError volleyError = new VolleyError(e);
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            mDelivery.postError(request, volleyError);
        }
    }
}

至此,NetworkDispatcher的run方法分析完毕。

作为一个服务端,NetworkDispatcher将具体的下载工作递交给BasicNetwork处理;
下载完毕后的结果递交给具体的Request子类分析,并将处理结果通过ExecutorDelivery发送给UI线程。


3. 总结

了解Volley的Cache和Network服务端处理流程后,
我们基本上已经大致知道了Volley处理网络请求的脉络了。

结合上图,我们再来整体回顾一下。
从整体架构来看,Volley就是一个供APK调用的工具类。

当Volley的接口被调用,用于创建RequetQueue时,
Volley会负责创建下载需要的HttpStack,并指定存储缓存的Cache区域。
然后,Volley将管理HttpStack的BasicNetwork对象,及管理Cache的DiskBasedCache对象
一并递交给RequestQueue的构造函数。

RequestQueue会负责启动检索缓存的CacheDispatcher对象,
执行网络下载工作的NetworkDispatcher对象,
及为UI线程传递Response信息的ExecutorDelivery对象。

CacheDispatcher启动后,将负责创建DiskBasedCache对应的文件存储目录;
收到Request后,根据对应目录下的缓存信息,决定是否进行实际的网络下载工作。

NetworkDispatcher启动,收到Request后,将利用BasicNetwork进行实际的下载工作。

CacheDispatcher和NetworkDispatcher均利用ExecutorDelivery向UI线程返回Response信息。


在下一篇博客中,我们再从向RequestQueue加入Request的流程开始分析,
从客户端流程入手,看看Volley是如何分配Request的,并深入看看BasicNetwork的实际下载流程。

作者:Gaugamela 发表于2017/3/24 14:58:17 原文链接
阅读:44 评论:0 查看评论

Gradle 庖丁解牛(构建源头源码浅析)

$
0
0

1 背景

陆陆续续一年多,总是有人问 Gradle 构建,总是发现很多人用 Gradle 是迷糊状态的,于是最近准备来一个“Gradle 庖丁解牛”系列,一方面作为自己的总结,一方面希望真的能达到标题所示效果,同时希望通过该系列达到珍惜彼此时间的目的,因为目前市面上关于 Gradle 的教程都是在教怎么配置和怎么编写插件,很少有说明 Gradle 自己到底是个啥玩意的,还有是如何工作的,本系列以官方 release 3.4 版本为基础。

废话不多说,标题也表明了本篇所总结的内容 —— 构建源头源码浅析,不懂啥叫 Gradle 和 Groovy 的请先移步我以前文章《Groovy 脚本基础全攻略》《Gradle 脚本基础全攻略》,免得这一系列也看不明白咋回事。不过还是要先打个预防针,竟然还有人觉得 Gradle 只能构建 Android App,这 TM 就尴尬了,我想说 Gradle 就是一套脚手架,你想干啥玩意都可以,就看你想不想干,他就是个自动化构建框架,你整明白它的核心以后,他的插件你只用依据规则使用即可,譬如用来构建 Android 的 apply plugin: 'com.android.application' 只是它的一个小插件而已,实质就看你想搞啥了,Java、Web、JS、C 等等都可以用它构建;而整明白它原理核心之一就是你得先整明白他的规则,也就是本文所总结讨论的内容。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

2 基础技能叨叨叙

所谓基础技能就是一些铺垫,既然要说 Gradle 构建系列了,我们有必要知道 Gradle 相关的一些基本命令,方便下面演示时使用(注意:如下命令是针对安装 gradle 并配置环境变量的情况下执行,你如果做 Android 开发的话,Ubuntu 直接将如下所有 gradle 替换为 ./gradlew 即可,这样就可以用包装特性了,至于包装是咋回事后面会说),关于其他命令请呼叫 help 或者查看官方 User Guide 的 Using the Gradle Command-Line

//不懂 Gradle 命令了就常呼叫帮助吧。
gradle --help //或者 gradle -h 或者 gradle -? 。

//和本节相关的,查看当前 Gradle 构建包含哪些 task。
gradle tasks [--all] //--all可以更加详细。

//和本节相关的,运行 task。
gradle [taskName | tN] [taskName | tN] ... //支持多个 task 执行,支持驼峰缩写方式,譬如tN,但tN必须全局保持唯一。

//和本节相关的,当我们想修改 Gradle 插件默认定制约束配置时可以查看官方 dsl 文档或者运行如下命令。
gradle properties //结果会列出标准可配置属性列表和当前默认值,我们一般也可用该命令检查自己修改的属性值是否是期望的,譬如 Android 中修改 Project 的编译输出默认目录等。

//查看web版的构建性能耗时分布表,位于build/reports/profile目录下。
gradle taskName --profile

看完和本篇相关的命令总结后来看看前面提到的 gradle 替换 gradlew 执行是咋回事,其实实质可以查看官方 User Guide 的 The Gradle Wrapper,这里给出总结如下:

//项目目录下这四个文件的作用
gradlew (Unix Shell script)
gradlew.bat (Windows batch file)
gradle/wrapper/gradle-wrapper.jar (Wrapper JAR)
gradle/wrapper/gradle-wrapper.properties (Wrapper properties)

其实上面脚本的作用就是对 Gradle 的一个包装,保证任何机器都可以运行这个构建,即使这个机器没有安装 Gradle 或者安装的 Gradle 版本不兼容匹配,如果没装或者不兼容就会根据 gradle-wrapper.properties 里的配置下载特定版本,下载的包放在 $USER_HOME/.gradle/wrapper/dists 目录下,所以我们有时候第一次通过 gradlew 包装脚本执行构建时总会看到正在下载一个 zip 文件,实质就是在下 gradle-wrapper.properties 中指定的 zip 包。
gradlew 有一个非常人性化和牛逼的特点,解决了几种坑爹的问题,譬如团队开发保证 Gradle 构建版本一致性、gradle-wrapper.properties 下载地址方便切换到公司内部服务器等(甚至可以先通过 gradle.properties 文件配置公司服务器的帐号密码或者验证信息,然后在 gradle-wrapper.properties 中配置distributionUrl=https://username:password@somehost/path/to/gradle-distribution.zip,这样就可以达到公司内网加认证下载的双重安全了)。
gradlew 由来的最终原理其实是通过 Gradle 预置的 Wrapper (继承自 DefaultTask )来实现的(AS 内部默认实现了自动生成而已),譬如:

task createWrapper(type: Wrapper) {
    gradleVersion = '3.4'
}

运行 gradle createWrapper 就能生成上面描述的几个文件。而关于 Gradle 的 Wrapper 可以参考 DSL Reference 的 WrapperJavaDoc 的 Wrapper API,里面提供了一堆属性设置下载地址、解压路径、 gradleVersion 等,你也可以在 distributionUrl 中通过 ${gradleVersion} 来使用你设置的 DSL 变量,这里暂时不再展开。

说完命令我们接着说说 Groovy、DSL、Gradle 之间的关系,首先一定要明确,Groovy 不是 DSL,而是通用的编程语言,类似 Java、C++ 等,就是一种语言;但 Groovy 对编写 DSL 提供了很牛逼的支持,这些支持都源自 Groovy 自己语法的特性,比如闭包特性、省略分号特性、有参方法调用省略括弧特性、属性默认实现 getter、setter 方法特性等,当然,作为 Android 开发来说,Gradle 构建 Android 应用实质也是基于 Groovy 编写的 DSL,DSL 存在的意义就在于简化编写和阅读。

而 Gradle 的实质就是一个基于 Groovy 的框架了,也就是说我们得按照他的约束来玩了,和我们平时 Android 开发使用框架类似,一旦引入框架,我们就得按照框架的写法来规规矩矩的编写。Gradle 这个框架只负责流程约定,处理细节是我们自己的事,就像我们编译 Android App 时基于 Gradle 框架流程引入 apply plugin: 'com.android.application' 构建插件一样,具体做事是我们插件再约束的,插件又对我们简化了配置,我们只用基于 Gradle 框架和相关插件进行构建配置。

了所以简单粗暴的解释就是, Groovy 是一门语言,DSL 就是一种特定领域的配置文件,Gradle 就是基于 Groovy 的一种框架,就像我们以前做 Android 开发使用 Ant 构建一样,build.xml 就可以粗略的理解为 Ant 的 DSL 配置,所以我们编写 build.xml 时会相对觉得挺轻松(和后来的 Gradle 还是没法比的)。

搞清了他们之间的关系后我们就要深入看看为啥是这样了,因为关于 Gradle 构建现在中文网上的教程要么是教你如何配置 DSL 属性,要么就是教你如何使用 Groovy 去拓展自己的特殊 task,或者是教你怎么编写插件,却几乎没人提到 Gradle 这个构建框架的本质,所以使用起来总是心里发慌,有种不受控制的感觉。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

3 Gradle 源码源头分析

还记得我们前一小节讲的 gradlew 么,追踪溯源就从它开始,以前我也是出于好奇就去看了下它,发现这个 shell 脚本前面其实就是干了一堆没事干的事,大招就在它的最后一句,如下:

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

对的,大招就是 GradleWrapperMain,这货还支持通过 “$@” 传递参数,想想我们平时执行的 gradlew 命令,这下明白了吧,既然这样我们就拿他开涮,去看看他的源码,如下:

public class GradleWrapperMain {
    ......
    //执行 gradlew 脚本命令时触发调用的入口。
    public static void main(String[] args) throws Exception {
        //不多说,正如上面分析 gradlew 作用一样,去工程目录下获取 wrapper.jar 和 properties 文件。
        File wrapperJar = wrapperJar();
        File propertiesFile = wrapperProperties(wrapperJar);
        File rootDir = rootDir(wrapperJar);
        //解析gradlew 输入传递的参数等,反正就是一堆准备工作
        CommandLineParser parser = new CommandLineParser();
        ......
        //憋大招的两行
        WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile);
        wrapperExecutor.execute(
                args,
                new Install(logger, new Download(logger, "gradlew", wrapperVersion()), new PathAssembler(gradleUserHome)),
                new BootstrapMainStarter());
    }
}

上面 WrapperExecutor.forWrapperPropertiesFile 方法实质就是通过 java Properties 去解析 gradle/wrapper/gradle-wrapper.properties 文件里的配置,譬如 distributionUrl 等;接着执行 wrapperExecutor.execute 方法,args 参数就是我们执行 gradlew 脚本时传递的参数,Install 实例就是管理本地本项目是否有 wrapper 指定版本的 gradle,木有就去下载解压等等,BootstrapMainStarter 实例就是 wrapper 执行 gradle 真实入口的地方,所以我们接着看看 execute 这个方法,如下:

public void execute(String[] args, Install install, BootstrapMainStarter bootstrapMainStarter) throws Exception {
    //config 就是 gradle-wrapper.properties 解析的配置
    File gradleHome = install.createDist(config);
    bootstrapMainStarter.start(args, gradleHome);
}

到这里如果本地没有 wrapper 包装的 Gradle,就会下载解压等,然后准备一堆货,货备足后就调用了前面说的 BootstrapMainStarter 的 start 方法,如下:

public class BootstrapMainStarter {
    public void start(String[] args, File gradleHome) throws Exception {
        File gradleJar = findLauncherJar(gradleHome);
        URLClassLoader contextClassLoader = new URLClassLoader(new URL[]{gradleJar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());
        Thread.currentThread().setContextClassLoader(contextClassLoader);
        Class<?> mainClass = contextClassLoader.loadClass("org.gradle.launcher.GradleMain");
        Method mainMethod = mainClass.getMethod("main", String[].class);
        mainMethod.invoke(null, new Object[]{args});
        if (contextClassLoader instanceof Closeable) {
            ((Closeable) contextClassLoader).close();
        }
    }
    ......
}

不解释,快上车,真的 Gradle 要现身了,Wrapper 的使命即将终结,我们把重点转到 org.gradle.launcher.GradleMain 的 main 方法,如下:

public class GradleMain {
    public static void main(String[] args) throws Exception {
        new ProcessBootstrap().run("org.gradle.launcher.Main", args);
    }
}

GG了,莫慌,我们的重点不是看懂 Gradle 的每一句代码,我们需要捡自己需要的重点,这货设置各种 ClassLoader 后最终还是调用了 org.gradle.launcher.Main 的 run 方法,实质就是 EntryPoint 类的 run 方法,因为 Main 类是 EntryPoint 类的实现类,而 EntryPoint 的 run 方法最主要做的事情就是创建了一个回调监听接口,然后调用了 Main 重写的 doAction 方法,所以我们去到 Main 的 doAction 看看,如下:

public class Main extends EntryPoint {
    ......
    protected void doAction(String[] args, ExecutionListener listener) {
        createActionFactory().convert(Arrays.asList(args)).execute(listener);
    }

    CommandLineActionFactory createActionFactory() {
        return new CommandLineActionFactory();
    }
}

这货实质调用了 CommandLineActionFactory 实例的 convert 方法得到 Action 实例,然后调用了 Action 的 execute 方法,我去,真特么绕的深,这弯溜的,我们会发现 CommandLineActionFactory 里的 convert 方法实质除过 log 记录准备外干的惟一一件事就是创建其内部类 WithLogging 的对象,这时候我们可以发现 Action 的 execute 方法实质就是调用了 WithLogging 的 execute 实现,如下:

public void execute(ExecutionListener executionListener) {
    //executionListener 是前面传入的回调实现实例
    //各种解析config,譬如参数的--内容等等,不是我们的重点
    //各种初始化、log启动等等,不是我们的重点
    ......
    //大招!!!外面new WithLogging实例时传入的参数!!!
    action.execute(executionListener);
    ......
}

这不,最终还是执行了 new ExceptionReportingAction(
new JavaRuntimeValidationAction(
new ParseAndBuildAction(loggingServices, args)),
new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), loggingConfiguration, clientMetaData())));
对象的 execute 方法(上面的 action 就是这个对象),关于这个对象的创建我们只用关注 new JavaRuntimeValidationAction(
new ParseAndBuildAction(loggingServices, args))
这个参数即可,这也是一个 Action,实例化后在 ExceptionReportingAction 的 execute 调用了他的 execute,而 ParseAndBuildAction 的 execute 又被 JavaRuntimeValidationAction 的 execute 触发,有点包装模式的感觉,所以我们直接关注 ParseAndBuildAction 的实例化和 execute 方法,因为其他不是我们的重点,如下:

private class ParseAndBuildAction implements Action<ExecutionListener> {
    ......
    public void execute(ExecutionListener executionListener) {
        List<CommandLineAction> actions = new ArrayList<CommandLineAction>();
        //给你一个默认的 help 和 version 的 CommandLineAction 加入 actions 列表
        actions.add(new BuiltInActions());
        //创建一个 GuiActionsFactory 和 BuildActionsFactory 加入 actions 列表
        createActionFactories(loggingServices, actions);
        //依据参数给各个添加到列表的 CommandLineAction 对象进行配置
        CommandLineParser parser = new CommandLineParser();
        for (CommandLineAction action : actions) {
            action.configureCommandLineParser(parser);
        }
        //依据这几个参数获取创建一个可用的 Action<? super ExecutionListener>
        Action<? super ExecutionListener> action;
        try {
            ParsedCommandLine commandLine = parser.parse(args);
            //如果输入的命令中包含 gui 参数则创建 GuiActionsFactory 的 action 备用。
            //如果输入的命令中包含 help 或者 version 参数则创建 BuiltInActions 的 action 备用。
            //其他参数的则创建 BuildActionsFactory 的 action 备用。
            action = createAction(actions, parser, commandLine);
        } catch (CommandLineArgumentException e) {
            action = new CommandLineParseFailureAction(parser, e);
        }
        //执行我们创建的备用 action。。。。
        action.execute(executionListener);
    }
    ......
}

既然我们是追主线分析(关于执行命令中带 help、version、gui 的情况我们就不分析了,也比较简单,当我们执行 gradle –help 或者 gradle –gui 时打印的 help 或者弹出的 GUI 是 BuiltInActions 或者 GuiActionsFactory ,比较简单,不作分析),我们看核心主线 BuildActionsFactory 的 createAction 方法和通过 createAction 方法生成的 Runnable 的 run 方法即可(所谓的主线就是我们执行 gradle taskName 命令走的流程,譬如 gradle asseambleDebug 等),如下是 BuildActionsFactory 的 createAction 方法:

public Runnable createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
    //命令各种转换包装
    Parameters parameters = parametersConverter.convert(commandLine, new Parameters());
    ......
    //三种判断,哪个中了就返回,runXXX系列方法实质都调用了runBuildAndCloseServices方法,只是参数不同而已
    if (parameters.getDaemonParameters().isEnabled()) {
        return runBuildWithDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
    }
    if (canUseCurrentProcess(parameters.getDaemonParameters())) {
        return runBuildInProcess(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
    }
    return runBuildInSingleUseDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
}

既然上面都判断最后调用都是类同的,那我们就假设调用了 runBuildInProcess 方法吧,如下:

private Runnable runBuildInProcess(StartParameter startParameter, DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
    //创建client,这是个神奇的设计思路,大招!
    ServiceRegistry globalServices = ServiceRegistryBuilder.builder()
            .displayName("Global services")
            .parent(loggingServices)
            .parent(NativeServices.getInstance())
            .provider(new GlobalScopeServices(startParameter.isContinuous()))
            .build();
    //上面说的,BuildActionsFactory的createAction方法最后都是调用这个方法,只是传递参数不同而已!
    return runBuildAndCloseServices(startParameter, daemonParameters, globalServices.get(BuildExecuter.class), globalServices);
}

此刻您可憋住了,别小看这么简单的一个方法,这玩意麻雀虽小五脏俱全啊,在我第一次看这部分源码时是懵逼的,好在看见了相关类的注释才恍然大悟,至于为啥我们现在来分析下。先说说 globalServices 对象的构建吧,其实和 createGlobalClientServices() 这个方法类似,随意咯,我们就随便看个,如下:

    private ServiceRegistry createGlobalClientServices() {
        return ServiceRegistryBuilder.builder()
                .displayName("Daemon client global services")
                .parent(NativeServices.getInstance())
                .provider(new GlobalScopeServices(false))
                .provider(new DaemonClientGlobalServices())
                .build();
    }

看起来就是构造了一个 clientSharedServices 对象,然后交给下面的 runBuildAndCloseServices 方法使用,对的,就是这样的,只是这个 createGlobalClientServices() 方法真的很懵逼,ServiceRegistryBuilder 的 build() 方法实质是实例化了一个 DefaultServiceRegistry 对象,然后通过构造方法传递了 parent(NativeServices.getInstance()) 实例,通过 addProvider(provider) 方法传递了 provider(new GlobalScopeServices(false)) 和 provider(new DaemonClientGlobalServices()) 实例,巧妙的地方就在 DefaultServiceRegistry 类的注释上面,大家一定要先看注释,ServiceRegistryBuilder 的 build() 最后调用的是 DefaultServiceRegistry 对象的 addProvider 方法,实质调用的是 DefaultServiceRegistry 的 findProviderMethods(provider) 方法,如下:

private void findProviderMethods(Object target) {
    Class<?> type = target.getClass();
    RelevantMethods methods = getMethods(type);
    //把target自己和所有父类中以create开头的方法通过new DecoratorMethodService(target, method)包装加入到ownServices列表。
    for (Method method : methods.decorators) {
        ownServices.add(new DecoratorMethodService(target, method));
    }
    //把target自己和所有父类中以create开头的方法通过new DecoratorMethodService(target, method)包装加入到ownServices列表。
    for (Method method : methods.factories) {
        ownServices.add(new FactoryMethodService(target, method));
    }
    //把target自己和所有父类中叫cofigure的方法都反射调用一把。
    for (Method method : methods.configurers) {
        applyConfigureMethod(method, target);
    }
}

这时咱们先回过头看看前面说的 createGlobalClientServices() 方法,我们发现其中的 NativeServices、FileSystemServices、DaemonClientGlobalServices 都没有 config 方法,但是有一堆 createXXX 的方法,这些都会被加入 ownServices 列表,但是 GlobalScopeServices 类却有 config 方法,如下:

public class GlobalScopeServices {
    ......
    //该方法在DefaultServiceRegistry的findProviderMethods(provider)中被反射调用。
    //registration是DefaultServiceRegistry的newRegistration()方法返回的ServiceRegistration匿名实现对象。
    //classLoaderRegistry就是一个
    void configure(ServiceRegistration registration, ClassLoaderRegistry classLoaderRegistry) {
        //getAll根据PluginServiceRegistry.class传入后的一系列规则,找到所有factory方法。
        final List<PluginServiceRegistry> pluginServiceFactories = new DefaultServiceLocator(classLoaderRegistry.getRuntimeClassLoader(), classLoaderRegistry.getPluginsClassLoader()).getAll(PluginServiceRegistry.class);
        for (PluginServiceRegistry pluginServiceRegistry : pluginServiceFactories) {
            registration.add(PluginServiceRegistry.class, pluginServiceRegistry);
            if (pluginServiceRegistry instanceof GradleUserHomeScopePluginServices) {
                registration.add(GradleUserHomeScopePluginServices.class, (GradleUserHomeScopePluginServices) pluginServiceRegistry);
            }
            pluginServiceRegistry.registerGlobalServices(registration);
        }
    }
    ......
}

到这里你可以暂时松口气了,上面 config 等等一堆都是在做准备,说白了就是各种列表注册都放好,然后 DefaultServiceRegistry 的 get(…) 系列方法实质都是通过 doGet(Type serviceType) 的 invok 方法调用等。接着让我们把目光移到上面运行 gradle taskName 命令时分析的 BuildActionsFactory 的 runBuildInProcess(…) 方法,我们在该方法中调用 runBuildAndCloseServices(…) 方法时第三个参数传递的是 globalServices.get(BuildExecuter.class), 也就是调用了 DefaultServiceRegistry 的 get(BuildExecuter.class) 方法,刚刚说了 DefaultServiceRegistry 的 get(…) 实质就是 invoke 一个方法,在这里 serviceType 参数是 BuildExecuter.class,所以调用的就是 ToolingGlobalScopeServices 的 createBuildExecuter(…) 方法,ToolingGlobalScopeServices 是 LauncherServices 的 registerGlobalServices(…) 方法实例化的,而 LauncherServices implements PluginServiceRegistry 又是刚刚上面分析 GlobalScopeServices 的 configure(…) 方法里被实例化添加的,LauncherServices 的 registerGlobalServices(…) 方法也是在那调用的。

绕了一圈真是折腾,突然发现累觉不爱,觉得 Gradle 框架中 DefaultServiceRegistry 类的设计真的是挺颠覆我认知的,虽然没啥黑科技,但是这个设计思路真的是坑爹,很容易绕懵逼,好在后来整明白了,真想说句 ZNMKD。好了,牢骚也发完了,下面该正题了,我们接着上面说的去看看 LauncherServices 内部 ToolingGlobalScopeServices 的 createBuildExecuter(…) 方法,一贯做法,关注主线,咱们会发现这方法最主要的就是层层简单代理模式包装实例化 BuildActionExecuter 对象,最关键的就是实例化了 InProcessBuildActionExecuter 对象,这个对象就一个 execute 方法(这个 execute 方法被调用的地方就在上面分析的 BuildActionsFactory 的 runBuildInProcess 方法返回的 RunBuildAction 对象的 run 方法中,RunBuildAction 对象的 run 方法又是前面分析的源头触发的),如下:

public class InProcessBuildActionExecuter implements BuildActionExecuter<BuildActionParameters> {
    ......
    //BuildActionsFactory的runBuildInProcess方法返回的RunBuildAction对象的run方法触发该方法调用。
    //参数都是RunBuildAction中传递的,RunBuildAction的参数又是前面分析的BuildActionsFactory的runBuildInProcess方法传递。
    public Object execute(BuildAction action, BuildRequestContext buildRequestContext, BuildActionParameters actionParameters, ServiceRegistry contextServices) {
        //创建DefaultGradleLauncher对象,真是活菩萨啊,总算看到光明了!
        GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(action.getStartParameter(), buildRequestContext, contextServices);
        try {
            gradleLauncher.addStandardOutputListener(buildRequestContext.getOutputListener());
            gradleLauncher.addStandardErrorListener(buildRequestContext.getErrorListener());
            GradleBuildController buildController = new GradleBuildController(gradleLauncher);
            buildActionRunner.run(action, buildController);
            return buildController.getResult();
        } finally {
            gradleLauncher.stop();
        }
    }
}

赞,到此真想说句真是不容易啊!!!总算看见光明了,我 Gradle 的大 GradleLauncher,为啥这么称呼呢,因为你看了 GradleLauncher 实现你会有种柳暗花明又一村的感觉,真的,你会觉得前面这些复杂的初始化就是为了等到这个实例的到来,因为 GradleLauncher 实现里就真真切切的告诉你了 Gradle 构建的三大生命周期——-初始化、配置、执行,不信你看:

public class DefaultGradleLauncher implements GradleLauncher {
    //大名鼎鼎的 gradle 构建三步生命周期!!!!有种亲爹的感觉!
    private enum Stage {
        Load, Configure, Build
    }
    ......
}

到此构建源头就分析完了,下一篇会接着这里分析 DefaultGradleLauncher 及后续真正开始构建的流程,所以这时候你回过头会发现 Gradle 其实也就那么回事,也是代码写的(哈哈,找打!),只是这一篇我们只分析了 Gradle 框架自身初始化(非构建生命周期的初始化,要区分)的核心流程。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

4 总结

为了 Gradle 庖丁解牛系列做铺垫,本篇主要进行了 Gradle 基础铺垫说明总结,后面对 Gradle 框架自身初始化(非构建生命周期的初始化,要区分)的核心流程进行了分析,通过本文我们至少应该知道如下:

  • Gradle 只是一个构建框架,而且大多数代码是 Java 和 Groovy 编写。
  • gradlew 是 gradle 的一个兼容包装工具。
  • 执行 gradle 或者 gradlew 命令开始进行构建生命周期前做的第一步是对 Gradle 框架自身的初始化(本文浅析内容)。

有了这一篇的铺垫,下面几篇我们会以此分析继续,不过主线都是基于执行一个 gradle taskName 命令,所以如果想看懂这个系列文章,建议先补习下 Gradle 构建基础。

^v^当然咯,如果觉得对自己有帮助,不妨扫描打赏一点买羽毛球的票子,是一种动力也是一种鼓励,谢谢。

这里写图片描述
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

作者:yanbober 发表于2017/3/24 17:54:57 原文链接
阅读:265 评论:4 查看评论

UGUI之坑点小结

$
0
0

以下是我总结断断续续遇到的一些UGUI上的问题,有机会能改则改,改不了的只能想办法注意和避免了。

UGUI刷新问题

场景中没有遮挡的背景时,首次打开UI 手机上不停闪烁或各种花屏问题:

处理办法:

场景中最好加一个物体遮挡,如进游戏底部加一块纯黑板背景,可以避免UI闪烁

其实是canvas相机照不到东西的时候,UGUI的显示不会及时刷新,比如把canvas中最后一个active的对象都隐藏的话,显示上会不刷新,即使拿到的属性activeself其实是false。即看到显示残留

如果父对象下面挂了几个子对象a,b,c,d,如果删除了其中部分子对象 如a,想立即修改bcd的先后层次的话,用SetSiblingIndex()方式就可能出问题,即出现 数值刷新了,但显示未刷新的问题。

解决办法:

因此对于兄弟节点出生销毁后同帧立即调整当前节点层级的话,最好通过transform.SetAsFirstSibling();或transform.SetAsLastSibling();来实现层级调整

UGUI的图集问题
1. UGUI的图集打包算法有问题,基本表现是它一定把不同实际压缩格式的小图放在了不同的group。比如有两个小图,用了不同的压缩格式,他们最终会在两个group图,导致一个图集扩成了两张;
2. 带透明通道和不带透明通道的素材也会被分在两个group!!坑的地方来了,使得如果众多素材中有几个不带透明通道的,那么你把那些透明素材压缩到死,也不会将非透明通道图合并到当前图集来,(因为是带和不带透明通道的两种图本身就会以两种压缩格式来处理))
3. 呵呵mitmap有或没有也会被拆分到连个group里

解决办法,几个都要保证:
  1. 同一个图集统一压缩格式;
  2. 在素材上,同一个图集中的所有素材都应该是带透明像素的,或者统一不带透明像素,遇到透明图集出现个别不包含透明像素的,去PS给将这个图其中一像素修改为有透明度就OK(如透明度99%);
  3. 想要使用UGUI默认的ETC压缩的话,单张图应该宽和高尺寸都能被4整除。否则的话,该单独素材会以RGB32压缩格式处理;

因此,如图中,所有标黄的地方,即便你填了相同的Packing Tag,但黄色设置有一项不一致,最终这批图不会出现在同一个图集中的

这里写图片描述

作者:Stephanie_1 发表于2017/3/24 18:34:11 原文链接
阅读:61 评论:0 查看评论

《从零开始搭建游戏服务器》项目发布到Linux环境

$
0
0

前言:

之前我们提及了如何使用Maven来创建、管理和打包项目,也简单过了一遍Linux中搭建Java开发环境的步骤,现在我们就开始将我们之前开发的项目demo发布到Linux环境下,并让它正常运行起来。

发布思路:

  • 使用Maven将项目打包为.jar
  • 将项目的.jar和项目所有依赖的jar包都复制到Linux下
  • 创建项目启动脚本来启动项目

准备工作:

  • 查询Linux系统机器的Ip地址:方法很简单,在Linux中打开终端输入ifconfig即可查到Ip地址为192.168.35.130
  • 修改Eclipse中客户端和服务器的连接Ip地址改为此地址:
    private static final String IP = "192.168.35.130";
    private static final int PORT = 8088;

Maven打包项目:

在之前的篇幅中我们已经讲解了Maven的相关常识,这里我们一开始没有使用Maven来创建项目,而是使用Eclipse创建了一个Java Application,这里要使用Maven来管理已创建好的Eclipse工程,步骤如下:

  • Eclipse安装Maven插件:
    在Eclipse中点击Help->Eclipse Marketplace,搜索maven在搜索结果列表中找到Maven Integration for Eclipse插件,假如未安装则点击Install进行安装,已安装但非最新版本可以点击Update,已经安装且为最新版本显示Installed:
    Maven Integration for Eclipse

  • 普通Eclipse项目Maven适配:
    在Eclipse选中项目根目录,右键Configure->Convert To Maven Project:

    在弹窗中输入Group Id(包名,一般是域名反写)、Artifact Id(工程名称)和Version:

    操作完成之后,在项目下面多出了一个pom.xml文件

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.tw.login</groupId>
      <artifactId>TWLogin</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <build>
        <sourceDirectory>src</sourceDirectory>
        <resources>
          <resource>
            <directory>src</directory>
            <excludes>
              <exclude>**/*.java</exclude>
            </excludes>
          </resource>
        </resources>
        <plugins>
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
              <source>1.8</source>
              <target>1.8</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </project>
  • 项目依赖于其他项目:
    可以打开pom.xmlDependencies页签,通过Add进行添加,添加时输入可以通过输入GroupId和ArtifactId进行搜索:
    例如:我们项目中使用了Netty来搭建网络层开发,所以要将其jar包添加到Dependencies中:

    需要将本地/lib文件夹中的.jar一一添加到Dependencies:

    或者是选中pom.xml右键Maven->Add Dependency也可以进入到添加依赖的窗口。也可以在pom.xml中直接添加<dependencies>...</dependencies>,添加一下内容:

    <dependencies>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.0.42.Final</version>
    </dependency>
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>2.5.0</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>commons-pool</groupId>
        <artifactId>commons-pool</artifactId>
        <version>1.6</version>
    </dependency>
    </dependencies>
  • 项目打包:
    通常通过命令行窗口和Maven指令来进行打包,但这是我们可以直接在Eclipse中进行打包简化打包流程,Jave Application工程会被打包成.jar包,而Jave Web工程则打包成.war包,这里我们要将Java Application打包成一个可执行程序的jar包,需要提前考虑三个步骤:

    • 配置文件需要打进jar包;
    • 需要制定程序的main入口类;
    • 所有依赖的第三方库也要打进jar包。

    满足以上三个条件的话,我们就能使用java -jar xxx.jar来执行我们的程序了,为了满足这个目的,我们通常需要借助一些辅助于Maven的打包插件,常见的有maven-assembly-pluginmaven-shade-plugin,但是使用maven-assembly-plugin的话会把所有需要打到包里的文件全部打成一个.jar包,而且假如第三方配置文件于本地配置文件存在重名时,会出现直接覆盖的bug,所以这里我们还是选用maven-shade-plugin插件来打包。

    • 修改pom.xml,首先引入maven-shade-plugin,需要在<build><plugins><plugin>...</plugin></plugins></build>中添加:
      <groupId>org.apache.maven.plugins</groupId>  
      <artifactId>maven-shade-plugin</artifactId>  
      <version>1.4</version>

    在同等级位置添加入口类和并制定打包插件(其中<goal>shade</goal>就指定了打包插件使用maven-shade-plugin):

    <executions>
    <execution>
        <phase>package</phase>
        <goals>
            <goal>shade</goal>  
        </goals>
        <configuration>
            <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                    <!-- 定义入口类 -->
                    <mainClass>com.tw.login.LoginSocketServer</mainClass>
                </transformer>
            </transformers>
        </configuration>
    </execution>
    </executions>
    • 方法一:在Eclipse中选中项目的pom.xml文件,右键Run As->Maven Clean,假如清理成功,target文件夹中的编译生成文件都被清除掉:
      [INFO] Scanning for projects...
      [INFO]                                                                         
      [INFO] ------------------------------------------------------------------------
      [INFO] Building TWLogin 0.0.1-SNAPSHOT
      [INFO] ------------------------------------------------------------------------
      [INFO] 
      [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ TWLogin ---
      [INFO] Deleting E:\java web\workplace\TWLogin\target
      [INFO] ------------------------------------------------------------------------
      [INFO] BUILD SUCCESS
      [INFO] ------------------------------------------------------------------------
      [INFO] Total time: 0.293 s
      [INFO] Finished at: 2017-03-24T13:09:58+08:00
      [INFO] Final Memory: 7M/153M
      [INFO] ------------------------------------------------------------------------

    然后执行打包指令:选中项目的pom.xml文件,右键Run As->Maven Build

    假如报错:

    [ERROR] No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]
    [ERROR] 
    [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
    [ERROR] Re-run Maven using the -X switch to enable full debug logging.
    [ERROR] 
    [ERROR] For more information about the errors and possible solutions, please read the following articles:
    [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/NoGoalSpecifiedException

    pom.xml文件<build>标签里面加上<defaultGoal>compile</defaultGoal>即可,假如又出现如下错误:

    [ERROR] COMPILATION ERROR : 
    [INFO] -------------------------------------------------------------
    [ERROR] No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?
    [INFO] 1 error
    [INFO] -------------------------------------------------------------
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD FAILURE
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 0.594 s
    [INFO] Finished at: 2017-03-24T14:06:13+08:00
    [INFO] Final Memory: 10M/153M
    [INFO] ------------------------------------------------------------------------
    [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project TWLogin: Compilation failure
    [ERROR] No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?
    [ERROR] -> [Help 1]

    解决方案是:在Eclipse选中项目,右键->Properties->Project Facets,勾选Apache Tomcat,因为Eclipse中Maven打包依赖Tomcat服务:

    正确的输出结果如下:

    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] ------------------------------------------------------------------------
    [INFO] Building TWLogin 0.0.1-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    [INFO] 
    [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ TWLogin ---
    [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
    [INFO] Copying 1 resource
    [INFO] 
    [INFO] --- maven-compiler-plugin:3.5.1:compile (default-compile) @ TWLogin ---
    [INFO] Nothing to compile - all classes are up to date
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 1.801 s
    [INFO] Finished at: 2017-03-22T13:07:50+08:00
    [INFO] Final Memory: 10M/217M
    [INFO] ------------------------------------------------------------------------

    假如出现编码的警告,要解决这个警告,只需在pom.xml<project>标签内添加以下内容指定编码方式:

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    • 方法二:可以打开命令行窗口,定位到当前项目的目录下,使用mvn clean package进行打包,相关依赖信息和打包方式在pom.xml中配置:
  • 打包成功后,在项目的target目录下会生成对应Version版本的.jar包,例如这里我的输出包为:TWLogin-0.0.1-SNAPSHOT.jar,这就是我们要用来放到Linux服务器中运行的源码包:

Linux下安装Maven:

  • Maven官网下载最新的Maven安装包,这里我下载的是:apache-maven-3.3.9-bin.tar.gz
  • 通过终端解压安装包到/usr/local目录下:

    sudo tar -xzf /mnt/Windows/apache-maven-3.3.9-bin.tar.gz -C /usr/local/jvm
  • 修改用户变量配置文件~/.bashrc和系统环境配置文件/etc/profile,都是在文件最后插入Maven的配置信息:

    
    #Set maven environment
    
    MAVEN_HOME=/usr/local/jvm/apache-maven-3.3.9
    export MAVEN_HOME
    export PATH=${PATH}:${MAVEN_HOME}/bin
  • 使用source .bashrcsource /etc/profile使修改配置文件内容立即生效,使用mvn -v测试安装是否成功,如果成功,则显示当前安装的Maven的版本信息:

    linsh@ubuntu:~$ mvn -v
    Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-10T08:41:47-08:00)
    Maven home: /usr/local/jvm/apache-maven-3.3.9
    Java version: 1.8.0_121, vendor: Oracle Corporation
    Java home: /usr/local/jvm/jdk1.8.0_121/jre
    Default locale: zh_CN, platform encoding: UTF-8
    OS name: "linux", version: "4.2.0-27-generic", arch: "amd64", family: "unix"
    linsh@ubuntu:~$ 

在Linux中运行项目:

  • 我们将之前打包得到的TWLogin-0.0.1-SNAPSHOT.jar复制到Linux中,新建一个目录作为项目总目录application,然后再为每个项目创建一个子目录,这里我们以我们的项目名称来创建子目录,将项目文件复制到此目录下:

    sudo mkdir /application
    sudo mkdir /application/TWLogin
    sudo cp -r /mnt/Windows/TWLogin /application/TWLogin
  • 测试运行项目:
    直接进到项目目录/application/TWLogin中,使用运行指令java -jar TWLogin-0.0.1-SNAPSHOT.jar执行程序,然后再在Eclipse中启动客户端代码,尝试连接服务器并发送数据,假如运行正常,情况如下:

    linsh@ubuntu:/application/TWLogin$ java -jar TWLogin-0.0.1-SNAPSHOT.jar
    三月 21, 2017 10:07:42 上午 com.tw.login.LoginSocketServer main
    信息: 开始启动Socket服务器...
    三月 21, 2017 10:07:42 上午 com.tw.login.LoginSocketServer run
    信息: Socket服务器已启动完成
    三月 21, 2017 10:07:54 上午 com.tw.login.LoginSocketServer channelRead
    信息: 数据内容:UserName=linshuhe,Password=123456

    注:
    假如你也跟我一样出现这个错误:TWLogin-0.0.1-SNAPSHOT.jar中没有主清单属性,那么恭喜你跟我一样犯了个低级错误,忘了在使用maven打包项目之前,在pom.xml中添加项目的入口函数main的相关路径信息,解决方案就是在pom.xml的中添加以下内容:

    <transformers>
    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
        <mainClass>com.tw.login.server.LoginSocketServer</mainClass>
    </transformer>
    </transformers>

    此处com.tw.login.server.LoginSocketServer脚本中的main函数即使本应用入口函数。

  • 创建启动脚本:
    我们为了避免每次运行项目都敲一遍启动项目所需的指令,通常会将指令封装成一个启动脚本,在Windows系统下我们通常使用.bat的批处理脚本来进行批处理,把很多命令放在此文件中,当然也能作为一些应用程序的启动脚本,而在Linux系统中,使用shell脚本来实现同等的效果,Shell脚本是Linux的一种文本文件,这里我们就来编辑一下启动脚本start.sh的内容:

    
    #!bin/sh
    
    echo 如果不能正常运行,请安装JDK 1.8版
    java -jar TWLogin-0.0.1-SNAPSHOT.jar
  • 执行启动脚本:
    启动脚本通常放到与可执行.jar文件同级的目录下,在终端中定位到脚本所在的目录,执行:

    sh start.sh
作者:linshuhe1 发表于2017/3/24 17:31:40 原文链接
阅读:755 评论:0 查看评论

(Swift 实现)二叉搜索树 —— 创建

$
0
0

了解了二叉堆之后,二叉搜索树就好说了,就是一个节点,左边的子节点是不可能比他大的,右边的子节点是一定大于它的,想了半天终于把创建给写好了。

直接看代码

import UIKit

var str = "二叉搜索树"
//这个就不跟前面的完全二叉树一样了,得自己建了类或者结构体了,我建了个类
class erchaTreeNote {
    var  data: Int
    var leftChild: erchaTreeNote!
    var rightChild: erchaTreeNote!
    init(data:Int) {
        self.data = data
    }
}

var a = [12,321,432,213,423,4]

func createTree() -> (erchaTreeNote) {

    let root = erchaTreeNote(data:a[0]);
    for x in a[1...a.count-1] {

        let child = erchaTreeNote(data: x)

        var temp = root
        //循环的条件想了半天,想着如何能走下去,在纸上练了几遍,发现了规律,本来进来一个数如果它加进去了,它的左右子节点都是空的,再往下就不走了,但是这个是走不通的,再想他们有什么共性,我就想,既然把它按在了树上,那它再走一次,必然和上一次的路径是一样的,当我找到和它一模一样的时候,就是结束的时候,如果我找不到它,一直都不能结束。就按这个条件走就出来了。

        while temp !== child {
        //如果进来的数小于父节点
            if child.data < temp.data {
            //不为空,那我就把父节点左边子节点拿上,再重新来过
                if temp.leftChild != nil
                {
                    temp = temp.leftChild
                }
             //当父节点左边是空的时候,那就直接填上
                else
                {
                    temp.leftChild = child     
                    print("\(temp.data)左边的孩子")
                    temp = child //优化语句
                }
            }else //进来的数大于父节点
            {
            //不为空,那我就把父节点右边子节点拿上,重新来过
                if temp.rightChild != nil {
                    temp = temp.rightChild
                }
            //当父节点的右边为空的时候,那就直接补上    
                else
                {
                    temp.rightChild = child
                    print("\(temp.data)右边的孩子")
                    temp = child //优化语句
                }   
            }
        }
        print(child.data)
    }
    return root
}
createTree()

就酱,还是蛮有成就感的。要是不对,咱们一起讨论

作者:u010095372 发表于2017/3/24 18:03:40 原文链接
阅读:18 评论:1 查看评论

Android性能优化 内存优化 避免OOM

$
0
0

转载请注明出处:http://blog.csdn.net/smartbetter/article/details/65442706

怎样才能写出高性能的应用程序,如何避免程序出现OOM,或者当程序内存占用过高的时候该怎么样去排查。这些问题对于一个优秀的应用程序应当处理得恰到好处。为此,我也阅读了不少Android官方给出的性能优化建议。本篇将系统的从 Android的内存管理方式 到 App内存优化方法,最后到OOM问题优化,系统的讲解内存优化的一些方案、见解,帮助大家能够写出更加出色的应用程序,避免OOM。

建议阅读的官方文档:https://developer.android.google.cn/topic/performance/memory.html

1.Android的内存管理方式

我们知道Android系统是多任务系统,通过分时复用的方式,多个应用可以同时在一个手机上运行。

我们打开终端,输入:

$ adb shell     // 进入安卓底层Linux系统的命令
$ ps            // 查看系统里面进程的命令

ps

USER:用户, PID:进程id, PPID:父进程id, VSIZE:进程虚拟地址空间大小, RSS:进程正在使用的物理内存大小, WCHAN:进程处于休眠状态时在内核中的地址, PC:program counter, NAME:进程名称.

$ dumpsys meminfo com.android.bluetooth   // 查看指定进程相关信息

dumpsys meminfo

1.Android系统内存分配与回收方式

一个App通常就是一个进程对应一个虚拟机。
GC只在Heap剩余空间不够时才触发垃圾回收,而且GC触发时,所有的线程都会被暂停(如果GC时间比较长,还有可能出现内存抖动的现象)。

2.App内存限制机制

每个App分配的最大内存限制,随不同设备而不同。系统分配的大小肯定是够用的,如果你的App出现OOM,往往是你的App优化做的不是很好。
其中吃内存比较厉害就是图片了。

Android上App的运行是有内存限制的,OOM导致App崩溃。

我们可以通过代码的方式查看一下手机的内存限制和最大内存限制:

ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
Log.i("JAVA", "内存限制:" + manager.getMemoryClass() + "M");
Log.i("JAVA", "最大内存限制:" + manager.getLargeMemoryClass() + "M");

3.切换应用时后台App清理机制

多个App切换的时候使用的是 LRU Cache(其中是使用LRU算法进行清理排序的。LRU算法:最近使用的排在最前面,最少可能的被清理掉)。
当具体清理的时候,系统还会发出 onTrimMemory() 的回调,随着系统内存有变化的时候发出回调给各个应用,通知系统的内存情况,然后我们的App可以做相应处理,把App中不用的内存尽快清理掉,这样你的App的占用就相对小一点,系统在查看后台App的时候发现你这个App内存占用比较少,那么在清理的时候就会小一点。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        Log.i("JAVA", "level:" + level);
    }
}

4.监控内存的几种方法

我们可以通过代码的方式监控内存:

Runtime runtime = Runtime.getRuntime();
Log.i("JAVA", "总内存:" + runtime.totalMemory() * 1.0f/(1024*1024) + "M");
Log.i("JAVA", "空闲内存:" + runtime.freeMemory() * 1.0f/(1024*1024) + "M");
Log.i("JAVA", "可申请最大内存:" + runtime.maxMemory() * 1.0f/(1024*1024) + "M");

我们还可以通过 Android Studio 的 Android Monitors工具 方便的监控内存:

Android Monitors

除此之外,Android Studio还提供了Android Device Monitor工具,可用于整体的监控内存:

Android Device Monitor

我们先选住应用程序,再点击 Heap 按钮:

Heap

然后点击 Cause GC 按钮:

Cause GC

此时Android Device Monitor就为我们展示了当前整体的内存情况:

Cause GC Updates

其中我们主要看的就是 data object 和 class object 这两个,如果不停的运行,这两个也不停的变大,那么很有可能内存泄漏。

2.App内存优化方法

1.数据结构优化

1)频繁字符串拼接用 StringBuilder,字符串通过”+”的方式拼接,会产生中间字符串内存块;
2)ArrayMap、SparseArray 替换 HashMap,内存使用更少,数据量大了效率更高;
3)内存抖动(监控内存时发现内存情况呈现 锯齿形),一般是代码设计的时候变量使用不当引起的;

内存抖动

4)再小的Class也要耗费 0.5KB;
5)HashMap每一个entry需要额外占用 32B。

2.对象复用

1)复用系统自带的资源;
2)ListView/GridView 的 ConvertView 复用;
3)避免在 onDraw方法 里面执行对象的创建,把对象创建放在外面。

3.避免内存泄漏

内存泄漏,由于代码有瑕疵,导致这块内存发生内存泄漏,虽然是停止不用了,但是依然被其他东西引用着,使得GC没法对它回收。
1)内存泄漏会导致剩余可用 Heap 越来越少,频繁触发GC,尤其是Activity泄漏;
2)引用上下文选用 Application Context 而不是 Activity Context;
3)注意 Cursor对象 及时关闭。

3.OOM问题优化

1.OOM问题分析

OOM的绝大部分场景发生在图片。

2.强引用、软引用

强引用:GC绝不会回收它,JVM宁愿抛出OutOfMemoryError错误,一般new出来的对象都是强引用。

// 如果是在Activity中创建则生命周期同Activity,如果在方法中创建则生命周期同方法。
// String str; 时就已经创建,new是进行实例化的(分配对象内存,并将该内存初始化为缺省值)。
String str = String.valueOf(Math.random());

软引用:当内存空间不足,GC会回收这些对象的内存,使用软引用构建敏感数据的缓存。

// 生命周期同强引用,只是当内存空间不足,GC会回收这些对象的内存。运行过程中可能会出现softref为null的情况。
SoftReference<String> softref = new SoftReference<String>(String.valueOf(Math.random()));

弱引用:在GC线程扫描内存区域的过程中,不管当前内存空间足够与否,都会回收内存,使用弱引用 构建非敏感数据的缓存。

虚引用:在任何时候都可能被垃圾回收,虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列联合使用,虚引用主要用来跟踪对象 被垃圾回收的活动。

3.Activity onTrimMemory()回调方法

@Override
public void onTrimMemory(int level) {
    super.onTrimMemory(level);
}

1)UI 不可见时释放资源:

在 onStop 中关闭网络连接、注销广播接收器、释放传感器等资源;

在 onTrimMemory() 回调方法中监听 TRIM_MEMORY_UI_HIDDEN 级别的信号,此时可在 Activity 中释放 UI 使用的资源,大符减少应用占用的内存,从而避免被系统清除出内存。

2)内存紧张时释放资源:

运行中的程序,如果内存紧张,会在 onTrimMemory() 回调方法中接收到以下级别的信号:
TRIM_MEMORY_RUNNING_MODERATE:系统可用内存较低,正在杀掉LRU缓存中的进程。你的进程正在运行,没有被杀掉的危险。
TRIM_MEMORY_RUNNING_LOW:系统可用内存更加紧张,程序虽然没有被杀死的危险,但是应该尽量释放一些资源,以提升系统的性能。
TRIM_MEMORY_RUNNING_CRITICAL:系统内存极度紧张,而LRU缓存中的大部分进程已被杀死,如果仍然无法获得足够的资源的话,接下来会清理掉LRU中的所有进程,并且开始杀死一些系统通常会保留的进程,比如后台运行的服务等。

当程序未在运行,保留在 LRU 缓存中时, onTrimMemory() 回调方法中接收到以下级别的信号:
TRIM_MEMORY_BACKGROUND:系统可用内存低,而你的程序处在LRU的顶端,不会被杀死,但是此时应释放一些程序再次打开时比较容易恢复的 UI 资源。
TRIM_MEMORY_MODERATE:系统可用内存低,程序处于LRU的中部位置,如果内存状态得不到缓解,程序会有被杀死的可能。
TRIM_MEMORY_COMPLETE:系统可用内存低,你的程序处于LRU尾部,如果系统仍然无法回收足够的内存资源,你的程序将首先被杀死,此时应释放无助于恢复程序状态的所有资源。

注意:该 API 在版本 14 中加入,旧版本的onLowMemory() 方法,大致相当于 onTrimMemory(int level) 中接收到 TRIM_MEMORY_COMPLETE 级别的信号。

尽管系统主要按照 LRU 中顺序来杀进程,不过系统也会考虑程序占用的内存多少,那些占用内存高的进程有更高的可能性会被首先杀死。

4.优化OOM问题的方案

1)注意临时 Bitmap 对象的及时回收
2)避免 Bitmap 的浪费
3)try catch 某些大内存分配的操作
4)加载 Bitmap(安卓手机上一般一个像素用4个字节来表示(ARGB)):缩放比例、解码格式、局部加载、软引用

作者:smartbetter 发表于2017/3/25 0:19:25 原文链接
阅读:277 评论:0 查看评论

一起Talk Android吧(第十五回:Java常用类之Arrays)

$
0
0

各位看官们,大家好,上一回中咱们说的是Java中异常的例子,这一回咱们说的例子是Java常用类之Arrays。闲话休提, 言归正转。让我们一起Talk Android吧!


看官们,我们在前面章回中提到数组类型,因为数组是类类型,我们没有介绍类相关的知识,所以当时一笔带过。不过,现在的情况不同了,我们已经掌握了类相关的内容,因此,我们在本章回中介绍数组以及常用的类Arrays。

1. 数组定义

看官们,我们先看看数组类型变量的定义方法,我们还是通过伪代码来演示:

type array[] = new type[size]; 

我们再和定义类对象的方法做个对比:

ClassName obj = new ClassName();

大家可以发现,它们是多么的类似呀!其实在Java中数组类型就是一种类类型,该类型中最常用的它的成员变量:length。通过该成员变量,可以得到数组的大小,也就是伪代码中size的值。另外,在定义数组时还有一种形式,我们通过伪代码来演示:

type[] array = new type[size];

该形式是Java中推荐的形式,因为通过这种形式可以一目了然地看出是数组类型。在对比一下前面的定义形式,我们就会发现,前面的形式是在沿用C语言中数组的定义形式,其实这是Java为了考虑编程人员的习惯而保留的。

2 .数组初始化

我们在定义数组类型的变量后,都会对该变量进行初始化操作。我们还是通过伪代码的形式来演示数组初始化。

type array[] = {val1,val2,...};

这种方式和C语言中数组的初始化方式相同,其优点是自动指定数组的大小,并且会对数组中的成员进行初始化操作。还有一种数组初始化方式:

type array[] = new type[size];

这种方式是典型的面向对象初始化方式,它会依据size的值来初始化数组的大小,并且把数组中每个元素初始化为类型的默认值。比如,type为int类型时,它的默认值为0,那么int类型的数组使用这种方式初始化时,数组中每个元素的初始值为0.

3. 数组类Arrays

看官们,为了方便我们对数组的操作,Java还提供了Arrays类,该类提供了许多静态方法,接下来我们介绍一些常用的静态方法。

Arrays.fill(type[] arg0, type arg1); //填充数组,使用arg1来填充数组arg0
Arrays.sort(type[] arg0);           //对数组成员排序,默认为升序。
Arrays.binarySearch(type[] arg0, type arg1); //在数组成员中查找某个变量,在数组arg0的成员中查找变量arg1

这些静态方法都是重载方法,可以对不同类型的数组进行操作。比如,我们把上面代码中的type替换为int,那么它就可以对int类型的数组进行相关操作,同理把type替换为double,那么它就可以对double类型的数组进行相关操作。这里的type不但可以是基本的类型,而且可以为类类型。

说了这么多,我们动手来实践一下。下面是程序的代码,请大家参考:

public class  ArrayExam{
    public static void main(String args[])
    {
        int [] array1 = {1,2,3,9,8,7} ;
        int array2[] = new int[6];

        System.out.println("arrar1 length:"+array1.length);
        System.out.println("arrar2 length:"+array2.length);

        System.out.print("array1 members: ");
        for(int i=0; i<array1.length; ++i)
            System.out.print(array1[i]+" ");
        System.out.println();

        System.out.print("array2 members: ");
        for(int i=0; i<array1.length; ++i)
            System.out.print(array2[i]+" ");
        System.out.println();

        Arrays.fill(array2, 6); 
        Arrays.sort(array1);

        System.out.print("array1 members: ");
        for(int i=0; i<array1.length; ++i)
            System.out.print(array1[i]+" ");
        System.out.println();

        System.out.print("array2 members: ");
        for(int i=0; i<array1.length; ++i)
            System.out.print(array2[i]+" ");
        System.out.println();

        if( 1== Arrays.binarySearch(array1,6) )
            System.out.println("6 is found in array1");
        else
            System.out.println("6 is not found in array1");
    }   
}

在上面的程序中,我们定义的int类型的数组,然后使用了两种初始化的数组的方法,并且把数组中每个成员的值都打印出来。接下来使用了Arrays类的静态方法对数组进行填充,排序和查找操作。下面是程序的运行结果,请大家参考:

arrar1 length:6
arrar2 length:6
array1 members: 1 2 3 9 8 7 
array2 members: 0 0 0 0 0 0 
array1 members: 1 2 3 7 8 9 
array2 members: 6 6 6 6 6 6 
6 is not found in array1

各位看官,关于Java常用类之Arrays的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!


作者:talk_8 发表于2017/3/25 10:59:37 原文链接
阅读:107 评论:0 查看评论

Recycleview上拉刷新_下拉加载_侧滑删除加强篇

$
0
0

总有那么几个二比产品,让你上拉刷新下拉加载之后,又想让你可以侧滑删除,我想静静.
产品狗虽然可恨,可是我们还是得乖乖的去实现,没办法,谁让我们是打工的,加油骚年们.

看下我们的效果


这里写图片描述

首先定义我们最重要的一个侧滑处理类,使用ViewDragHelper来处理的.不懂的可以看下弘扬大神的博客


Android ViewDragHelper完全解析 自定义ViewGroup神器

package yuan.kuo.yu.view;

import android.content.Context;
import android.graphics.Rect;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

/**
 * Created by yukuoyuan on 2017/3/10.
 * 这是一个可以侧滑菜单的条目的父布局
 */

public class SwipeRecycleviewItemLayout extends FrameLayout {


    private View menu;
    private View content;
    private final ViewDragHelper dragHelper;
    private boolean isOpen;
    private int currentState;

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param attrs   属性集合
     */
    public SwipeRecycleviewItemLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        /**
         * 初始化我们自定义处理触摸事件的方法
         */
        dragHelper = ViewDragHelper.create(this, rightCallback);
    }

    private ViewDragHelper.Callback rightCallback = new ViewDragHelper.Callback() {

        // 触摸到View的时候就会回调这个方法。
        // return true表示抓取这个View。
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return content == child;
        }

        /**
         * 重新处理子view的左侧
         * @param child
         * @param left
         * @param dx
         * @return
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return left > 0 ? 0 : left < -menu.getWidth() ? -menu.getWidth() : left;
        }

        /**
         * 当手指释放的时候回调
         * @param releasedChild
         * @param xvel
         * @param yvel
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {

            // x轴移动速度大于菜单一半,或者已经移动到菜单的一般之后,展开菜单
            if (isOpen) {
                if (xvel > menu.getWidth() || -content.getLeft() < menu.getWidth() / 2) {
                    close();
                } else {
                    open();
                }
            } else {
                if (-xvel > menu.getWidth() || -content.getLeft() > menu.getWidth() / 2) {
                    open();
                } else {
                    close();
                }
            }
        }

        /**
         * view 横向移动的范围
         * @param child
         * @return
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            return 1;
        }

        /**
         *view纵向移动的范围
         * @param child
         * @return
         */
        @Override
        public int getViewVerticalDragRange(View child) {
            return 1;
        }

        /**
         * 当ViewDragHelper状态发生变化的时候调用(IDLE,DRAGGING,SETTING[自动滚动时])
         * @param state
         */
        @Override
        public void onViewDragStateChanged(int state) {
            super.onViewDragStateChanged(state);
            currentState = state;
        }
    };

    /**
     * 处理触摸事件(交给draghelper去处理)
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        dragHelper.processTouchEvent(event);
        return true;
    }

    /**
     * 处理触摸事件
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return dragHelper.shouldInterceptTouchEvent(ev);
    }

    /**
     * 获取当前的状态
     *
     * @return
     */
    public int getState() {
        return currentState;
    }

    private Rect outRect = new Rect();

    public Rect getMenuRect() {
        menu.getHitRect(outRect);
        return outRect;
    }

    /**
     * 当绘制完毕调用的方法
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        menu = getChildAt(0);
        content = getChildAt(1);
    }

    /**
     * 这是一个关闭菜单的方法
     */
    public void close() {
        dragHelper.smoothSlideViewTo(content, 0, 0);
        isOpen = false;
        invalidate();
    }

    /**
     * 这是一个打开菜单的方法
     */
    public void open() {
        dragHelper.smoothSlideViewTo(content, -menu.getWidth(), 0);
        isOpen = true;
        invalidate();
    }

    /**
     * 计算滚动事件
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (dragHelper.continueSettling(true)) {
            invalidate();
        }
    }

    /**
     * 设置点击事件
     *
     * @param l
     */
    @Override
    public void setOnClickListener(OnClickListener l) {
        content.setOnClickListener(l);
    }

    public boolean isOpen() {
        return this.isOpen;
    }
}

然后定义我们的两个布局

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="?android:colorBackground"
    android:layout_height="60dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/item_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#49ff0000"
            android:gravity="center"
            android:text="小雨来了"
            android:textColor="@android:color/white"
            android:textSize="16sp" />
    </RelativeLayout>
</FrameLayout>

带侧滑删除的类型

<?xml version="1.0" encoding="utf-8"?>
<yuan.kuo.yu.view.SwipeRecycleviewItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:background="?android:colorBackground">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="right"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/delete"
            android:layout_width="60dp"
            android:layout_height="match_parent"
            android:background="#FF6A6A"
            android:gravity="center"
            android:text="删除"
            android:textColor="#fff"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/ok"
            android:layout_width="60dp"
            android:layout_height="match_parent"
            android:background="#e0e0e0"
            android:gravity="center"
            android:text="确定"
            android:textColor="#fff"
            android:textSize="16sp" />
    </LinearLayout>

    <include layout="@layout/item_content" />

</yuan.kuo.yu.view.SwipeRecycleviewItemLayout>

接下来看下我们的适配器

package cn.yu.yuan;

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by yukuo on 2016/4/30.
 */
public class DemoSwipeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List<SwipeDate> list = new ArrayList<>();

    public DemoSwipeAdapter(List<SwipeDate> list) {
        this.list = list;
    }

    public void addReFreshData() {
        notifyDataSetChanged();
    }

    public void addRLoadMOreData() {
        notifyDataSetChanged();
    }

    /**
     * 删除一个数据的方法
     *
     * @param position 索引
     */
    // TODO 一定要按照这个方式写,不然会crash,希望你有更好的解决方案
    public void removeData(int position) {
        list.remove(position);
        notifyItemRemoved(position + 1);
        if (position != list.size()) {
            if (position == 0) {
                notifyDataSetChanged();
            } else if (position == (list.size() - 1)) {
                notifyItemRangeChanged(position, 0);
            } else {
                notifyItemRangeChanged(position, list.size() - position);
            }
        }
    }

    @Override
    public int getItemViewType(int position) {
        return list.get(position).type;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view;
        if (viewType == 1) {
            view = View.inflate(parent.getContext(), R.layout.item_swipe_menu, null);
            return new MySwipeMenuHolder(view);
        } else {
            view = View.inflate(parent.getContext(), R.layout.item_content, null);
            return new MyHolder(view);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof MyHolder) {
            MyHolder myHolder = (MyHolder) holder;
            myHolder.item_content.setText(list.get(position).name);
        } else if (holder instanceof MySwipeMenuHolder) {
            MySwipeMenuHolder myHolder = (MySwipeMenuHolder) holder;
            myHolder.item_content.setText(list.get(position).name + "######" + position);
            myHolder.item_content.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(v.getContext(), list.get(position).name, Toast.LENGTH_SHORT).show();
                }
            });
            myHolder.delete.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    removeData(position);
                }
            });
        }
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    class MyHolder extends RecyclerView.ViewHolder {

        private final TextView item_content;

        public MyHolder(View itemView) {
            super(itemView);
            item_content = (TextView) itemView.findViewById(R.id.item_content);
        }
    }

    class MySwipeMenuHolder extends RecyclerView.ViewHolder {

        private final TextView delete;
        private final TextView ok;
        private final TextView item_content;

        public MySwipeMenuHolder(View itemView) {
            super(itemView);
            item_content = (TextView) itemView.findViewById(R.id.item_content);

            delete = (TextView) itemView.findViewById(R.id.delete);
            ok = (TextView) itemView.findViewById(R.id.ok);
        }
    }
}

ok,到此我们自定义的侧滑删除功能就实现了,其实并没有太多难点,主要是在viewDragHelper这个类里边

Demo地址
欢迎Fork和Star,如果有问题,记得留言反馈给我啊.谢谢

作者:EaskShark 发表于2017/3/25 16:23:37 原文链接
阅读:174 评论:0 查看评论

hostapd wpa_supplicant madwifi详细分析(十五)——supplicant扫描结果排序规则

$
0
0
int (*compar)(const void *, const void *) = wpa_scan_result_compar;

qsort(scan_res->res, scan_res->num, sizeof(struct wpa_scan_res *),compar);  // qsort函数介绍

static int wpa_scan_result_compar(const void *a, const void *b)  //这个函数用于scan result结果降序排序
{
#define MIN(a,b) a < b ? a : b
    struct wpa_scan_res **_wa = (void *) a;  //scan result的二级指针
    struct wpa_scan_res **_wb = (void *) b;
    struct wpa_scan_res *wa = *_wa; //scan result的一级指针
    struct wpa_scan_res *wb = *_wb;
    int wpa_a, wpa_b;
    int snr_a, snr_b, snr_a_full, snr_b_full;

    /* WPA/WPA2 support preferred */
    wpa_a = wpa_scan_get_vendor_ie(wa, WPA_IE_VENDOR_TYPE) != NULL ||
        wpa_scan_get_ie(wa, WLAN_EID_RSN) != NULL;
    wpa_b = wpa_scan_get_vendor_ie(wb, WPA_IE_VENDOR_TYPE) != NULL ||
        wpa_scan_get_ie(wb, WLAN_EID_RSN) != NULL;

    if (wpa_b && !wpa_a) //这里先比较是否有WPA_IE_VENDOR_TYPE和WLAN_EID_RSN这两个IE,谁有就谁排在前面,如果都没有,就不将他们看作排序因子
        return 1;  //返回正数表示升序,反会负表示逆序,返回0表示相等,所以正数时表示a比b小,负数时表示a比b大
    if (!wpa_b && wpa_a)
        return -1; //这里返回负数,表示将a放在b的后面

    /* privacy support preferred */
    if ((wa->caps & IEEE80211_CAP_PRIVACY) == 0 &&  //如果有标志位IEEE80211_CAP_PRIVACY的,就排在前面
        (wb->caps & IEEE80211_CAP_PRIVACY))
        return 1;
    if ((wa->caps & IEEE80211_CAP_PRIVACY) &&
        (wb->caps & IEEE80211_CAP_PRIVACY) == 0)
        return -1;

    if (wa->flags & wb->flags & WPA_SCAN_LEVEL_DBM) {
        snr_a_full = wa->snr;
        snr_a = MIN(wa->snr, GREAT_SNR); //如果信噪比大于30,那么信噪比snr_a的值就赋值为30,所以当wa->snr大于30的时候,snr_a_full是真实值,snr_a是阈值
        snr_b_full = wb->snr;
        snr_b = MIN(wb->snr, GREAT_SNR); //通过log可知,其实排序的时候没有用到信噪比这个因子
    } else {
        /* Level is not in dBm, so we can't calculate
         * SNR. Just use raw level (units unknown). */
        snr_a = snr_a_full = wa->level;   //level表示信号强度,一般用百分比表示,和信噪比的算法不一样
        snr_b = snr_b_full = wb->level;
    }

    /* if SNR is close, decide by max rate or frequency band */
    if ((snr_a && snr_b && abs(snr_b - snr_a) < 5) ||     //如果两者的level相差在5以内,就认为两者是相等的
        (wa->qual && wb->qual && abs(wb->qual - wa->qual) < 10)) {  //如果两者的信号质量相差在10以内,就认为两者是相等的
        if (wa->est_throughput != wb->est_throughput)  //Enrollment over Secure Transport, 我们没有用这种协议,所以两者都是0, 非排序因子
            return wb->est_throughput - wa->est_throughput;
        if (IS_5GHZ(wa->freq) ^ IS_5GHZ(wb->freq))   //如果两者都是5G,那就不用比较了,如果其中一个是5G,另外一个不是,那么5G的排在前面
            return IS_5GHZ(wa->freq) ? -1 : 1;
    }

    /* all things being equal, use SNR; if SNRs are
     * identical, use quality values since some drivers may only report
     * that value and leave the signal level zero */
    if (snr_b_full == snr_a_full)   //如果运行到这里还没有return,就表示前面的那些参数都是相等的,所以这里看看两者的真是信噪比是否相等
        return wb->qual - wa->qual; //如果相等,那就比信号质量,信号质量高的排在前面
    return snr_b_full - snr_a_full; //如果不相等,那就比真实信噪比,信噪比高的排在前面
#undef MIN
}



作者:lee244868149 发表于2017/3/25 18:15:59 原文链接
阅读:38 评论:0 查看评论

OpenGL ES总结(六)OpenGL ES中EGL

$
0
0

Agenda:

  • EGL是什么?
  • EGL数据类型
  • EGL在Android中应用
  • EGL的工作流程
  • GLSurfaceView与EGL区别
  • 简单Demo

EGL是什么?

EGL? is an interface between Khronos rendering APIs such as OpenGL ES or OpenVG and the underlying native platform window system.
It handles graphics context management, surface/buffer binding, and rendering synchronization and enables high-performance, accelerated, mixed-mode 2D and 3D rendering using other Khronos APIs.
这里写图片描述
那么什么是EGL?EGL是OpenGL ES和底层的native window system之间的接口,承上启下。

在Android上,EGL完善了OpenGL ES。利用类似eglCreateWindowSurface的EGL函数可以创建surface 用来render ,有了这个surface你就能往这个surface中利用OpenGL ES函数去画图了。OpenGL ES 本质上是一个图形渲染管线的状态机,而 EGL 则是用于监控这些状态以及维护 Frame buffer 和其他渲染 Surface 的外部层。下图是一个3d游戏典型的 EGL 系统布局图。


这里写图片描述

EGL数据类型

数据类型
EGLBoolean EGL_TRUE =1, EGL_FALSE=0
EGLint int 数据类型
EGLDisplay 系统显示 ID 或句柄
EGLConfig Surface 的 EGL 配置
EGLSurface 系统窗口或 frame buffer 句柄
EGLContext OpenGL ES 图形上下文
NativeDisplayType Native 系统显示类型
NativeWindowType Native 系统窗口缓存类型
NativePixmapType Native 系统 frame buffer

EGL在Android中应用

下面是开机动画BootAnimation中的实现,首先是创建本地环境,

status_t BootAnimation::readyToRun() {


    // 创建SurfaceControl
    // create the native surface
    sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
            dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);

    SurfaceComposerClient::openGlobalTransaction();
    // 设置layerstack
    control->setLayer(0x40000000);
    SurfaceComposerClient::closeGlobalTransaction();

    //获取Surface
    sp<Surface> s = control->getSurface();

    // initialize opengl and egl
    const EGLint attribs[] = {
            EGL_RED_SIZE,   8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE,  8,
            EGL_DEPTH_SIZE, 0,
            EGL_NONE
    };
    EGLint w, h, dummy;
    EGLint numConfigs;
    EGLConfig config;
    EGLSurface surface;
    EGLContext context;

    //调用eglGetDisplay
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); // 第一步

    eglInitialize(display, 0, 0); // 第二步
    eglChooseConfig(display, attribs, &config, 1, &numConfigs); // 第三步
    //调用eglCreateWindowSurface将Surface s转换为本地窗口,
    surface = eglCreateWindowSurface(display, config, s.get(), NULL); // 第四步
    context = eglCreateContext(display, config, NULL, NULL); // 第五步
    eglQuerySurface(display, surface, EGL_WIDTH, &w);
    eglQuerySurface(display, surface, EGL_HEIGHT, &h);

    //eglMakeCurrent后生成的surface就可以利用opengl画图了
    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
        return NO_INIT;

    return NO_ERROR;
}

egl创建好后,调用gl相关命令去画图,注意eglSwapBuffers(mDisplay, mSurface) 函数是非常重要的一个函数,会去触发queueBuffer和dequeueBuffer,图画就一帧一帧的画出来了。

bool BootAnimation::android()
{
    initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
    initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");

    // clear screen
    glShadeModel(GL_FLAT);
    glDisable(GL_DITHER);
    glDisable(GL_SCISSOR_TEST);
    glClearColor(0,0,0,1);
    glClear(GL_COLOR_BUFFER_BIT);
    //调用eglSwapBuffers会去触发queuebuffer,dequeuebuffer,
    //queuebuffer将画好的buffer交给surfaceflinger处理,
    //dequeuebuffer新创建一个buffer用来画图
    eglSwapBuffers(mDisplay, mSurface); // 第六步

    glEnable(GL_TEXTURE_2D);
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    const GLint xc = (mWidth  - mAndroid[0].w) / 2;
    const GLint yc = (mHeight - mAndroid[0].h) / 2;
    const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);

    glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
            updateRect.height());

    // Blend state
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    const nsecs_t startTime = systemTime();
    do {
        nsecs_t now = systemTime();
        double time = now - startTime;
        float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
        GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
        GLint x = xc - offset;

        glDisable(GL_SCISSOR_TEST);
        glClear(GL_COLOR_BUFFER_BIT);

        glEnable(GL_SCISSOR_TEST);
        glDisable(GL_BLEND);
        glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
        glDrawTexiOES(x,                 yc, 0, mAndroid[1].w, mAndroid[1].h);
        glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);

        glEnable(GL_BLEND);
        glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
        glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);

        EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
        if (res == EGL_FALSE)
            break;

        // 12fps: don't animate too fast to preserve CPU
        const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
        if (sleepTime > 0)
            usleep(sleepTime);

        checkExit();
    } while (!exitPending());

    glDeleteTextures(1, &mAndroid[0].name);
    glDeleteTextures(1, &mAndroid[1].name);
    return false;
}

EGL的工作流程

以上开机动画,可分如下几个阶段:

  • 1、 获取Display。
    Display代表显示。获得Display要调用EGLboolean eglGetDisplay(NativeDisplay dpy),参数一般为 EGL_DEFAULT_DISPLAY 。对应开机动画就是如下代码:

    //调用eglGetDisplay
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  • 2、 初始化egl。
    调用 EGLboolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor),该函数会进行一些内部初始化工作,并传回EGL版本号(major.minor)。对应开机动画就是如下代码:

    eglInitialize(display, 0, 0); 
  • 3、 选择Config。
    所为Config实际指的是FrameBuffer的参数。一般用EGLboolean eglChooseConfig(EGLDisplay dpy, const EGLint * attr_list, EGLConfig * config, EGLint config_size, EGLint num_config),其中attr_list是以EGL_NONE结束的参数数组,通常以id,value依次存放,对于个别标识性的属性可以只有 id,没有value。另一个办法是用EGLboolean eglGetConfigs(EGLDisplay dpy, EGLConfig config, EGLint config_size, EGLint *num_config) 来获得所有config。这两个函数都会返回不多于config_size个Config,结果保存在config[]中,系统的总Config个数保存 在num_config中。可以利用eglGetConfig()中间两个参数为0来查询系统支持的Config总个数。
    Config有众多的Attribute,这些Attribute决定FrameBuffer的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改。对应开机动画就是如下代码:

    eglChooseConfig(display, attribs, &config, 1, &numConfigs);
  • 4、 构造Surface。
    Surface实际上就是一个FrameBuffer,通过 EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig confg, NativeWindow win, EGLint *cfg_attr) 来创建一个可实际显示的Surface。系统通常还支持另外两种Surface:PixmapSurface和PBufferSurface,这两种都不是可显示的Surface,PixmapSurface是保存在系统内存中的位图,PBuffer则是保存在显存中的帧。
    Surface也有一些attribute,基本上都可以故名思意, EGL_HEIGHT EGL_WIDTH EGL_LARGEST_PBUFFER EGL_TEXTURE_FORMAT EGL_TEXTURE_TARGET EGL_MIPMAP_TEXTURE EGL_MIPMAP_LEVEL,通过eglSurfaceAttrib()设置、eglQuerySurface()读取。对应开机动画就是如下代码:

    surface = eglCreateWindowSurface(display, config, s.get(), NULL);
  • 5、 创建Context。
    OpenGL的pipeline从程序的角度看就是一个状态机,有当前的颜色、纹理坐标、变换矩阵、绚染模式等一大堆状态,这些状态作用于程序提交的顶点 坐标等图元从而形成帧缓冲内的像素。在OpenGL的编程接口中,Context就代表这个状态机,程序的主要工作就是向Context提供图元、设置状态,偶尔也从Context里获取一些信息。
    用EGLContext eglCreateContext(EGLDisplay dpy, EGLSurface write, EGLSurface read, EGLContext * share_list)来创建一个Context。对应开机动画就是如下代码:

    context = eglCreateContext(display, config, NULL, NULL);
  • 6、 绘制。
    应用程序通过OpenGL API进行绘制,一帧完成之后,调用eglSwapBuffers(EGLDisplay dpy, EGLContext ctx)来显示。对应开机动画就是如下代码:

    eglSwapBuffers(mDisplay, mSurface); /

EGL 官网详细讲述了Surface、Display、Context 概念。对应链接:

简单地说
(1)Display 是图形显示设备(显示屏)的抽象表示。大部分EGL函数都要带一个 Display 作为参数
(2)Context 是 OpenGL 状态机。Context 与 Surface 可以是一对一、多对一、一对多的关系
(3)Surface 是绘图缓冲,可以是 window、pbuffer、pixmap 三种类型之一
EGL 工作流程为:
(1)初始化
(2)配置
(3)创建Surface(绑定到平台Windowing系统)
(4)绑定Surface与Context
(5)Main Loop:渲染(OpenGL),交换离线缓冲(offline buffer)与显示缓冲
(6)释放资源

GLSurfaceView与EGL区别

  • GLSurfaceView隐藏了EGL操作及渲染线程的细节,并提供了生命周期回调方法。
  • EGL可以控制渲染循环,例如:可以没法控制帧速(fps),GLSurfaceView不能

简单Demo:

这里写图片描述这里写图片描述这里写图片描述

作者:hejjunlin 发表于2017/3/25 11:37:08 原文链接
阅读:18 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>