Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

Spring基础配置

$
0
0

从毕业到现在我一直从事Android开发,但是对JavaEE一直念念不忘,毕业校招的时候,一个礼拜拿了三个offer,岗位分别是Android、JavaEE和JavaSE,后来觉得Android比较简单,而且在当时也比较受欢迎,于是阴差阳错跳到了这个坑中,从此心里也一直有个结,很多时候都想重新拾起JavaEE,却由于工作原因难以实现,age也在慢慢变大。
上上个月CSDN专家奖励,我和梦鸽美女要了一本《JavaEE开发的颠覆者 Spring Boot实战》这本书,打算认真复习一下JavaEE,书到手之后一直束之高阁,现在到了年底,手头上的工作不是特别繁忙,于是决定年前花点时间重温一下JavaEE。
OK,以上算是缘起吧。
JavaEE的复习我觉得就顺着这本书的思路来复习,jsp、Servlet那些过于简单的就不再赘述,当然我由于长时间未从事专业的JavaEE开发,文中若有遗漏讹误欢迎专业人士拍砖。
OK,今天我想先来回顾下Spring基础配置。

Spring 配置问题

Spring的配置常见的有三种方式:

1.xml文件配置
2.注解配置
3.Java配置

一般来说,我们在选择配置方式的时候,应用的基础配置选择xml的方式来完成,业务配置使用注解来完成。

Spring框架本身有四大原则:

1.使用POJO进行轻量级和最小侵入式开发
2.通过依赖注入和基于接口编程实现松耦合
3.通过AOP和默认习惯进行声明式编程
4.使用AOP和模板减少模式化的代码

Spring中所有功能的设计和实现都是基于这四大原则的。这里关于AOP很多初学的小伙伴都不太理解,这里大家可以参考这篇回答什么是面向切面编程AOP?。懒得打开链接的直接看下图:
这里写图片描述

Spring常见注解

声明Bean的注解:

1.@Component组件,没有明确角色
2.@Service组件(使用在service层)
3.@Repository组件(使用在dao层)
4.@Controller组件(使用SpringMVC时使用)

注入Bean的注解

1.@Autowired(Spring提供的注解)
2.@Inject(JSR-330提供的注解,使用时要导入相应的jar包)
3.@Resource(JSR-250提供的注解)

注入Bean的这三个注解既可以直接用在属性上,也可以用在该属性的set方法上,一般建议使用前者,简洁高效。

OK,接下来我们来通过三个案例来看看三种不同的配置方式。

依赖注入

先来看第一种配置方式。

1.创建一个Maven项目,添加Spring依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>
    </dependencies>

2.编写功能类Bean

@Service
public class FunctionService {
    public String sayHello(String word) {
        return "你好" + word + "!";
    }
}

@Service注解表示这个类属于Service层,并且是由Spring管理的一个类。当然这里也可以选用@Repository、@Component、@Controller注解,效果相同。实际应用中根据相关类所处的位置选用合适的注解。

3.使用功能类Bean

@Service
public class UseFunctionService {
    @Autowired
    FunctionService functionService;
    public String sayHello(String word) {
        return functionService.sayHello(word);
    }
}

@Service注解含义不再多说,@Autowired注解表示将FunctionService的实体Bean注入到UseFunctionService中,这里也可以使用上文提到的@Inject或者@Resource,效果相同。

4.配置类

@Configuration
@ComponentScan("org.sang")
public class MyConfig {

}

@Configuration表示该类是一个配置类,@ComponentScan表示扫描org.sang包下所有使用了@Service、@Controller、@Component、@Repository注解的类,并将之注册为Bean。

5.使用

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        UseFunctionService bean = context.getBean(UseFunctionService.class);
        System.out.println(bean.sayHello("sang"));
        context.close();
    }
}

这里我们使用AnnotationConfigApplicationContext来作为Spring容器,它的参数就是我们之前创建的配置类,然后通过getBean方法来获取类的Bean,最后调用Bean中的方法。
输出结果如下:
这里写图片描述

源码地址:

本项目GitHub地址

Java配置

OK,上文我们说了依赖注入,接下来来看看Java配置,Java配置是Spring4.x推荐的配置方式,也算是一种比较潮的方式,在使用的过程中建议全局配置使用Java配置(数据库配置等),业务配置使用注解配置,不仅在JavaEE中,在Android开发中也有一个依赖注入框架Dagger2,也使用了类似的方式。OK,我们来看看Java配置的使用方式:

1.编写功能类Bean

public class FunctionService {
    public String sayHello(String word) {
        return "你好 " + word + " !";
    }
}

小伙伴们注意功能类Bean并没有使用@Service等注解。

2.使用功能类Bean

public class UseFunctionService {
    FunctionService functionService;

    public void setFunctionService(FunctionService functionService) {
        this.functionService = functionService;
    }

    public String sayHello(String word) {
        return functionService.sayHello(word);
    }
}

小伙伴们注意,这里的类也没有使用@Service注解,同时FunctionService属性也没有使用@Autowired注解。

3.编写配置文件

@Configuration
public class MyConfig {
    @Bean
    public FunctionService functionService() {
        return new FunctionService();
    }

    @Bean
    public UseFunctionService useFunctionService(FunctionService functionService) {
        UseFunctionService useFunctionService = new UseFunctionService();
        useFunctionService.setFunctionService(functionService);
        return useFunctionService;
    }
}

首先,@Configuration注解表示这是一个配置文件,同时这里没有使用包扫描注解,因为所有需要实例化的Bean都在类中有对应的方法去实现。这里我们使用了@Bean注解,该注解表示当前方法的返回值是一个Bean,Bean的名称就是方法名。在获取UseFunctionService的实例时,需要一个FunctionService的参数,在实例化UseFunctionService的时候,系统会自动查找它需要的Bean并作为参数传入,有木有觉得方便又熟悉呢?Android中的Dagger2不就是这样吗!

4.使用

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        UseFunctionService bean = context.getBean(UseFunctionService.class);
        System.out.println(bean.sayHello("sang"));
        context.close();
    }
}

使用比较简单,和前文基本一致,我们来看看运行结果:
这里写图片描述

本案例下载地址:

本案例GitHub地址

AOP

OK,看了前面两种不同配置方式之后,接下来我们再来看看AOP,面向切面编程,关于面向切面编程如果小伙伴有不理解的可以回顾上文给出的知乎上的答案。OK,那我们就来看看怎么样来玩一玩AOP。

1.添加Spring AOP支持及AspectJ依赖

和前文不同,使用AOP需要我们添加Spring框架对AOP的支持,同时需要添加AspectJ依赖,添加方式如下:

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>
        <!--AOP支持-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>
        <!--aspectj支持-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
    </dependencies>

2.编写拦截规则的注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Action {
    String name();
}

这就是一个普通的注解,@Target一行表示该注解将作用在方法上,@Retention一行表示该注解将一直保存到运行时,@Documented表示该注解将保存在javadoc中。

3.使用注解被拦截

@Service
public class AnnotationService {
    @Action(name = "add-1")
    public void add1() {
        System.out.println("add-1");
    }

    public void add2() {
        System.out.println("add-2");

    }

    @Action(name = "add-3")
    public void add3() {
        System.out.println("add-3");

    }

}

4.使用方法规则被拦截

@Service
public class MethodService {
    public void add() {
        System.out.println("method-add()");
    }
}

5.编写切面

@Aspect
@Component
public class LogAspect {
    @Pointcut("@annotation(org.sang.Action)")
    public void annotationPointCut() {

    }

    //after表示先执行方法,后拦截,before表示先拦截,后执行方法
//    @Before("annotationPointCut()")
    @After("annotationPointCut()")
    public void after(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Action action = method.getAnnotation(Action.class);
        System.out.println("注解式拦截:"+action.name());
    }

    /**
     * 第一个星号表示返回类型,×表示所有类型,注意第一个星号和包名之间有空格
     * 后面的星号表示任意字符
     * 两个点表示任意个参数
     *
     * 参考  http://www.cnblogs.com/yansum/p/5898412.html
     * @param joinPoint
     */
    @Before("execution(* org.sang.MethodService.*(..))")
    public void before(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("方法规则式拦截:"+method.getName());
    }
}

首先这里我们通过@AspectJ注解声明该类是一个切面,然后添加的@Component注解让这个切面成为Spring容器管理的Bean。@PointCut声明的是一个切点,这个切点是所有使用了@Action注解的方法。然后我们通过@After声明一个建言,使用@PointCut定义的切点。当然我们也可以通过方法规则来设置切点,这个详见execution。

6.配置类

@Configuration
@ComponentScan("org.sang")
@EnableAspectJAutoProxy
public class MyConfig {

}

这里的配置类就比较简单了,@EnableAspectJAutoProxy表示开启Spring对AspectJ代理的支持。

7.运行

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        AnnotationService annotationService = context.getBean(AnnotationService.class);
        annotationService.add1();
        annotationService.add2();
        annotationService.add3();
        MethodService methodService = context.getBean(MethodService.class);
        methodService.add();
        context.close();
    }
}

运行很简单,我们来看看运行结果:
这里写图片描述

本案例源码:
本案例GitHub地址

以上。

参考资料:

1.《JavaEE开发的颠覆者 Spring Boot实战》第一章
2.https://www.zhihu.com/question/24863332
3.http://www.cnblogs.com/yansum/p/5898412.html

作者:u012702547 发表于2016/12/13 11:59:33 原文链接
阅读:29 评论:0 查看评论

通俗易懂:一个三角恋让你明白什么叫网关、IP地址、DNS、MAC?

$
0
0

计算机主机网关的作用是什么?

假设你的名字叫小不点,你住在一个大院子里,你的邻居有很多小伙伴,在门口传达室还有个看大门的李大爷,李大爷就是你的网关。当你想跟院子里的某个小伙伴玩,只要你在院子里大喊一声他的名字,他听到了就会回应你,并且跑出来跟你玩。

经典:小明趣解什么叫网关?

但是你不被允许走出大门,你想与外界发生的一切联系,都必须由门口的李大爷(网关)用电话帮助你联系。假如你想找你的同学小明聊天,小明家住在很远的另外一个院子里,他家的院子里也有一个看门的王大爷(小明的网关)。但是你不知道小明家的电话号码,不过你的班主任老师有一份你们班全体同学的名单和电话号码对照表,你的老师就是你的DNS服务器。于是你在家里拨通了门口李大爷的电话,有了下面的对话:

小不点:李大爷,我想找班主任查一下小明的电话号码行吗?

李大爷:好,你等着。(接着李大爷给你的班主任挂了一个电话,问清楚了小明的电话)问到了,他家的号码是211.99.99.99

小不点:太好了!李大爷,我想找小明,你再帮我联系一下小明吧。

李大爷:没问题。(接着李大爷向电话局发出了请求接通小明家电话的请求,最后一关当然是被转接到了小明家那个院子的王大爷那里,然后王大爷把电话给转到小明家)

就这样你和小明取得了联系。

至于DHCP服务器嘛,可以这样比喻:

你家院子里的居民越来越多了,传达室李大爷那里的电话交换机已经不能满足这么多居民的需求了,所以只好采用了一种新技术叫做DHCP,居民们开机的时候随机得到一个电话号码,每一次得到的号码都可能会不同。

你家门口的李大爷:就是你的网关

你的班主任:就是你的DNS服务器

传达室的电话交换机:就是你的DHCP服务器

同上,李大爷和王大爷之间的对话就叫做路由。

另:如果还有个小朋友叫做小暗,他住的院子看门的是孙大爷,因为小暗的院子刚盖好,孙大爷刚来不久,他没有李大爷和王大爷办公室的电话(李大爷和王大爷当然也没有他的电话),这时会有两种情况:

1、居委会的赵大妈告诉了孙大爷关于李、王两位大爷的电话(同时赵大妈也告诉了李、王关于孙的电话),这就叫静态设定路由

2、赵大妈病了,孙大爷自己到处打电话,见人就说:“我是小暗他们院子管电话的”,结果被李、王二位听到了,就记在了他们的通讯录上,然后李、王就给孙大爷回了个电话说:“我是小明(小不点)他们院子管电话的”,这就叫动态设定路由

然后有一天小不点要找小暗,结果自然是小不点给李大爷打电话说:“大爷,我找小暗”(这里省略了李大爷去查小暗电话的过程,假设他知道小暗的电话),李大爷一找通讯录:“哦,小暗的院子的电话是孙大爷管着的,要找小暗自然先要通知孙大爷,我可以通知王大爷让他去找孙大爷,也可以自己直接找孙,那当然是自己直接找孙方便了”,于是李大爷给孙大爷打了电话,然后孙大爷又把电话转到了小暗家。

这里李大爷的通讯录叫做路由表。

李大爷选择是自己直接找孙大爷还是让王大爷帮忙转接叫做路由选择。

李大爷之所以选择直接找孙大爷是有依据的,因为他直接找孙大爷就能一步到位,如果要王大爷转接就需要两步才能完成,这里的“步”叫做“跳数”,李大爷的选择遵循的是最少步骤(跳数)原则(如果他不遵守这个原则,小不点可能就会多等些时间才能找到小暗,最终结果可能导致李大爷因工作不力被炒鱿鱼,这叫做“延时太长,选路原则不合理,换了一个路由器”)

当然,事情总是变化的,小不点和小明吵架了,这些天小不点老是给小暗打电话,小明心里想:“操,他是不是在说我坏话啊?”于是小明决定偷听小不点和小暗的通话,但是他又不能出院子,怎么办呢?小明做了这样一个决定:

首先他告诉自己院里管电话的王大爷说:“你给李大爷打个电话说小暗搬到咱们院子了,以后凡是打给他的电话我来接”,王大爷没反映过来(毕竟年纪大了啊!)就给李大爷打了电话,说:“现在我来管理小暗的电话了,孙已经不管了”,结果李大爷就把他的通讯录改了,这叫做路由欺骗。

以后小不点再找小暗,李大爷就转给王大爷了(其实应该转给孙大爷的),王大爷收到了这个电话就转给了小明(因为他之前已经和小明说好了),小明收到这个电话就假装小暗和小不点通信。因为小明作贼心虚,害怕明天小不点和小暗见面后当面问他,于是通信断了之后,又自己以小不点的名义给小暗通了个电话复述了一遍刚才的话,有这就叫数据窃听

再后来,小不点还是不断的和小暗联系,而零落了小明,小明心里嘀咕啊:“我不能总是这样以小暗的身份和小不点通话啊,外一有一天露馅了怎么办!”于是他想了一个更阴险的招数:“干脆我也不偷听你们的电话了,你小不点不是不给我打电话吗!那我让你也给小暗打不了,哼哼!”,他怎么做的呢?我们来看:

他联系了一批狐朋狗友,和他们串通好,每天固定一个时间大家一起给小暗院子传达室打电话,内容什么都有,只要传达室的孙爷爷接电话,就会听到“打雷啦,下雨收衣服啊!”、“人是人他妈生的,妖是妖他妈生的”、“你妈贵姓”等等,听的脑袋都大了,不听又不行,电话不停的响啊!终于有一天,孙爷爷忍不住了,大喊一声:“我受不了拉!!!!”,于是上吊自杀了!

这就是最简单的DDOS攻击,孙爷爷心理承受能力弱的现象叫做“数据报处理模块有BUG”,孙爷爷的自杀叫做“路由器瘫痪”。如果是我,就会微笑着和他们拉家常,例如告诉他们“我早就听了天气预报,衣服10分钟前已经收好了”或者“那你妈是人还是妖”或者“和你奶奶一个姓”等等,我这种健全的心理叫做“健壮的数据报处理,能够抵御任何攻击”

孙爷爷瘫了之后,小不点终于不再给小暗打电话了,因为无论他怎么打对方都是忙音,这种现象叫做“拒绝服务”,所以小明的做法还有一个名字叫做“拒绝服务攻击”。

小明终于安静了几天,...

几天后,小明的院子来了一个美丽的女孩,名字叫做小丽,小明很喜欢她(小小年纪玩什么早恋!)可是小丽有个很帅的男朋友,小明干瞪眼没办法。当然这里还是要遵循上面的原则:小丽是不能出院子的。那个男的想泡小丽自然只能打电话,于是小明又蠢蠢欲动了:

还记得王爷爷是院子的电话总管吗?他之所以能管理电话是因为他有一个通讯录,因为同一个院子可能有2个孩子都叫小明,靠名字无法区分,所以通讯录上每一行只有两项:

门牌电话

一号门1234567(这个是小明的)

二号门7654321(这个是小丽的)

......

王爷爷记性不好,但这总不会错了吧(同一个院子不会有2个“二号门”吧)?每次打电话人家都要说出要找的电话号码,然后通过通讯录去院子里面敲门,比如人家说我找“1234567”,于是王爷爷一比较,哦,是一号门的,他就去敲一号门“听电话”,如果是找“7654321”,那他就找二号门“听电话”。

这里的电话号码就是传说中的“IP地址”

这里的门牌号就是传说中的网卡的’MAC‘地址(每一块网卡的MAC地址都是不一样的,这是网卡的制造商写死在网卡的芯片中的)

小明心里想“奶奶的,老子泡不到你也别想泡”,于是他打起了王爷爷通讯录的主意,经过细心的观察,周密的准备,他终于发现王爷爷有尿频的毛病(毕竟是老人啊...),终于在一个月黑风高的白天,王爷爷去上厕所了,小明偷偷的摸进传达室,小心翼翼的改了王爷爷的通讯录......

过了几天,小丽的男朋友又给小丽打来了电话,对方报的电话是“7654321”,王爷爷一看通讯录,靠:

门牌电话

一号门1234567(这个是小明的)

一号门7654321(注意:这个原来是小丽的,但是被小明改了)

......

王爷爷不知道改了啊,于是就去找一号门的小明了,小明心里这个美啊,他以小丽父亲的口吻严厉的教训了那个男的和小丽之间不正当的男女关系,结果那个男的恭恭敬敬的挂了电话。当然小丽并不知道整个事情的发生...

这里小明的行为叫做“ARP欺骗”(因为在实际的网络上是通过发送ARP数据包来实现的,所以叫做“ARP欺骗”),王爷爷的通讯录叫做“ARP表”

这里要注意:王爷爷现在有两个通讯录了,一个是记录每个院子传达室电话的本本,叫做“路由表”,一个是现在说的记录院子里面详细信息的本本,叫做“ARP表”。

有句命言是“人们总是在追求完美的,尽管永远也做不到”(请记住这句话,因为这是一个大名人--也就是我,说的)

王爷爷的制度中有一条是这么写的“每个月要重新检查一下门牌号和电话的对应本(也就是ARP表)”,这个动作叫做“刷新ARP表”,每个月的时间限制叫做“刷新ARP表的周期”。这样小明为了让那个男的永远不能找到小丽,之后每个月都要偷偷改一次那个通讯录,不过这样也是不得不做的事啊!

补充一点,小明是很聪明的,如果通讯录(ARP表)被改成了这样:

门牌(MAC)电话(IP)

一号门1234567(这个是小明的)

二号门1234567(注意:这个被小明改了,但是他一时头晕改错了)

......

就会是计算机就会弹出一个对话框提示“出现重复的IP地址”,最终会导致王爷爷不知所措,于是通知一号门和二号门,你们的电话重复了。这样小丽就知道有人在破坏她的好事,这个现象叫做“骗局被揭穿了”

小不点知道了小明偷听他和小暗的电话,于是就和小暗约定好了密码。小不点在家里把要说的加密了之后告诉小暗。土豆-〉星期三,地瓜-〉请客,笨蛋-〉小不点家。于是小不点告诉小暗:土豆笨蛋地瓜。小明听了???不懂。。。。郁闷了。。。这是加密。

除此之外,小丽也知道了小明改他家的电话号码了。于是王爷爷就登门一个一个把电话和门牌号记下来。并且藏起来不允许外人修改,只能自己有钥匙(密码)。这是ip地址和MAC地址绑定。当有人改了电话号码的时候,就得找王爷爷改。麻烦是麻烦了,但是安全了。不过小明偷偷的把王爷爷的钥匙偷配了一把(盗窃密码成功),于是他还可以修改。这样么,就这样了。

作者:shenpengchao 发表于2016/12/14 17:23:20 原文链接
阅读:37 评论:0 查看评论

独立成分分析ICA系列2:概念、应用和估计原理.

$
0
0

1.概念

独立成分分析是从多元(多维)统计数据中寻找潜在因子或成分的一种方法.ICA与其它的方法重要的区别在于,它寻找满足统计独立和非高斯的成分。这里我们简要介绍ICA的基本概念、应用和估计原理。

1.1 多元数据的线性表示

统计数据处理及相关领域中的一个重要和需要长期研究的问题就是,寻找多元数据一个恰当的表示,使得人们可以获得给定数据的本质特征或者使得数据的结构可视化。
在神经计算领域中,这个基本问题也就是非监督学习(unsupervised learning)问题,因为这种表示是从数据本身学习得来的,即给定某个数据集作为神经网络的输入,在没有导师的情况下,通过神经网络学习得到数据的本质特征.这种数据表示问题是数据挖掘、特征提取和信号处理的核心问题。
为了更为清楚的说明这个问题,假设我们已经获得了m维的观测数据集xi(t),其中{i=1,…,m及t=1,…,T,这里t表示观测样本点的个数,m和t的数目可以非常之大.我们可以提出这样一个问题:通过怎样的一个映射,使得m维数据空间变换到另一个n维数据空间,使得变换后的变量能够揭示观测数据的某些信息,而这些信息是隐藏在原始的大规模数据中的.变换后的变量就是所谓的“因子”或者是“成分”,能够描述数据的本质特征.
在绝大多数的例子中,我们仅考虑线性变换,这样不仅使表示的解释简单,计算上也简单易行.这样,每一个成分yi可以表示为观测变量的线性组合:

其中Wij(i=1,…,n,j=1,…,m)是某些常系数,这些系数就定义了这个线性表示.因此可以看出,为了得到数据yi的线性表示,必须求出未知系数Wij.简单起见,这种数据的表示可写成矩阵的形式:

在统计的框架下,问题转化为通过成分yi的某些统计特性来求解系数矩阵W。
选择矩阵W的一个统计原理是限制成分yi的个数相当之少,也许只有1或2,寻找矩阵W以便成分尽可能的包含原始数据的信息.这导致统计技术如主成分分析(principal component analysis,PCA)因子分析(factor analysis,FA)的出现,它们是进行统计数据处理、特征提取、数据压缩等比较经典的技术。
寻找矩阵W的另一个统计原理是统计独立性:假设成分yi之间是统计独立的.这意味着其中一个成分没有受到另一个成分的任何影响,成分之间没有任何信息传递.在因子分析中,经常声称因子之间是统计独立的,这个说法只是部分正确,因为因子分析假设因子是服从高斯分布的,找到独立的方法相当容易(对于高斯分布的成分来说,不相关与独立是等价的)
而在现实世界中,数据通常并不服从高斯分布,假设成分服从高斯分布的方法在这种情况下是失效的.例如,许多真实世界的数据集是服从超高斯分布的(supergaussian).这意味着随机变量更经常的在零附近取值,与相同方差的高斯密度相比,超高斯分布在零点更尖!
事实上,存在度量随机变量y非高斯性的一个测度,峰度是度量非高斯性的一个比较传统的方法.y的蜂度kurt(y)在统计学上是用四阶统计量来表示的:

这个表达式可以进一步简化,假设随机变量的方差为单位方差,E{y^2}=1,则上述表达式就可以表示为:

通常在信号处理领域有如下约定:峰度值为正值的随机变量称为超高斯分布的随机变量(super-gaussian);峰度值为负值的随机变量称为亚高斯分布的随机变量(sub-gaussian);而高斯分布的随机变量的峰度值为零。形象的说,服从超高斯分布的随机变量比高斯分布更尖(spiky),拉普拉斯分布(Laplacian distribution)就是一
个典型的超高斯分布密度函数;服从贬高斯分布的随机变量比高斯分布更平(filat),均匀分布(Uniform distribution)就是一个典型的亚高斯分布密度函数。

2.盲源分离

2.1 未知信号的观测混合

考虑这样一种情况,由某些物体或源发出的一组信号,这些源可能包括,例如,发出电信号的不同脑区;在一个房间说话的几个人,发出语音信号;发出雷达信号的移动电话,等等.进一步假设有几个传感器或接收器,这些传感器放置在不同的位置上,这样,每个传感器纪录的是源信号带有不同权重的混合.前面所说的鸡尾酒会问题就符合这样的情况。
举一个具体的例子来说明盲源分离问题.用s1(t),s2(t)和s3(t)表示三个源信号,分别表示在t时刻的幅度,用x1(t),x2(t)和x3(t)表示三个观测信号.x(t)是s(t)的权重和:

其中常系数aij(i,j∈{1,2,3})表示混合权重,与源信号和传感器的距离有关.这些混合系数是未知的,既然我们在一般情况下很难知道混合系统的性质.源信号也是未知的,因为我们不能直接记录它们。

作为解释,见上图所示的波形.它们是某些源信号的线性混合。它们看上去完全是一些噪声信号,实际上,有些具有结构的源信号隐藏在这些观测信号中
我们想要做的就是从混合信号x1(t),x2(t)和x3(t)中找到源信号,这就是盲源分离问题(blind source separation,BSS).盲指的是源信号未知,混合系统未知(混合系数未知)
为了使问题简单化,假设由未知系数aij(i,j∈{1,2,3))所构成的系数矩阵是可逆的。则由它的逆矩阵w=(wij) 3x3可得到未知的源信号:

比较有趣的是,假设源信号之间是相互统计独立的,用独立成分分析就可以解决这个问题。 

2.2 基于独立的源分离

现在我们的问题是:如何估计如上方程中的系数wij?我们想要获得一般的方法,事实上,又回到刚开始的问题,即寻找多维数据的好的表示。因此,我们使用非常一般的统计性质.我们所得到的是混合信号值,需要找到矩阵W,使得表示由源信号si给出。
这个问题的一个令人感到吃惊的解是考虑信号的统计独立性。事实上,如果信号是非高斯的,我们可以决定系数wij以便信号:

是统计独立的。如果信号yl,y2和y3是独立的,则可认为它们就是所要求的源信号sl,s2和s3。
利用统计独立性这一信息,我们可以估计系数矩阵w.对于上图所示的观测信号,用ICA算法估计系数矩阵w,所得到的源信号如下图所示。这些用算法所估计出的信号实际上就是创建混合信号的源信号。

3.独立成分分析模型

我们首先给出标准的(即源信号的个数等于混合信号的个数)无噪声独立成分分析的线性模型.标准的线性独立成分分析模型的矩阵形式为X=AS;
其中随机向量X=(x1,x2,...,xn)表示观测数据或观测信号(observed data),随机向量S=(s1,s2,...,sn)表示源信号,称为独立成分(independent components),A称为nxn的混合矩阵(mixing matrix),在该模型中,X表示的是一个随机向量,x(t)表示随机向量X的一个样本.假设源信号是相互统计独立的。
这就是标准的独立成分分析模型,可以看作是一个生成模型(generativemodel),它的意思是说观测信号是通过源信号混合而生成的,在这个意义下,独立成分也称为隐含或潜在交量(hidden/latent , nariable ),也就是说这些独立成分是无法直接观测到的,另一方面,混合系数矩阵A也是未知的.独立成分分析的任务就是:在只知道观测信号X的T个样本x(1),…,x(T),且在源信号S和混合矩阵A未知的条件下,假设源信号si(0=1,…,n)之间是相互统计独立的,来求解混合矩阵A和源信号s

3.1 独立成分分析的假设条件

为了实现独立成分分析,必须给出几个假设条件:
a.各个成分之间是相互统计独立的.
这是独立成分分析的一个基本原则.比较有趣的是假设统计独立这个原则,就可以实现ICA模型的估计。这也是独立成分分析可以广泛应用在许多领域的一个重要原因.直观的说,如果任意的随机变量序列y1,y2,…,yn之间是相互统计独立的,则这就意味着从随机变量yi(i=1,…,n)的信息中不能得到随机变量yj(i≠j)的任何信息。随机变量之间的统计独立性可以通过概率密度函数来精确的刻画。如果用p(y1,…,yn)表示yi(0=1,…,n)的联合概率密度函数(joint probability density function),用pi(yi)表示随机变量yi(1=1,…,n)的边际概率密度
函数(marginal probability density function),那么我们说yi(i=1,…,他)是相互统计独立的,如果满足:

b.独立成分是服从非高斯分布的
直观的说,高斯信息太过于“简单”,真正有意义的信息是服从非高斯分布的信息。高斯随机变量的高阶累积量为零,而对于独立成分分析而言,高阶信息是实现独立成分分析的本质因素,这也是独立成分分析和其它数据处理方法诸如主成分分析和因子分析的本质区别.况且,真实世界的许多数据是服从非高斯分布的.事实
上,标准的独立成分分析也可以考虑为非高斯因子分析(nongaussian factor analysis)。Comon,Hyviirinen详细说明了独立成分必须是非高斯的原因,一般的,在标准的独立成分分析中最多只允许有一个成分服从高斯分布如果独立成分中有两个以上的高斯成分,用标准的独立成分分析来处理这样的数据是不可能的标准的独立成分分析只挖掘数据的非高斯结构,在某些思想上与投影寻踪(projection pursuit)相似,如果需要进一步挖掘数据的其它信息,应发展新的思想来解决更为复杂的情况.有一些学者致力于这方面的研究,例如利用数据或信号的时间结构和相关的信息来完成这样的任务。
c.假设混合矩阵是方阵
事实上,对于标准的独立成分分析而言,还有一个假设就是混合矩阵为方阵.也就是说,独立成分的个数等于观测混合信号的个数,进一步假设混合矩阵A是可逆的,这可以使得计算简单化,求混合矩阵A就等价于求它的逆矩阵w,则源信号就可以很容易的得到:S=(W-1)X。
对于标准的独立成分分析而言,当给定上述的三个条件时,独立成分分析就是可实现的,也就是说混合矩阵和独立成分是可以求解的。

3.2 独立成分分析无法确定的因素

从独立成分分析的模型可以看出下列因素是很难确定的:
a.不能确定独立成分的方差、能量
事实上,原因是很明显的,由于混合矩阵和独立成分都是未知的,如果对独立成分乘上某个标量ai≠0,或同时对混合矩阵相应的除以一个相同的标量,则不影响混合信号的值。
因此,在独立成分分析算法中,可以固定独立成分的方差,由于独立成分是随机变量,则最自然的方法就是假设独立成分具有单位方差。
b.不能确定独立成分的顺序
在独立成分分析的绝大多数应用中,这两个不确定性并不是十分重要的,用ICA算法所得到的解能够满足相当多的实际应用,所得到的源信号的幅度和排序对于通常所考虑的问题影响不大.所以我们可以说独立成分分析所求得的解是波形保持解.在某些特殊的应用中,我们需要确定输出成分的顺序,可以通过某些统计量的大小来规定输出独立成分的顺序,这样的规定,使得这个问题转化为一个具有某些约束的问题,即标准的ICA问题转化为约束ICA问题。

3.3数据的中心化

不失一般性,我们可以假设混合变量和独立成分是零均值的.这个假设在相当程度上简化了算法,如无特殊说明,假设混合变量和独立成分都是零均值的.
如果零均值并不成立,我们可以通过预处理来达到这个条件.一般的,我们使用中心化观测变量这一技术,即减去样本均值

3.4 不相关和白化

独立和不相关(uncorrelated)是紧密相关的概念,因此,可以设想使用估计不相关变量的方法来同样估计独立成分,这样的典型方法为白化(whitening)或球化(sphering),通常由主成分分析(principal component analysis)来进行.但用这样的方法来估计独立成分通常是不可行的,一般的,白化是以独立成分分析的预处理技术身份出现的。
不相关是独立的较弱形式,两个随机变量y1,y2是不相关的,那么它们的协方差是零:

如果随机变量是零均值的,协方差化为相关coor(y1,y2)=E{y1,y2),不相关相当于零相关.
如果随机变量是独立的,它们即是不相关的.这是因为两个随机变量y1和y2是独立的,那么对于任意两个函数h1和h2,我们有:

这就是我们常说的,独立意味着不相关,而不相关并不意味着独立
比不相关稍强的概念是白化.白化的随机向量y与它的各分量是不相关的,并且具有单位方差.换句话说,随机向量Y的协方差矩阵是单位阵

白化意味着我们将观测数据向量x进行线性变换,使得新向量

是白化的随机向量.白化有时称为球化.
白化变换总是可行的.白化的一个流行方法是协方差矩阵的特征值分解(EVD)

这里,E是E(XXT)的特征向量组成的正交矩阵,D是它的特征值组成的对角矩阵.这样,白化可以通过白化矩阵来完成!
作者:shenziheng1 发表于2016/12/14 17:24:16 原文链接
阅读:22 评论:0 查看评论

Paper Reading:Spatial Transformer Networks(with code explanation)

$
0
0

原文:Spatial Transformer Networks

前言:卷积神经网络(CNN)已经可以构建出一个强大的分类模型,但它仍然缺乏能力来应对输入数据的空间变换,比如:平移、缩放、旋转,尽管convolutional层和pooling层在一定程度上解决了平移和缩放的问题,但很多时候面对旋转、大尺度的缩放等情况时仍然无能为力。这篇文章提出了一种叫做空间变换网络(Spatial Transform Networks, STN)的模型,它能自动学习变换参数,对上一层的图像进行处理,在一定程度上自适应实现这些变换,从而实现对空间变换的不变性。当输入数据的空间变换差异较大时,STN可以将输入的数据进行“矫正”,进而提高分类的准确性。

前导知识:

1:图像的二维仿射变换

图像的二维仿射变换包括图像的平移(Translation)、缩放(Scale)、旋转(Rotation)等变换,实现这些变换只需要一个2*3维的变换矩阵。

(1)平移变换(Translation)

  平移变换完成的操作是:
   x=x+Tx
   y=y+Ty

  写成矩阵形式为:
这里写图片描述

(2)缩放(Scale)

  x=xSx
  y=ySy

  写成矩阵形式为:
这里写图片描述

(3)旋转

  旋转的矩阵形式为:

这里写图片描述

  由于图像的坐标系的原点在左上角,所以这里会做一个Normalization,把坐标归一化到[-1,1],这样就是绕图像的中心进行旋转了。不然的话会绕图片的左上角进行旋转。

2、双线性插值

  如果把输入图像的一部分映射的输出图像,那么输出图像V中的每一个点,在输入图像U中不一定对应整数点,比如输入图像中的5*5的区域对应输出图像10*10的区域,那么输出图像中很多点对应的输入图像的坐标并不是整数点。这个时候要使用插值法来填充这些像素。
  论文中提到了2种方法来填充这些像素,在代码实现中使用了双线性插值的方法。
  如下图,中间点P是待插值的点,其中f(x)在我们这里是位于点x处的像素值。
这里写图片描述

STN网络详解

1、正向计算过程:

网络结构:
这里写图片描述
  这幅图是论文中出现的网络结构,可以看出,STN网络为主网络的一个分支,整个STN分支分为三个部分,分别为:

1、Localisation Network
  这部分主要是通过一个子网络生成变换参数θ,即2*3维的变换矩阵。子网络主要是由全连接层或者卷积层组成。

2、 Parameterised Sampling Grid
  该部分将输入坐标转换为输出坐标。假设输入U每个像素的坐标为(xsi,ysi),输出V的每个像素坐标为(xti,yti),空间变换函数Tθ为仿射变换函数,那么 (xsi,ysi) 和 (xti,yti) 的对应关系可以写为:
这里写图片描述

  这里有一点需要注意,论文中用的是输入图像的坐标(xsi,ysi) =θ* (xti,yti)(输出图像的坐标),即根据输出图像V中一点的坐标找该点在输入图像U中的对应坐标,这正好是我们前边仿射变换的逆过程。

3、Differentiable Image Sampling

  最后,就可以采用不同的插值方法将输入图像映射到输出图像。入下式 k为不同的sampling kernel。
这里写图片描述
  若采用双线性插值的方法,则插值公式为
这里写图片描述

2、反向传播过程:

  该层的输入梯度为lossV,我们需要计算的包括loss对输入U的导数,以及loss对仿射矩阵θ的导数。
  前边的双线性插值的公式如下,在后面的计算过程中会用到:

这里写图片描述

1、loss对输入U的导数

  根据双线性插值的公式,对U求导得:
这里写图片描述

  该导数的值就是双线性插值的系数。然后根据链式求导法则即可得出loss对U的导数。

2、loss对仿射矩阵θ的导数
  根据链式求导法则这里写图片描述 ,所以我们需要求Vxxθ
  文中已经给出:
这里写图片描述

  所以双线性插值只需对周围4个点进行计算即可,其他的点均为0。Vy的求法也一样。

  而对于xθyθ,可以根据下面公式:
这里写图片描述
  很容易求到:
这里写图片描述
  根据链式法则即可求出Vθ

  至此,整个stn的推导便结束了。

代码分析

LayerSetUp:

void SpatialTransformerLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {

    string prefix = "\t\tSpatial Transformer Layer:: LayerSetUp: \t";

    if(this->layer_param_.st_param().transform_type() == "affine") {
        transform_type_ = "affine";
    } else {
        CHECK(false) << prefix << "Transformation type only supports affine now!" << std::endl;
    }

    if(this->layer_param_.st_param().sampler_type() == "bilinear") {
        sampler_type_ = "bilinear";
    } else {
        CHECK(false) << prefix << "Sampler type only supports bilinear now!" << std::endl;
    }

    if(this->layer_param_.st_param().to_compute_du()) {
        to_compute_dU_ = true;
    }

    std::cout<<prefix<<"Getting output_H_ and output_W_"<<std::endl;

    output_H_ = bottom[0]->shape(2);
    if(this->layer_param_.st_param().has_output_h()) {
        output_H_ = this->layer_param_.st_param().output_h();
    }
    output_W_ = bottom[0]->shape(3);
    if(this->layer_param_.st_param().has_output_w()) {
        output_W_ = this->layer_param_.st_param().output_w();
    }

    std::cout<<prefix<<"output_H_ = "<<output_H_<<", output_W_ = "<<output_W_<<std::endl;

    std::cout<<prefix<<"Getting pre-defined parameters"<<std::endl;

    is_pre_defined_theta[0] = false;
    if(this->layer_param_.st_param().has_theta_1_1()) {
        is_pre_defined_theta[0] = true;
        ++ pre_defined_count;
        pre_defined_theta[0] = this->layer_param_.st_param().theta_1_1();
        std::cout<<prefix<<"Getting pre-defined theta[1][1] = "<<pre_defined_theta[0]<<std::endl;
    }

    is_pre_defined_theta[1] = false;
    if(this->layer_param_.st_param().has_theta_1_2()) {
        is_pre_defined_theta[1] = true;
        ++ pre_defined_count;
        pre_defined_theta[1] = this->layer_param_.st_param().theta_1_2();
        std::cout<<prefix<<"Getting pre-defined theta[1][2] = "<<pre_defined_theta[1]<<std::endl;
    }

    is_pre_defined_theta[2] = false;
    if(this->layer_param_.st_param().has_theta_1_3()) {
        is_pre_defined_theta[2] = true;
        ++ pre_defined_count;
        pre_defined_theta[2] = this->layer_param_.st_param().theta_1_3();
        std::cout<<prefix<<"Getting pre-defined theta[1][3] = "<<pre_defined_theta[2]<<std::endl;
    }

    is_pre_defined_theta[3] = false;
    if(this->layer_param_.st_param().has_theta_2_1()) {
        is_pre_defined_theta[3] = true;
        ++ pre_defined_count;
        pre_defined_theta[3] = this->layer_param_.st_param().theta_2_1();
        std::cout<<prefix<<"Getting pre-defined theta[2][1] = "<<pre_defined_theta[3]<<std::endl;
    }

    is_pre_defined_theta[4] = false;
    if(this->layer_param_.st_param().has_theta_2_2()) {
        is_pre_defined_theta[4] = true;
        ++ pre_defined_count;
        pre_defined_theta[4] = this->layer_param_.st_param().theta_2_2();
        std::cout<<prefix<<"Getting pre-defined theta[2][2] = "<<pre_defined_theta[4]<<std::endl;
    }

    is_pre_defined_theta[5] = false;
    if(this->layer_param_.st_param().has_theta_2_3()) {
        is_pre_defined_theta[5] = true;
        ++ pre_defined_count;
        pre_defined_theta[5] = this->layer_param_.st_param().theta_2_3();
        std::cout<<prefix<<"Getting pre-defined theta[2][3] = "<<pre_defined_theta[5]<<std::endl;
    }

    // check the validation for the parameter theta
    CHECK(bottom[1]->count(1) + pre_defined_count == 6) << "The dimension of theta is not six!"
            << " Only " << bottom[1]->count(1) << " + " << pre_defined_count << std::endl;
    CHECK(bottom[1]->shape(0) == bottom[0]->shape(0)) << "The first dimension of theta and " <<
            "U should be the same" << std::endl;

    // initialize the matrix for output grid
    std::cout<<prefix<<"Initializing the matrix for output grid"<<std::endl;

    vector<int> shape_output(2);
    shape_output[0] = output_H_ * output_W_; shape_output[1] = 3;
    output_grid.Reshape(shape_output);

    Dtype* data = output_grid.mutable_cpu_data();
    //这里初始化了保存输出V的坐标的矩阵,并做了Normalization,将坐标归一化到了[-1,1],每个点的坐标为[x,y,1]
    for(int i=0; i<output_H_ * output_W_; ++i) {
        data[3 * i] = (i / output_W_) * 1.0 / output_H_ * 2 - 1;
        data[3 * i + 1] = (i % output_W_) * 1.0 / output_W_ * 2 - 1;
        data[3 * i + 2] = 1;
    }

    // initialize the matrix for input grid
    std::cout<<prefix<<"Initializing the matrix for input grid"<<std::endl;

    vector<int> shape_input(3);
    shape_input[0] = bottom[1]->shape(0); shape_input[1] = output_H_ * output_W_; shape_input[2] = 2;
    input_grid.Reshape(shape_input);

    std::cout<<prefix<<"Initialization finished."<<std::endl;
}

Forward:

void SpatialTransformerLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
    const vector<Blob<Dtype>*>& top) {

    string prefix = "\t\tSpatial Transformer Layer:: Forward_cpu: \t";

    // CHECK(false) << "Don't use the CPU implementation! If you really want to, delete the" <<
            // " CHECK in st_layer.cpp file. Line number: 240-241." << std::endl;

    if(global_debug) std::cout<<prefix<<"Starting!"<<std::endl;
    //U为输入的图像(可以为整个网络的输入,也可以为某一层的feature map)
    //theta为前边计算得到的仿射矩阵
    const Dtype* U = bottom[0]->cpu_data();
    const Dtype* theta = bottom[1]->cpu_data();
    const Dtype* output_grid_data = output_grid.cpu_data();

    Dtype* input_grid_data = input_grid.mutable_cpu_data();
    Dtype* V = top[0]->mutable_cpu_data();

    caffe_set(input_grid.count(), (Dtype)0, input_grid_data);
    caffe_set(top[0]->count(), (Dtype)0, V);

    // for each input
    for(int i = 0; i < N; ++i) {

        Dtype* coordinates = input_grid_data + (output_H_ * output_W_ * 2) * i;
        //计算输出V中的每一个点的坐标在输入U中对应的坐标
        caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, output_H_ * output_W_, 2, 3, (Dtype)1.,
              output_grid_data, theta + 6 * i, (Dtype)0., coordinates);

        int row_idx; Dtype px, py;

        for(int j = 0; j < C; ++j)
            for(int s = 0; s < output_H_; ++s)
                for(int t = 0; t < output_W_; ++t) {

                    row_idx = output_W_ * s + t;

                    px = coordinates[row_idx * 2];
                    py = coordinates[row_idx * 2 + 1];
                    //该函数通过双线性插值得到V中每个点的像素值,具体实现见下一段代码
                    V[top[0]->offset(i, j, s, t)] = transform_forward_cpu(
                            U + bottom[0]->offset(i, j, 0, 0), px, py);
                }
    }

    if(global_debug) std::cout<<prefix<<"Finished."<<std::endl;
}

transform_forward_cpu:

Dtype SpatialTransformerLayer<Dtype>::transform_forward_cpu(const Dtype* pic, Dtype px, Dtype py) {

    bool debug = false;

    string prefix = "\t\tSpatial Transformer Layer:: transform_forward_cpu: \t";

    if(debug) std::cout<<prefix<<"Starting!\t"<<std::endl;
    if(debug) std::cout<<prefix<<"(px, py) = ("<<px<<", "<<py<<")"<<std::endl;

    Dtype res = (Dtype)0.;
    //Normalization到[-1,1]区间的坐标值还原到原来区间
    Dtype x = (px + 1) / 2 * H; 
    Dtype y = (py + 1) / 2 * W;

    if(debug) std::cout<<prefix<<"(x, y) = ("<<x<<", "<<y<<")"<<std::endl;

    int m, n; Dtype w;
    //下面4部分分别为双线性插值对应的4个点,res为插值得到的像素值
    m = floor(x); n = floor(y); w = 0;
    if(debug) std::cout<<prefix<<"1: (m, n) = ("<<m<<", "<<n<<")"<<std::endl;

    if(m >= 0 && m < H && n >= 0 && n < W) {
        w = max(0, 1 - abs(x - m)) * max(0, 1 - abs(y - n));
        res += w * pic[m * W + n];
        if(debug) std::cout<<prefix<<"w = "<<w<<", pic[m, n] = "<<pic[m * W + n]<<std::endl;
    }

    m = floor(x) + 1; n = floor(y); w = 0;
    if(debug) std::cout<<prefix<<"2: (m, n) = ("<<m<<", "<<n<<")"<<std::endl;

    if(m >= 0 && m < H && n >= 0 && n < W) {
        w = max(0, 1 - abs(x - m)) * max(0, 1 - abs(y - n));
        res += w * pic[m * W + n];
        if(debug) std::cout<<prefix<<"w = "<<w<<", pic[m, n] = "<<pic[m * W + n]<<std::endl;
    }

    m = floor(x); n = floor(y) + 1; w = 0;
    if(debug) std::cout<<prefix<<"3: (m, n) = ("<<m<<", "<<n<<")"<<std::endl;

    if(m >= 0 && m < H && n >= 0 && n < W) {
        w = max(0, 1 - abs(x - m)) * max(0, 1 - abs(y - n));
        res += w * pic[m * W + n];
        if(debug) std::cout<<prefix<<"w = "<<w<<", pic[m, n] = "<<pic[m * W + n]<<std::endl;
    }

    m = floor(x) + 1; n = floor(y) + 1; w = 0;
    if(debug) std::cout<<prefix<<"4: (m, n) = ("<<m<<", "<<n<<")"<<std::endl;

    if(m >= 0 && m < H && n >= 0 && n < W) {
        w = max(0, 1 - abs(x - m)) * max(0, 1 - abs(y - n));
        res += w * pic[m * W + n];
        if(debug) std::cout<<prefix<<"w = "<<w<<", pic[m, n] = "<<pic[m * W + n]<<std::endl;
    }

    if(debug) std::cout<<prefix<<"Finished. \tres = "<<res<<std::endl;

    return res;
}

backward:

void SpatialTransformerLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
    const vector<bool>& propagate_down,
    const vector<Blob<Dtype>*>& bottom) {

        string prefix = "\t\tSpatial Transformer Layer:: Backward_cpu: \t";

        CHECK(false) << "Don't use the CPU implementation! If you really want to, delete the" <<
                " CHECK in st_layer.cpp file. Line number: 420-421." << std::endl;

        if(global_debug) std::cout<<prefix<<"Starting!"<<std::endl;

        const Dtype* dV = top[0]->cpu_diff();
        const Dtype* input_grid_data = input_grid.cpu_data();
        const Dtype* U = bottom[0]->cpu_data();

        Dtype* dU = bottom[0]->mutable_cpu_diff();
        Dtype* dTheta = bottom[1]->mutable_cpu_diff();
        Dtype* input_grid_diff = input_grid.mutable_cpu_diff();

        caffe_set(bottom[0]->count(), (Dtype)0, dU);
        caffe_set(bottom[1]->count(), (Dtype)0, dTheta);
        caffe_set(input_grid.count(), (Dtype)0, input_grid_diff);

        for(int i = 0; i < N; ++i) {

            const Dtype* coordinates = input_grid_data + (output_H_ * output_W_ * 2) * i;
            Dtype* coordinates_diff = input_grid_diff + (output_H_ * output_W_ * 2) * i;

            int row_idx; Dtype px, py, dpx, dpy, delta_dpx, delta_dpy;

            for(int s = 0; s < output_H_; ++s)
                for(int t = 0; t < output_W_; ++t) {

                    row_idx = output_W_ * s + t;

                    px = coordinates[row_idx * 2];
                    py = coordinates[row_idx * 2 + 1];

                    for(int j = 0; j < C; ++j) {

                        delta_dpx = delta_dpy = (Dtype)0.;
                        //计算dx和dU,具体实现见下一段代码
                        transform_backward_cpu(dV[top[0]->offset(i, j, s, t)], U + bottom[0]->offset(i, j, 0, 0),
                                px, py, dU + bottom[0]->offset(i, j, 0, 0), delta_dpx, delta_dpy);

                        coordinates_diff[row_idx * 2] += delta_dpx;
                        coordinates_diff[row_idx * 2 + 1] += delta_dpy;
                    }

                    dpx = coordinates_diff[row_idx * 2];
                    dpy = coordinates_diff[row_idx * 2 + 1];

                    //这里对应了上面求dx/dTheta和dx/dTheta的部分
                    dTheta[6 * i] += dpx * (s * 1.0 / output_H_ * 2 - 1);
                    dTheta[6 * i + 1] += dpx * (t * 1.0 / output_W_ * 2 - 1);
                    dTheta[6 * i + 2] += dpx;
                    dTheta[6 * i + 3] += dpy * (s * 1.0 / output_H_ * 2 - 1);
                    dTheta[6 * i + 4] += dpy * (t * 1.0 / output_W_ * 2 - 1);
                    dTheta[6 * i + 5] += dpy;
                }
        }

        if(global_debug) std::cout<<prefix<<"Finished."<<std::endl;
}

transform_backward_cpu:

void SpatialTransformerLayer<Dtype>::transform_backward_cpu(Dtype dV, const Dtype* U, const Dtype px,
        const Dtype py, Dtype* dU, Dtype& dpx, Dtype& dpy) {

    bool debug = false;

    string prefix = "\t\tSpatial Transformer Layer:: transform_backward_cpu: \t";

    if(debug) std::cout<<prefix<<"Starting!"<<std::endl;

    //Normalization到[-1,1]区间的坐标值还原到原来区间
    Dtype x = (px + 1) / 2 * H; 
    Dtype y = (py + 1) / 2 * W;
    if(debug) std::cout<<prefix<<"(x, y) = ("<<x<<", "<<y<<")"<<std::endl;

    int m, n; Dtype w;

    //下面4部分也对应了双线性插值的那4个点
    m = floor(x); n = floor(y); w = 0;
    if(debug) std::cout<<prefix<<"(m, n) = ("<<m<<", "<<n<<")"<<std::endl;

    if(m >= 0 && m < H && n >= 0 && n < W) {
        w = max(0, 1 - abs(x - m)) * max(0, 1 - abs(y - n));

        //U中的(m,n)是V中(s,t)对应的点的坐标,则梯度向前传播
        dU[m * W + n] += w * dV;

        //对应上面的dV/dx和dV/dy
        if(abs(x - m) < 1) {
            if(m >= x) {
                dpx += max(0, 1 - abs(y - n)) * U[m * W + n] * dV * H / 2;
                if(debug) std::cout<<prefix<<"dpx += "<<max(0, 1 - abs(y - n))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<H / 2<<std::endl;
            } else {
                dpx -= max(0, 1 - abs(y - n)) * U[m * W + n] * dV * H / 2;
                if(debug) std::cout<<prefix<<"dpx -= "<<max(0, 1 - abs(y - n))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<H / 2<<std::endl;
            }
        }

        if(abs(y - n) < 1) {
            if(n >= y) {
                dpy += max(0, 1 - abs(x - m)) * U[m * W + n] * dV * W / 2;
                if(debug) std::cout<<prefix<<"dpy += "<<max(0, 1 - abs(x - m))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<W / 2<<std::endl;
            } else {
                dpy -= max(0, 1 - abs(x - m)) * U[m * W + n] * dV * W / 2;
                if(debug) std::cout<<prefix<<"dpy -= "<<max(0, 1 - abs(x - m))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<W / 2<<std::endl;
            }
        }
    }

    m = floor(x) + 1; n = floor(y); w = 0;
    if(debug) std::cout<<prefix<<"(m, n) = ("<<m<<", "<<n<<")"<<std::endl;

    if(m >= 0 && m < H && n >= 0 && n < W) {
        w = max(0, 1 - abs(x - m)) * max(0, 1 - abs(y - n));

        dU[m * W + n] += w * dV;

        if(abs(x - m) < 1) {
            if(m >= x) {
                dpx += max(0, 1 - abs(y - n)) * U[m * W + n] * dV * H / 2;
                if(debug) std::cout<<prefix<<"dpx += "<<max(0, 1 - abs(y - n))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<H / 2<<std::endl;
            } else {
                dpx -= max(0, 1 - abs(y - n)) * U[m * W + n] * dV * H / 2;
                if(debug) std::cout<<prefix<<"dpx -= "<<max(0, 1 - abs(y - n))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<H / 2<<std::endl;
            }
        }

        if(abs(y - n) < 1) {
            if(n >= y) {
                dpy += max(0, 1 - abs(x - m)) * U[m * W + n] * dV * W / 2;
                if(debug) std::cout<<prefix<<"dpy += "<<max(0, 1 - abs(x - m))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<W / 2<<std::endl;
            } else {
                dpy -= max(0, 1 - abs(x - m)) * U[m * W + n] * dV * W / 2;
                if(debug) std::cout<<prefix<<"dpy -= "<<max(0, 1 - abs(x - m))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<W / 2<<std::endl;
            }
        }
    }

    m = floor(x); n = floor(y) + 1; w = 0;
    if(debug) std::cout<<prefix<<"(m, n) = ("<<m<<", "<<n<<")"<<std::endl;

    if(m >= 0 && m < H && n >= 0 && n < W) {
        w = max(0, 1 - abs(x - m)) * max(0, 1 - abs(y - n));

        dU[m * W + n] += w * dV;

        if(abs(x - m) < 1) {
            if(m >= x) {
                dpx += max(0, 1 - abs(y - n)) * U[m * W + n] * dV * H / 2;
                if(debug) std::cout<<prefix<<"dpx += "<<max(0, 1 - abs(y - n))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<H / 2<<std::endl;
            } else {
                dpx -= max(0, 1 - abs(y - n)) * U[m * W + n] * dV * H / 2;
                if(debug) std::cout<<prefix<<"dpx -= "<<max(0, 1 - abs(y - n))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<H / 2<<std::endl;
            }
        }

        if(abs(y - n) < 1) {
            if(n >= y) {
                dpy += max(0, 1 - abs(x - m)) * U[m * W + n] * dV * W / 2;
                if(debug) std::cout<<prefix<<"dpy += "<<max(0, 1 - abs(x - m))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<W / 2<<std::endl;
            } else {
                dpy -= max(0, 1 - abs(x - m)) * U[m * W + n] * dV * W / 2;
                if(debug) std::cout<<prefix<<"dpy -= "<<max(0, 1 - abs(x - m))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<W / 2<<std::endl;
            }
        }
    }

    m = floor(x) + 1; n = floor(y) + 1; w = 0;
    if(debug) std::cout<<prefix<<"(m, n) = ("<<m<<", "<<n<<")"<<std::endl;

    if(m >= 0 && m < H && n >= 0 && n < W) {
        w = max(0, 1 - abs(x - m)) * max(0, 1 - abs(y - n));

        dU[m * W + n] += w * dV;

        if(abs(x - m) < 1) {
            if(m >= x) {
                dpx += max(0, 1 - abs(y - n)) * U[m * W + n] * dV * H / 2;
                if(debug) std::cout<<prefix<<"dpx += "<<max(0, 1 - abs(y - n))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<H / 2<<std::endl;
            } else {
                dpx -= max(0, 1 - abs(y - n)) * U[m * W + n] * dV * H / 2;
                if(debug) std::cout<<prefix<<"dpx -= "<<max(0, 1 - abs(y - n))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<H / 2<<std::endl;
            }
        }

        if(abs(y - n) < 1) {
            if(n >= y) {
                dpy += max(0, 1 - abs(x - m)) * U[m * W + n] * dV * W / 2;
                if(debug) std::cout<<prefix<<"dpy += "<<max(0, 1 - abs(x - m))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<W / 2<<std::endl;
            } else {
                dpy -= max(0, 1 - abs(x - m)) * U[m * W + n] * dV * W / 2;
                if(debug) std::cout<<prefix<<"dpy -= "<<max(0, 1 - abs(x - m))<<" * "<<U[m * W + n]<<" * "<<dV<<" * "<<W / 2<<std::endl;
            }
        }
    }

    if(debug) std::cout<<prefix<<"Finished."<<std::endl;
}
作者:l691899397 发表于2016/12/14 17:25:35 原文链接
阅读:43 评论:0 查看评论

数据保存的三种方法SharedPreference、文件、数据库

$
0
0

数据保存

获取SharedPreference

我们可以通过以下两种方法之一创建或者访问shared preference 文件:

  • getSharedPreferences() — 如果需要多个通过名称参数来区分的shared preference文件, 名称可以通过第一个参数来指定。可在app中通过任何一个Context 执行该方法。
  • getPreferences() — 当activity仅需要一个shared preference文件时。因为该方法会检索activity下默认的shared preference文件,并不需要提供文件名称。
Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
    getString(R.string.preference_file_key), Context.MODE_PRIVATE);

应以与app相关的方式为shared preference文件命名,该名称应唯一。如本例中可将其命名为

"com.example.myapp.PREFERENCE_FILE_KEY" 。

当然,当activity仅需要一个shared preference文件时,我们可以使用getPreferences()方法:

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);

Caution: 如果创建了一个MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE 模式的shared preference文件,则其他任何app均可通过文件名访问该文件。

写Shared Preference

为了写shared preferences文件,需要通过执行edit()创建一个 SharedPreferences.Editor。

通过类似putInt()与putString()等方法传递keys与values,接着通过commit() 提交改变.

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();

读Shared Preference

为了从shared preference中读取数据,可以通过类似于 getInt() 及 getString()等方法来读取。在那些方法里面传递我们想要获取的value对应的key,并提供一个默认的value作为查找的key不存在时函数的返回值。如下:

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
int defaultValue = getResources().getInteger(R.string.saved_high_score_default);
long highScore = sharedPref.getInt(getString(R.string.saved_high_score), default);

保存到文件

存储在内部还是外部

  • Internal storage:
    总是可用的
    这里的文件默认只能被我们的app所访问。
    当用户卸载app的时候,系统会把internal内该app相关的文件都清除干净。
    Internal是我们在想确保不被用户与其他app所访问的最佳存储区域。

  • External storage:
    并不总是可用的,因为用户有时会通过USB存储模式挂载外部存储器,当取下挂载的这部分后,就无法对其进行访问了。
    是大家都可以访问的,因此保存在这里的文件可能被其他程序访问。
    当用户卸载我们的app时,系统仅仅会删除external根目录(getExternalFilesDir())下的相关文件。

Tip: 尽管app是默认被安装到internal storage的,我们还是可以通过在程序的manifest文件中声明android:installLocation 属性来指定程序安装到external storage。当某个程序的安装文件很大且用户的external storage空间大于internal storage时,用户会倾向于将该程序安装到external storage。更多安装信息见App Install Location。

获取External存储的权限

为了写数据到external storage, 必须在你manifest文件中请求WRITE_EXTERNAL_STORAGE权限:

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

Caution:目前,所有的apps都可以在不指定某个专门的权限下做读external storage的动作。但这在以后的安卓版本中会有所改变。如果我们的app只需要读的权限(不是写), 那么将需要声明 READ_EXTERNAL_STORAGE 权限。为了确保app能持续地正常工作,我们现在在编写程序时就需要声明读权限。

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

但是,如果我们的程序有声明WRITE_EXTERNAL_STORAGE 权限,那么就默认有了读的权限。
对于internal storage,我们不需要声明任何权限,因为程序默认就有读写程序目录下的文件的权限。

保存到Internal Storage

当保存文件到internal storage时,可以通过执行下面两个方法之一来获取合适的目录作为 FILE 的对象:

getFilesDir() : 返回一个File,代表了我们app的internal目录。
getCacheDir() : 返回一个File,代表了我们app的internal缓存目录。请确保这个目录下的文件能够在一旦不再需要的时候马上被删除,并对其大小进行合理限制,例如1MB 。系统的内部存储空间不够时,会自行选择删除缓存文件。

可以使用File() 构造器在那些目录下创建一个新的文件,如下:

File file = new File(context.getFilesDir(), filename);

同样,也可以执行openFileOutput() 获取一个 FileOutputStream 用于写文件到internal目录。如下:

String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;

try {
  outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
  outputStream.write(string.getBytes());
  outputStream.close();
} catch (Exception e) {
  e.printStackTrace();
}

如果需要缓存一些文件,可以使用createTempFile()。例如:下面的方法从URL中抽取了一个文件名,然后再在程序的internal缓存目录下创建了一个以这个文件名命名的文件。

 public File getTempFile(Context context, String url) {
    File file;
    try {
        String fileName = Uri.parse(url).getLastPathSegment();
        file = File.createTempFile(fileName, null, context.getCacheDir());
    catch (IOException e) {
        // Error while creating file
    }
    return file;
}
  • Note: 我们的app的internal storage 目录以app的包名作为标识存放在Android文件系统的特定目录下[data/data/com.example.xx]。 从技术上讲,如果文件被设置为可读的,那么其他app就可以读取该internal文件。然而,其他app需要知道包名与文件名。若没有设置为可读或者可写,其他app是没有办法读写的。因此我们只要使用了MODE_PRIVATE ,那么这些文件就不可能被其他app所访问。

保存文件到External Storage

因为external storage可能是不可用的,比如遇到SD卡被拔出等情况时。因此在访问之前应对其可用性进行检查。我们可以通过执行 getExternalStorageState()来查询external storage的状态。若返回状态为MEDIA_MOUNTED, 则可以读写。示例如下:

 /* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}

/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state) ||
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return true;
    }
    return false;
}

尽管external storage对于用户与其他app是可修改的,我们可能会保存下面两种类型的文件。

  • Public files :这些文件对与用户与其他app来说是public的,当用户卸载我们的app时,这些文件应该保留。例如,那些被我们的app拍摄的图片或者下载的文件。
  • Private files: 这些文件完全被我们的app所私有,它们应该在app被卸载时删除。尽管由于存储在external storage,那些文件从技术上而言可以被用户与其他app所访问,但实际上那些文件对于其他app没有任何意义。因此,当用户卸载我们的app时,系统会删除其下的private目录。例如,那些被我们的app下载的缓存文件。

想要将文件以public形式保存在external storage中,请使用getExternalStoragePublicDirectory()方法来获取一个 File 对象,该对象表示存储在external storage的目录。这个方法会需要带有一个特定的参数来指定这些public的文件类型,以便于与其他public文件进行分类。参数类型包括DIRECTORY_MUSIC 或者 DIRECTORY_PICTURES. 如下:

public File getAlbumStorageDir(String albumName) {
    // Get the directory for the user's public pictures directory.
    File file = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES), albumName);
    if (!file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

想要将文件以private形式保存在external storage中,可以通过执行getExternalFilesDir() 来获取相应的目录,并且传递一个指示文件类型的参数。每一个以这种方式创建的目录都会被添加到external storage封装我们app目录下的参数文件夹下(如下则是albumName)。这下面的文件会在用户卸载我们的app时被系统删除。如下示例:

public File getAlbumStorageDir(Context context, String albumName) {
    // Get the directory for the app's private pictures directory.
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (!file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}
  • 如果刚开始的时候,没有预定义的子目录存放我们的文件,可以在 getExternalFilesDir()方法中传递null. 它会返回app在external storage下的private的根目录。

  • 请记住,getExternalFilesDir() 方法会创建的目录会在app被卸载时被系统删除。如果我们的文件想在app被删除时仍然保留,请使用getExternalStoragePublicDirectory().

  • 无论是使用 getExternalStoragePublicDirectory() 来存储可以共享的文件,还是使用 getExternalFilesDir() 来储存那些对于我们的app来说是私有的文件,有一点很重要,那就是要使用那些类似DIRECTORY_PICTURES 的API的常量。那些目录类型参数可以确保那些文件被系统正确的对待。例如,那些以DIRECTORY_RINGTONES 类型保存的文件就会被系统的media scanner认为是ringtone而不是音乐。

查询剩余空间

如果事先知道想要保存的文件大小,可以通过执行getFreeSpace() or getTotalSpace() 来判断是否有足够的空间来保存文件,从而避免发生IOException。那些方法提供了当前可用的空间还有存储系统的总容量。

然而,系统并不能保证可以写入通过getFreeSpace()查询到的容量文件, 如果查询的剩余容量比我们的文件大小多几MB,或者说文件系统使用率还不足90%,这样则可以继续进行写的操作,否则最好不要写进去。

Note:并没有强制要求在写文件之前去检查剩余容量。我们可以尝试先做写的动作,然后通过捕获 IOException 。这种做法仅适合于事先并不知道想要写的文件的确切大小。例如,如果在把PNG图片转换成JPEG之前,我们并不知道最终生成的图片大小是多少。

删除文件

在不需要使用某些文件的时候应删除它。删除文件最直接的方法是直接执行文件的delete()方法。

myFile.delete();

如果文件是保存在internal storage,我们可以通过Context来访问并通过执行deleteFile()进行删除

myContext.deleteFile(fileName);
  • Note: 当用户卸载我们的app时,android系统会删除以下文件:
    所有保存到internal storage的文件。
    所有使用getExternalFilesDir()方式保存在external storage的文件。

然而,通常来说,我们应该手动删除所有通过 getCacheDir() 方式创建的缓存文件,以及那些不会再用到的文件。

保存到数据库

定义Schema与Contract

SQL中一个重要的概念是schema:一种DB结构的正式声明,用于表示database的组成结构。schema是从创建DB的SQL语句中生成的。我们会发现创建一个伴随类(companion class)是很有益的,这个类称为合约类(contract class),它用一种系统化并且自动生成文档的方式,显示指定了schema样式。

Contract Clsss是一些常量的容器。它定义了例如URIs,表名,列名等。这个contract类允许在同一个包下与其他类使用同样的常量。 它让我们只需要在一个地方修改列名,然后这个列名就可以自动传递给整个code。

组织contract类的一个好方法是在类的根层级定义一些全局变量,然后为每一个table来创建内部类。

Note:通过实现 BaseColumns 的接口,内部类可以继承到一个名为_ID的主键,这个对于Android里面的一些类似cursor adaptor类是很有必要的。这么做不是必须的,但这样能够使得我们的DB与Android的framework能够很好的相容。

例如,下面的例子定义了表名与该表的列名:

public final class FeedReaderContract {
    // To prevent someone from accidentally instantiating the contract class,
    // give it an empty constructor.
    public FeedReaderContract() {}

    /* Inner class that defines the table contents */
    public static abstract class FeedEntry implements BaseColumns {
        public static final String TABLE_NAME = "entry";
        public static final String COLUMN_NAME_ENTRY_ID = "entryid";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
        ...
    }
}

使用SQL Helper创建DB

定义好了的DB的结构之后,就应该实现那些创建与维护db和table的方法。下面是一些典型的创建与删除table的语句。

private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES =
    "CREATE TABLE " + FeedReaderContract.FeedEntry.TABLE_NAME + " (" +
    FeedReaderContract.FeedEntry._ID + " INTEGER PRIMARY KEY," +
    FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
    FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
    ... // Any other options for the CREATE command
    " )";

private static final String SQL_DELETE_ENTRIES =
    "DROP TABLE IF EXISTS " + TABLE_NAME_ENTRIES;

类似于保存文件到设备的internal storage ,Android会将db保存到程序的private的空间。我们的数据是受保护的,因为那些区域默认是私有的,不可被其他程序所访问。

在SQLiteOpenHelper类中有一些很有用的APIs。当使用这个类来做一些与db有关的操作时,系统会对那些有可能比较耗时的操作(例如创建与更新等)在真正需要的时候才去执行,而不是在app刚启动的时候就去做那些动作。我们所需要做的仅仅是执行getWritableDatabase()或者getReadableDatabase().

  • Note:因为那些操作可能是很耗时的,请确保在background thread(AsyncTask or IntentService)里面去执行 getWritableDatabase() 或者 getReadableDatabase() 。

为了使用 SQLiteOpenHelper, 需要创建一个子类并重写onCreate(), onUpgrade()与onOpen()等callback方法。也许还需要实现onDowngrade(), 但这并不是必需的。

例如,下面是一个实现了SQLiteOpenHelper 类的例子:

public class FeedReaderDbHelper extends SQLiteOpenHelper {
    // If you change the database schema, you must increment the database version.
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "FeedReader.db";

    public FeedReaderDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_ENTRIES);
    }
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // This database is only a cache for online data, so its upgrade policy is
        // to simply to discard the data and start over
        db.execSQL(SQL_DELETE_ENTRIES);
        onCreate(db);
    }
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(db, oldVersion, newVersion);
    }
}

为了访问我们的db,需要实例化 SQLiteOpenHelper的子类:

FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());

添加信息到DB

通过传递一个 ContentValues 对象到insert()方法:

// Gets the data repository in write mode
SQLiteDatabase db = mDbHelper.getWritableDatabase();

// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID, id);
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_CONTENT, content);

// Insert the new row, returning the primary key value of the new row
long newRowId;
newRowId = db.insert(
         FeedReaderContract.FeedEntry.TABLE_NAME,
         FeedReaderContract.FeedEntry.COLUMN_NAME_NULLABLE,
         values);

insert()方法的第一个参数是table名,第二个参数会使得系统自动对那些ContentValues 没有提供数据的列填充数据为null,如果第二个参数传递的是null,那么系统则不会对那些没有提供数据的列进行填充。

从DB中读取信息

为了从DB中读取数据,需要使用query()方法,传递需要查询的条件。查询后会返回一个 Cursor 对象。

SQLiteDatabase db = mDbHelper.getReadableDatabase();

// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
    FeedReaderContract.FeedEntry._ID,
    FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE,
    FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED,
    ...
    };

// How you want the results sorted in the resulting Cursor
String sortOrder =
    FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED + " DESC";

Cursor c = db.query(
    FeedReaderContract.FeedEntry.TABLE_NAME,  // The table to query
    projection,                               // The columns to return
    selection,                                // The columns for the WHERE clause
    selectionArgs,                            // The values for the WHERE clause
    null,                                     // don't group the rows
    null,                                     // don't filter by row groups
    sortOrder                                 // The sort order
    );

要查询在cursor中的行,使用cursor的其中一个move方法,但必须在读取值之前调用。一般来说应该先调用moveToFirst()函数,将读取位置置于结果集最开始的位置。对每一行,我们可以使用cursor的其中一个get方法如getString()或getLong()获取列的值。对于每一个get方法必须传递想要获取的列的索引位置(index position),索引位置可以通过调用getColumnIndex()或getColumnIndexOrThrow()获得。

下面演示如何从course对象中读取数据信息:

cursor.moveToFirst();
long itemId = cursor.getLong(
    cursor.getColumnIndexOrThrow(FeedReaderContract.FeedEntry._ID)
);

删除DB中的信息

和查询信息一样,删除数据同样需要提供一些删除标准。DB的API提供了一个防止SQL注入的机制来创建查询与删除标准。

SQL Injection:(随着B/S模式应用开发的发展,使用这种模式编写应用程序的程序员也越来越多。但由于程序员的水平及经验也参差不齐,相当大一部分程序员在编写代码时没有对用户输入数据的合法性进行判断,使应用程序存在安全隐患。用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据,这就是所谓的SQL Injection,即SQL注入)

该机制把查询语句划分为选项条件与选项参数两部分。条件定义了查询的列的特征,参数用于测试是否符合前面的条款。由于处理的结果不同于通常的SQL语句,这样可以避免SQL注入问题。

// Define 'where' part of query.
String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
// Specify arguments in placeholder order.
String[] selelectionArgs = { String.valueOf(rowId) };
// Issue SQL statement.
db.delete(table_name, mySelection, selectionArgs);

更新数据

当需要修改DB中的某些数据时,使用 update() 方法。

update结合了插入与删除的语法。

SQLiteDatabase db = mDbHelper.getReadableDatabase();

// New value for one column
ContentValues values = new ContentValues();
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title);

// Which row to update, based on the ID
String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
String[] selectionArgs = { String.valueOf(rowId) };

int count = db.update(
    FeedReaderDbHelper.FeedEntry.TABLE_NAME,
    values,
    selection,
    selectionArgs);
作者:an13531829360 发表于2016/12/14 17:25:58 原文链接
阅读:19 评论:0 查看评论

第九课 协同程序

$
0
0
协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈、局部变量和指令指针,同时又与其他协同程序共享全局变量和其他大部分东西。从概念上讲线程与协同程序的只要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作地运行。就是说,一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显示地要求挂起时,它的执行才会暂停。

协同程序基础
Lua将所有关于协同程序的函数放置在一个名为“ coroutine”的table中。
函数create用于创建新的协同程序,它只有一个参数,就是一个函数。该函数的代码就是协同程序所需执行的内容。create会返回一个thread类型的值, 用以表示新的协同程序。通常create函数的参数是一个匿名函数,如:
co = coroutine.create(function () print("Hi") end)
print(co) -->thread:0x8071d98
一个协同程序可以处在4个不同的状态:挂起(suspended)、运行(running)、死亡(dead)和正常(normal)。当创建一个协同程序时,它处于挂起状态。也就是说 ,协同程序不会在创建它时自动执行其内容。可以通过函数status来检查协同程序的状态:
print(coroutine.status(co)) --suspended
函数coroutine.resume用于启动或再次启动一个协同程序的执行,并将其状态由挂起改为运行。
coroutine.resume(co) -->Hi
本例子中,协同程序的内容只是简单地打印了"Hi"后便终止了,然后它就处于死亡状态,也就再也无法返回了:
print(coroutine.status(co)) -->dead
到目前为止,协同程序看上去还只是像一种复杂的函数调用方法。其实协同程序真正的强大之处在于函数yield的使用上,该函数可以让一个运行中的协同程序挂起,而之后可以 再恢复它的运行。
co = coroutine.create(function ()
for i = 1, 10 do
print("co", i)
coroutine.yield()
end
end)
当唤醒这个协同程序时,它就会开始执行,直到第一个yield:
coroutine.resume(co) -->co 1
print(coroutine.status(co)) -->suspended
从协同程序的角度看,所有在它挂起时发生的活动都发生在yield调用中。当恢复协同程序的执行时,对于yield的调用才最终返回。然后协同程序继续它的执行,直到下一个yield调用或者执行结束:
coroutine.resume(co) -->co 2
coroutine.resume(co) -->co 3
coroutine.resume(co) -->co 4
...
coroutine.resume(co) -->co 10
coroutine.resume(co) -->什么都不打印
print(coroutine.status(co)) --dead
print(coroutine.resume(co))
-->false connot resume dead coroutine
注:resume是在保护模式中运行的。如果一个协同程序在执行中发生任何错误,Lua是不会显示错误消息的,而是将执行权返回给resume调用。
当一个协同程序A 唤醒另一个协同程序B 时,协同程序A就处于一个特殊状态,既不是挂起状态(无法继续执行A),也不是运行状态(是B在运行)。所以将这时A的状态称为“正常状态”。
Lua的协同程序还具有一项有用的机制,就是可以通过一对resume-yield来交换数据。在第一次调用resume时,并没有对应的yield在等待它,因此所有传递给resume的额外参数都将视为协同程序主函数的参数:
co = coroutine.create(function (a, b, c)
print("co", a, b, c) end)
coroutine.resume(co, 1, 2, 3) -->co 1 2 3
在resume调用的返回的内容中,第一个值为 true则表示没有错误,而后面所有的值都是对应yield传入的参数:
co = coroutine.create(function (a, b)
coroutine.yield(a + b, a - b)
end )
print(coroutine.resume(co, 20, 10)) -->true 30 10
与此对应的是,yield返回的额外值就是对应resume传入的参数:
co = coroutine.create(function ()
print("co", coroutine.yield())
end)
coroutine.resume(co)
coroutine.resume(co, 4, 5) -->co 4 5
最后,当一个协同程序结束时,它的主函数所返回的值都将作为对应resume的返回值:
co = coroutine.create(function ()
return 6, 7
end)
print(coroutine.resume(co)) -->true 6 7

Lua提供的是一种“非对称的协同程序”。也就是说,Lua提供了两个函数来控制协同程序的执行,一个用于挂起执行,另一个用于恢复执行。而一些其他的语言则提供了“对称的协同程序”,其中只有一个函数用于转让协同程序之间的执行权。

管道(pipe)与过滤器(filter)
生产者-消费者问题:
function producer ()
while true do
local x = io.read() --产生新值
send(x) --发送给消费者
end
end

function consumer ()
while true do
local x = receive() --从生产者接收值
io.write(x, "\n") --消费新值
end
end
如何将send与receive匹配起来。这是一个典型的“谁具有主循环(who-has-main-loop)”的问题。由于生产者和消费者都处于活动状态,它们各自具有一个主循环,并且都将对方视为一个可调用的服务。
协同程序被称为是一种匹配生产者和消费者的理想工具,一对resume-yield完全一改典型的调用者与被调用者之间的关系。当一个协同程序调用yield时,它不是进入了一个新的函数,而是从一个悬而未决的resume调用中返回。同样地,对于resume的调用也不会启动一个新函数,而是从一次yield调用中返回。这项特性正可用于匹配send和receive,这两者都认为自己是主动方,对方是被动方。receive唤醒生产者执行,促使其能产生一个新值。而send则 产出一个新值返还给消费者:
function receive ()
local status, value = coroutine.resume(producer)
return value
end
function send(x)
coroutine.yield(x)
end
因此,生产者一定是一个协同程序:
producer = coroutine.create(
function ()
while true do
local x = io.read()
send(x)
end
end)
在这种设计中,程序通过调用消费者启动。当消费者需要一个新值时,它唤醒生产者。生产者返回一个新值后停止运行,并等待消费者的再次唤醒。将这种设计称为“消费者驱动”。
可以扩展上述设计,实现“过滤器(filter)”。过滤器是一种位于生产者和消费者之间的处理功能,可用于对数据的一些变换。过滤器既是一个消费者又是一个生产者,它唤醒一个生产者促使其产生新值,然后又将变换后的值传递给消费者。

function receive (prod)
loacl status, value = coroutine.resume(prod)
return value
end

function send (x)
coroutine.yield(x)
end

function producer ()
return coroutine.create(
function ()
while true do
local x = io.read()
send(x)
end
end)
end

function filter (prod)
return coroutine.create(
function ()
for line = 1, math.huge do
local x = receive(prod)
x = string.format("%5d %s", line, x)
send(x)
end
end)
end

function consumer (prod)
while true do
local x = receive(prod)
io.write(x, "\n")
end
end

创建运行代码,将这些函数串联起来,然后启动消费者:
p = producer()
f = filter(p)
consumer(f)
或者:
consumer(filter(producer()))

以协同程序实现迭代器
将循环迭代器视为“生产者-消费者”模式的一种特例,一个迭代器会产出一些内容,而循环体则会消费这些内容。
例如:写一个迭代器,使其可以遍历某个数组的所有排列组合形式。若直接编写这种迭代器可能不太容易,但若编写一个递归函数来产生所有的排列组合则不会很困难。只要将每个数组元素都依次放到最后一个位置,然后递归地生成其余元素的排列。
function permgen (a, n)
n = n or #a --默认n为a的大小
if n <= 1 then
printResult(a)
else
for i = 1, n do
--将第i个元素放到数组末尾
a[n], a[i] = a[i], a[n]
--生成其余元素的排列
permgen(a, n - 1)
-- 恢复第i个元素
a[n], a[i] = a[i], a[n]
end
end
end
打印函数定义:
function printResult (a)
for i = 1, #a do
io.write(a[i], " ")
end
io.write("\n")
end

调用pergmen:
pergmen({1, 2, 3, 4})
-->2, 3, 4, 1
-->3, 2, 4, 1
-->3, 4, 2, 1
....
-->1, 2, 3, 4

协同程序实现迭代器:
首先将permgen中的printResult改为yield:
function permgen (a, n)
n = n or #a --默认n为a的大小
if n <= 1 then
coroutine.yield(a)
else
for i = 1, n do
--将第i个元素放到数组末尾
a[n], a[i] = a[i], a[n]
--生成其余元素的排列
permgen(a, n - 1)
-- 恢复第i个元素
a[n], a[i] = a[i], a[n]
end
end
end
再定义一个工厂函数,用于将生成函数放到一个协同程序中运行,并创建迭代器函数。迭代器只是简单地唤醒协同程序,让其产生下一种序列:
function permutations (a)
local co = coroutine.create(function () permgen(a) end)
return function () --迭代器
local code, res = coroutine.resume(co)
return res
end
end

for p in permutations {1, 2, 3, 4} do
printResult(p)
end

permutations函数使用了一种在Lua中比较常见的模式,就是将一条唤醒协同程序的调用包装在一个函数中。由于这种模式比较常见,所以Lua专门提供了一个函数coroutine.wrap来完成这个功能。类似于create,wrap创建了一个新的协同程序。但不同的是,wrap并不是返回协同程序本身,而是返回一个函数。每当调用这个函数,即可唤醒一次 协同程序。但这个函数与resume的不同之处在于,它不会返回错误代码。当遇到错误时,它会引发错误。
function permutations (a)
return coroutine.wrap(function () permgen(a) end)
end

非抢先式的多线程
协同程序提供了一种协作式的多线程。每个协同程序都等于是一个多线程。一对yield-resume可以将执行权在不同线程间切换。然而协同程序与常规的多线程的不同之处在于,协同程序是非抢先式的。当一个协同程序运行时,是无法从外部停止它的。只有当协同程序显示地要求挂起时(调用yield),它才会停止。
对于非抢先式的多线程来说,只要有一个线程调用了一个阻塞的操作,这个程序在该操作完成前,都会停止下来。对于大多数应用程序来说,这种行为是无法接受的。这也导致了许多程序员放弃协同程序,转而使用传统的多线程。接下来会用一个有用的方法来解决这个问题。
例如:从网站下载一个文件
require "socket"
host = "www.w3.org"
file = "/TR/REC-html32.html"
c = assert(socket.connect(host, 80))
c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")
while true do
local s, status, partial = c:receive(2^10)
io.write(s or partial)
if status == "closed" then break end
end
c:close()
同时能够下载多个文件的例子:
function download (host, file)
c = assert(socket.connect(host, 80))
local count = 0
c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")
while true do
local s, status, partial = receive(c)
count = count + #(s or partial)
if status == "closed" then break end
end
c:close()
print(file, count)
end

function receive (connection)
return connect:receive(2^10)
end
在并发的实现中,这个函数在接收数据时绝对不能阻塞。因此,它需要在没有足够的可用数据时挂起执行。修改如下:
function receive (connection)
connection:settimeout(0) --非阻塞调用
local s, status, partial = connection:receive(2^10)
if status == "timeout" then
coroutine.yield(connection)
end
return s or partial, status
end

调度程序:
threads = {} --用于记录所有正在运行的线程
function get (host, file)
--创建协同程序
local co = coroutine.create(function ()
download(host, file) end)
--将其插入记录表中
table.insert(threads, co)
end

function dispatch ()
--主调度
local i = 1
while true do
if threads[i] == nil then --还有线程吗
if thread[1] == nil then break end --列表是否为空
i = 1 --重新开始循环
end

local status, res = coroutine.resume(threads[i])
if not res then --线程是否完成任务了
table.remove(threads[i])
else
i = i + 1
end
end
end

main程序如下:
host = "www.w3.org"
get (host, "/TR/html401/html40.txt")
get (host, "/TR/2002/REC-xhtml1-20020801/xhtml.pdf")
...
dispatch() --主循环

一个问题:如果所有线程都没有数据可读,调度程序就会执行一个“忙碌等待”,不断地从一个线程切换到另一个线程,仅仅是为了检测是否有数据可读。
修改此问题:LuaSocket中的select函数,用于等待一组socket的状态改变,在等待时程序陷入阻塞状态。
function dispatch ()
--主调度
local i = 1
local connections = {}
while true do
if threads[i] == nil then --还有线程吗
if thread[1] == nil then break end --列表是否为空
i = 1 --重新开始循环
connections = {}
end

local status, res = coroutine.resume(threads[i])
if not res then --线程是否完成任务了
table.remove(threads[i])
else
i = i + 1
connections[#connections + 1] = res
if #connections == #threads then
socket.select(connections) --所有线程都阻塞
end
end
end
end










作者:zhenhuax 发表于2016/12/14 17:31:43 原文链接
阅读:13 评论:0 查看评论

使用数位板遇到的常见问题及解决方法

$
0
0

如何判断出现的故障是硬件故障还是软件故障?

当数位绘图板不能正常使用,通常有可能是数位扳硬件本身有问题,但更多的故障是软件引起的。如何判断故障现象到底是硬件故障还是软件故障?下面介绍几个简单的检测方法: 

1、最简单最有效的检测方法就是将数位扳接到另外一台电脑上测试,如果在其他的电脑上正常,那么可以认为数位扳硬件没有问题,故障是出在驱动程序、操作系统、应用程序或者其他电磁干扰上。 
2、如果没有条件在别的电脑上测试,那么可以通过简单的操作判断是否是数位扳的问题。 
a、将数位扳接到电脑USB口上,数位扳的指示灯应该点亮 
b、将压感笔放到数位扳的有效区域内,压下笔尖/笔擦或者侧开关,数位扳的指示灯会变色。 如果满足以上两点,那么通常数位扳硬件应该没有问题。如果您没有办法做以上简单的测试,那么您可以将数位板递交到Huion的技术服务中心,技术服务中心将免费为您检测。 
数位屏

压感笔始终有压感现象的分析和解决


故障现象: 
“我的板子出了点问题,反正就是离它老远就划上去了,点击也用不了,变的好敏感啊!” 
“在painter8里试,和以前不一样。以前非要接触到板子才有痕迹画上去。现在却离板子还有好远,一厘米的样子,它就记录了。” 
“只要用笔头指到板子上(没碰到,查5~6毫米)板子就有反映了。箭头在屏幕里乱点东西。好像笔头就紧压在板上不松的样子。” 
可能的原因及解决办法(按照出现的比例由多到少排序): 
一、绘王数位板驱动程序的数据文件有问题,导致驱动程序工作不稳定。 
解决办法: 卸载驱动程序,然后重新安装(请严格按照下面的步骤操作,因为驱动程序一直在后台运行,如果操作不正确,会导致卸载不干净,重新安装后故障还会依旧的) 
步骤: 
1、自动卸载: 
a、将绘王数位板拔下。 
b、在“控制面板”的“添加删除程序”里找到Huion的驱动程序,卸载。 
c、重新启动电脑,看看控制面板上的Huion图标还在不在,如果不在,说明卸载成功,如果还在,请按照下面的方法手动卸载。 
d、接上数位扳,重新安装驱动程序。 
2、手动卸载(此方法也适合安装驱动程序时提示系统中已经有数位扳驱动,而不让继续安装的故障): 
a、将数位扳拔下; 
b、打开电脑查看系统文件和隐藏文件的功能 
Win98系统操作: 
我的电脑-查看-文件夹选项-查看-将“显示所有文件“选中,将“隐藏已知文件类型的扩展名”去掉选择 
Win2000/xp/2003 系统操作 
我的电脑-工具-文件夹选项-查看- 将“显示所有文件和文件夹”选中,将“隐藏已知文件类型的扩展名”去掉选择 
c、查找残留的驱动程序文件,一一删除即可 
请点击 开始 - 搜索 - 文件或者文件夹 搜索时选择操作系统目录 通常是 windows 或者winnt 的目录。 
请将将下列的文件一一搜索,如果有,则将其删除。 
Huion.dat wintab.dll wintab32.dll tablet.cpl tablet.exe tablet.dat HuionTablet.cpl HuionTablet.znc TabUserW.exe 
d、删除完毕后重启电脑,安装新的驱动程序即可。 
二、笔尖或者笔擦过紧,导致笔尖或者笔擦活动不灵活,如果有异物进入压感笔,导致笔尖和笔擦活动不灵活,就会造成始终有压感的情况。 
解决办法:把笔芯拔出来,再轻轻插回去,笔芯应该是能很轻松的插入,如果很紧,有可能是笔芯过粗或者笔杆的开口过小。笔擦也可以很轻松转动才对。 
(请注意:丽图和批笔的压感笔笔芯是不可更换的,请勿按此提示操作) 
三、压感笔的压感电容有问题或者笔硬件参数设置有问题(这种问题通常出现在开封第一次使用或者使用多年之后) 
解决办法:只能返回Huion公司维修更换,请联系将填写完整的保修卡连同购买时的发票御笔一同寄回Huion公司保修。请勿自行拆笔,以免失去保修权利。 


我需要每次开机前就插入数位板,还是我可以在进入系统后再插入?不用的时候是否需要拔下来?一直插着对数位板寿命有影响吗?


安装驱动程序时与安装后第一次重新开机时,必须插入数位板以建立数位板设定文件。一旦喜好设定文件建立完成,您的数位板便能随插即用。如果您很长时间不用 (几天以上)建议您将数位板拔下,否则,还是不要经常插拔。此答复适用操作系统:此答复适合USB接口的数位板,和Windows操作平台,如果您是串口或者其他操作系统,建议您不要经常插拔数位板。


安装好文通手写输入软件后,手写时出现乱码的解决


此问题处理方法: 
1.打开: “开始菜单”("Start")-“控制面板”("Control Panel")-“区域和语言选项”("Regional and Language Options") 
2.选择“语言”("Languages")属性页,单击“设置”("Details")按钮,在“文字及输入语言对话框”("Text Services and Input Languages")中, 
将“缺省的输入法”("Default Input Language")设置成: 
“中国-中文(简体)-美式键盘”("Chinese(PRC)-Chinese(Simplified)-USKeyBoard") 
或其他中文简体输入法,比如“微软拼音输入法3.0”("MicroSoft pinyin IME 3.0") 
3.如果使用的是英文版Windows 2K/XP, 
选择“高级”("Advanced")属性页,将“非Unicode程序语言”("Language for non-Unicode programs")选择成 “中文”("Chinese(PRC)") 
选择“辅助语言支持”("Supplemental language support"),选中“东亚语言支持”("Install files for East-Asian languages")


自动或手动卸载Huion数位板驱动的方法


一,自动卸载: 
a、将数位板拔下。 
b、在“控制面板”的“添加删除程序”里找到Huion的驱动程序,卸载。 
c、重新启动电脑,看看控制面板上的Huion图标还在不在,如果不在,说明卸载成功,如果还在,请按照下面的方法手动卸载。 d、接上数位扳,重新安装驱动程序。 
二,手动卸载: (此方法也适合安装驱动程序时提示系统中已经有数位扳驱动,而不让继续安装的故障) 
a、将数位扳拔下; 
b、打开电脑查看系统文件和隐藏文件的功能 
Win98系统操作: 
我的电脑-查看-文件夹选项-查看-将“显示所有文件“选中,将“隐藏已知文件类型的扩展名”去掉选择 
Win2000/xp/2003 系统操作 
我的电脑-工具-文件夹选项-查看- 将“显示所有文件和文件夹”选中,将“隐藏已知文件类型的扩展名”去掉选择 
c、查找残留的驱动程序文件,一一删除即可 
请点击 开始 - 搜索 - 文件或者文件夹 搜索时选择操作系统目录 通常是 windows 或者winnt 的目录。 
请将将下列的文件一一搜索,如果有,则将其删除。 Huion.dat wintab.dll wintab32.dll tablet.cpl tablet.exe tablet.dat HuionTablet.cpl HuionTablet.znc TabUserW.exe 
d、删除完毕后重启电脑,安装新的驱动程序即可


在英文版系统上安装Huion数位板驱动(驱动为多语言版本),打开数位板驱动界面是乱码的解决方法:


打开 “开始菜单”("Start")-“控制面板”("Control Panel")-“区域和语言选项”("Regional and Language Options")里面做如下设置.在语言选项里选择您系统语言的区域,点击确定。 
解决办法: 
详细说明,打个比方说:用户的系统为英文系统,但是在中国使用,所以,区域选择为中国;这样,用户在安装驱动的时候,我们的驱动就会自动判断系统为中文, 安装中文的驱动界面;中文的驱动界面,在英文的界面下,因为缺少中文字库,所以会显示乱码.不同的语言版本,如果出现乱码,产生的原因大都如此. 
我们网站上的新版驱动,全部为多语言版本,可以自动适应所有语言版本的系统.但是,因为有些系统的区域选择为本地的区域(非外文系统的区域),所以就会出现乱码.


关于Intuos3 绘图区贴膜起来的问题!


Intuos3 贴膜有2种,一种是灰色的,可以增大笔画时的摩擦力,另外一种是透明的,方便用户将需要描绘的稿件放在贴膜下面进行临摹。 
标准配置是灰色的贴膜,透明的贴膜如果需要,可以单独购买。 
贴膜只有上边的一条边用不干胶贴在数位板上,因此可以掀开。当贴膜磨损之后,您可以购买新的贴膜自行更换。 
贴膜是常见的耗材,和笔芯一样。 
因此,您不必担心,绘图区贴膜起来不是质量问题。


使用一段时间后,数位板的覆盖膜会有刮痕。要如何预防这种状况


请及时更换笔尖,因为它的边缘可能因部分磨损变得太锐利,尤其用笔非常频繁时更容易有此现象。边缘锐利的笔尖可能会损坏数字板覆盖膜。此外,由于长时间使用 也会造成正常磨损,最好能定期更换覆盖膜。请注意,笔尖与覆盖膜属于耗材,并非免费保修项目。如果你需要更换笔尖或者覆盖贴膜,您可以在网站上的配件中心 订购,也可以就近联络当地的经销商。


Intuos3 的耗电量使用情形如何?为何我接到键盘上的USB口就无法使用?


Intuos3 需要从 USB 埠获得至少 280 mA 的电流。低电量的 USB 埠只供应 100 mA,因此使用 Intuos3 时,您必须使用主要电源端口(例如,计算机上的 USB 端口)或是 USB 集线器。 (以上情形适用于下列操作系统: Windows 95/98、Windows 2000、Windows XP、Windows ME、Mac OSX)


我可以停用Intuos3 上,一个或同时两个 Touch Strip 触控带吗?


是的,您可以停用 Touch Strip 触控板或是设定只配合使用压感笔,避免您的手碰触 Touch Strip 触控带时,不小心卷动画面。 您可以在控制面板的Huion数位板属性里设置这些功能。 
请依下列步骤操作停用 Touch Strip 触控带: 
1. 开启 "Huion 数位板属性" 窗口(位于控制面板)。 
2. 从靠近控制面板窗口上方的 "工具" 表中,选择 "功能"。 
3. 选择窗口中间的 Touch Strip 键,按下 "功能" 的下拉选单上的 "无效"。尺寸为 6X8 和9X12 在左右两侧个有一个 Touch Strip 触控带。4×5 型的只有单一的触控带。 
4. 关闭控制台。 请按下列步骤设定 Touch Strip 触控带为只用感应笔输入: 
a. 开启 "Huion 数位板属性" 窗口(位于控制面板)。 
b. 从靠近控制面板窗口上方的 "工具" 表中,选择 "功能"。 
c. 选择窗口中央的 Touch Strip 键,按下按键区左下方的" 高级 "。 
d. 在 "仅用笔" 处勾选相应的选框,然后按下 "OK"。 
e. 关闭 "Huion 数位板属性" 窗口。


为什么我的驱动程序装不上?


可能的原因 
1、 硬件故障,请检查数位板的指示灯是否亮,如果不亮,请检查USB接口是否能提供+5V电,是否是USB供电不足,或者使用延长线造成数据传递不良。当然也有可能是数位板本身故障。可以换台电脑试试。 
2、 系统软件故障,当数位板接入电脑后,电脑侦测到硬件,但是安装驱动程序的时候提示找不到数位板。请打开 控制面板 系统 设备管理器,下面的图显示的是安装数位板前和安装数位板后的区别,如果系统没有侦测到人体学输入设备,则表明系统安装的时候没有安装全,或者系统用的是盗 版,并且版本太低。解决办法请重装系统或者致电Huion公司技术部。 
3、 其他程序引起的故障。在安装数位板驱动的时候,提示系统里有其他的数位板驱动,或者安装完驱动,控制面板里的Huion数位板图标一打开就报错,显示找不 到数位板驱动。原因是该电脑以前装过其他型号或者别的牌子的数位板。解决办法很简单,请参考 《1、关于压感笔始终有压感现象的分析和解决》的手动卸载驱动程序步骤即可解上述问题。


我的板子没办法和显示器绝对定位?


通常这种故障都是驱动程序引起的,请重新安装数位板驱动程序,并且检查一下是否电脑感染病毒或者电脑灰尘过多造成系统不稳定,导致驱动程序不能稳定工作甚至非法退出。也可以参考第一个问题的解决方式来解决。 
同类问题: 
我的光标用着就突然不动了? 
我现在的是用到一半,笔压一下子就没了,重新启动又可以了? 
控制面板的Huion图标打不开,提示找不到数位板驱动程序? 
都可能是这个原因引起的
作者:mynote 发表于2016/12/14 17:33:08 原文链接
阅读:9 评论:0 查看评论

ffmpeg学习七:avformat_find_stream_info函数源码分析

$
0
0

前面两篇文章分析avformat_open_input和avcodec_open2两个函数,我们所做的函数分析工作都是为了能够很好的理解前面一篇博客:ffmpeg学习四:写第一个程序-视频解码中所给的视频解码的程序。avformat_find_stream_info函数也是视频解码程序中必须要有的函数,因此这篇文章主要来分析这个函数。

一、功能简介

先看看avformat_find_stream_info函数的注释:

/**
 * Read packets of a media file to get stream information. This
 * is useful for file formats with no headers such as MPEG. This
 * function also computes the real framerate in case of MPEG-2 repeat
 * frame mode.
 * The logical file position is not changed by this function;
 * examined packets may be buffered for later processing.
 *
 * @param ic media file handle
 * @param options  If non-NULL, an ic.nb_streams long array of pointers to
 *                 dictionaries, where i-th member contains options for
 *                 codec corresponding to i-th stream.
 *                 On return each dictionary will be filled with options that were not found.
 * @return >=0 if OK, AVERROR_xxx on error
 *
 * @note this function isn't guaranteed to open all the codecs, so
 *       options being non-empty at return is a perfectly normal behavior.
 *
 * @todo Let the user decide somehow what information is needed so that
 *       we do not waste time getting stuff the user does not need.
 */
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

从注释来看,avformat_find_stream_info主要是读一些包(packets ),然后从中提取初流的信息。有一些文件格式没有头,比如说MPEG格式的,这个时候,这个函数就很有用,因为它可以从读取到的包中获得到流的信息。在MPEG-2重复帧模式的情况下,该函数还计算真实的帧率。
逻辑文件位置不被此函数更改; 读出来的包会被缓存起来供以后处理。
注释很好的解释了这个函数的功能。我们可以想象一下,既然这个函数的功能是更新流的信息,那么可以猜测它的作用就是更新AVStream这个结构体中的字段了。我们先浏览下这个结构体:

二、AVStream结构体解析

/**
 * Stream structure.
 * New fields can be added to the end with minor version bumps.
 * Removal, reordering and changes to existing fields require a major
 * version bump.
 * sizeof(AVStream) must not be used outside libav*.
 */
typedef struct AVStream {
    int index;    /**< stream index in AVFormatContext */
    /**
     * Format-specific stream ID.
     * decoding: set by libavformat
     * encoding: set by the user, replaced by libavformat if left unset
     */
    int id;
#if FF_API_LAVF_AVCTX
    /**
     * @deprecated use the codecpar struct instead
     */
    attribute_deprecated
    AVCodecContext *codec;
#endif
    void *priv_data;

#if FF_API_LAVF_FRAC
    /**
     * @deprecated this field is unused
     */
    attribute_deprecated
    struct AVFrac pts;
#endif

    /**
     * This is the fundamental unit of time (in seconds) in terms
     * of which frame timestamps are represented.
     *
     * decoding: set by libavformat
     * encoding: May be set by the caller before avformat_write_header() to
     *           provide a hint to the muxer about the desired timebase. In
     *           avformat_write_header(), the muxer will overwrite this field
     *           with the timebase that will actually be used for the timestamps
     *           written into the file (which may or may not be related to the
     *           user-provided one, depending on the format).
     */
    AVRational time_base;

    /**
     * Decoding: pts of the first frame of the stream in presentation order, in stream time base.
     * Only set this if you are absolutely 100% sure that the value you set
     * it to really is the pts of the first frame.
     * This may be undefined (AV_NOPTS_VALUE).
     * @note The ASF header does NOT contain a correct start_time the ASF
     * demuxer must NOT set this.
     */
    int64_t start_time;

    /**
     * Decoding: duration of the stream, in stream time base.
     * If a source file does not specify a duration, but does specify
     * a bitrate, this value will be estimated from bitrate and file size.
     */
    int64_t duration;

    int64_t nb_frames;                 ///< number of frames in this stream if known or 0

    int disposition; /**< AV_DISPOSITION_* bit field */

    enum AVDiscard discard; ///< Selects which packets can be discarded at will and do not need to be demuxed.

    /**
     * sample aspect ratio (0 if unknown)
     * - encoding: Set by user.
     * - decoding: Set by libavformat.
     */
    AVRational sample_aspect_ratio;

    AVDictionary *metadata;

    /**
     * Average framerate
     *
     * - demuxing: May be set by libavformat when creating the stream or in
     *             avformat_find_stream_info().
     * - muxing: May be set by the caller before avformat_write_header().
     */
    AVRational avg_frame_rate;

    /**
     * For streams with AV_DISPOSITION_ATTACHED_PIC disposition, this packet
     * will contain the attached picture.
     *
     * decoding: set by libavformat, must not be modified by the caller.
     * encoding: unused
     */
    AVPacket attached_pic;

    /**
     * An array of side data that applies to the whole stream (i.e. the
     * container does not allow it to change between packets).
     *
     * There may be no overlap between the side data in this array and side data
     * in the packets. I.e. a given side data is either exported by the muxer
     * (demuxing) / set by the caller (muxing) in this array, then it never
     * appears in the packets, or the side data is exported / sent through
     * the packets (always in the first packet where the value becomes known or
     * changes), then it does not appear in this array.
     *
     * - demuxing: Set by libavformat when the stream is created.
     * - muxing: May be set by the caller before avformat_write_header().
     *
     * Freed by libavformat in avformat_free_context().
     *
     * @see av_format_inject_global_side_data()
     */
    AVPacketSideData *side_data;
    /**
     * The number of elements in the AVStream.side_data array.
     */
    int            nb_side_data;

    /**
     * Flags for the user to detect events happening on the stream. Flags must
     * be cleared by the user once the event has been handled.
     * A combination of AVSTREAM_EVENT_FLAG_*.
     */
    int event_flags;
#define AVSTREAM_EVENT_FLAG_METADATA_UPDATED 0x0001 ///< The call resulted in updated metadata.

    /*****************************************************************
     * All fields below this line are not part of the public API. They
     * may not be used outside of libavformat and can be changed and
     * removed at will.
     * New public fields should be added right above.
     *****************************************************************
     */

    /**
     * Stream information used internally by av_find_stream_info()
     */
#define MAX_STD_TIMEBASES (30*12+30+3+6)
    struct {
        int64_t last_dts;
        int64_t duration_gcd;
        int duration_count;
        int64_t rfps_duration_sum;
        double (*duration_error)[2][MAX_STD_TIMEBASES];
        int64_t codec_info_duration;
        int64_t codec_info_duration_fields;

        /**
         * 0  -> decoder has not been searched for yet.
         * >0 -> decoder found
         * <0 -> decoder with codec_id == -found_decoder has not been found
         */
        int found_decoder;

        int64_t last_duration;

        /**
         * Those are used for average framerate estimation.
         */
        int64_t fps_first_dts;
        int     fps_first_dts_idx;
        int64_t fps_last_dts;
        int     fps_last_dts_idx;

    } *info;

    int pts_wrap_bits; /**< number of bits in pts (used for wrapping control) */

    // Timestamp generation support:
    /**
     * Timestamp corresponding to the last dts sync point.
     *
     * Initialized when AVCodecParserContext.dts_sync_point >= 0 and
     * a DTS is received from the underlying container. Otherwise set to
     * AV_NOPTS_VALUE by default.
     */
    int64_t first_dts;
    int64_t cur_dts;
    int64_t last_IP_pts;
    int last_IP_duration;

    /**
     * Number of packets to buffer for codec probing
     */
    int probe_packets;

    /**
     * Number of frames that have been demuxed during av_find_stream_info()
     */
    int codec_info_nb_frames;

    /* av_read_frame() support */
    enum AVStreamParseType need_parsing;
    struct AVCodecParserContext *parser;

    /**
     * last packet in packet_buffer for this stream when muxing.
     */
    struct AVPacketList *last_in_packet_buffer;
    AVProbeData probe_data;
#define MAX_REORDER_DELAY 16
    int64_t pts_buffer[MAX_REORDER_DELAY+1];

    AVIndexEntry *index_entries; /**< Only used if the format does not
                                    support seeking natively. */
    int nb_index_entries;
    unsigned int index_entries_allocated_size;

    /**
     * Real base framerate of the stream.
     * This is the lowest framerate with which all timestamps can be
     * represented accurately (it is the least common multiple of all
     * framerates in the stream). Note, this value is just a guess!
     * For example, if the time base is 1/90000 and all frames have either
     * approximately 3600 or 1800 timer ticks, then r_frame_rate will be 50/1.
     *
     * Code outside avformat should access this field using:
     * av_stream_get/set_r_frame_rate(stream)
     */
    AVRational r_frame_rate;

    /**
     * Stream Identifier
     * This is the MPEG-TS stream identifier +1
     * 0 means unknown
     */
    int stream_identifier;

    int64_t interleaver_chunk_size;
    int64_t interleaver_chunk_duration;

    /**
     * stream probing state
     * -1   -> probing finished
     *  0   -> no probing requested
     * rest -> perform probing with request_probe being the minimum score to accept.
     * NOT PART OF PUBLIC API
     */
    int request_probe;
    /**
     * Indicates that everything up to the next keyframe
     * should be discarded.
     */
    int skip_to_keyframe;

    /**
     * Number of samples to skip at the start of the frame decoded from the next packet.
     */
    int skip_samples;

    /**
     * If not 0, the number of samples that should be skipped from the start of
     * the stream (the samples are removed from packets with pts==0, which also
     * assumes negative timestamps do not happen).
     * Intended for use with formats such as mp3 with ad-hoc gapless audio
     * support.
     */
    int64_t start_skip_samples;

    /**
     * If not 0, the first audio sample that should be discarded from the stream.
     * This is broken by design (needs global sample count), but can't be
     * avoided for broken by design formats such as mp3 with ad-hoc gapless
     * audio support.
     */
    int64_t first_discard_sample;

    /**
     * The sample after last sample that is intended to be discarded after
     * first_discard_sample. Works on frame boundaries only. Used to prevent
     * early EOF if the gapless info is broken (considered concatenated mp3s).
     */
    int64_t last_discard_sample;

    /**
     * Number of internally decoded frames, used internally in libavformat, do not access
     * its lifetime differs from info which is why it is not in that structure.
     */
    int nb_decoded_frames;

    /**
     * Timestamp offset added to timestamps before muxing
     * NOT PART OF PUBLIC API
     */
    int64_t mux_ts_offset;

    /**
     * Internal data to check for wrapping of the time stamp
     */
    int64_t pts_wrap_reference;

    /**
     * Options for behavior, when a wrap is detected.
     *
     * Defined by AV_PTS_WRAP_ values.
     *
     * If correction is enabled, there are two possibilities:
     * If the first time stamp is near the wrap point, the wrap offset
     * will be subtracted, which will create negative time stamps.
     * Otherwise the offset will be added.
     */
    int pts_wrap_behavior;

    /**
     * Internal data to prevent doing update_initial_durations() twice
     */
    int update_initial_durations_done;

    /**
     * Internal data to generate dts from pts
     */
    int64_t pts_reorder_error[MAX_REORDER_DELAY+1];
    uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1];

    /**
     * Internal data to analyze DTS and detect faulty mpeg streams
     */
    int64_t last_dts_for_order_check;
    uint8_t dts_ordered;
    uint8_t dts_misordered;

    /**
     * Internal data to inject global side data
     */
    int inject_global_side_data;

    /**
     * String containing paris of key and values describing recommended encoder configuration.
     * Paris are separated by ','.
     * Keys are separated from values by '='.
     */
    char *recommended_encoder_configuration;

    /**
     * display aspect ratio (0 if unknown)
     * - encoding: unused
     * - decoding: Set by libavformat to calculate sample_aspect_ratio internally
     */
    AVRational display_aspect_ratio;

    struct FFFrac *priv_pts;

    /**
     * An opaque field for libavformat internal usage.
     * Must not be accessed in any way by callers.
     */
    AVStreamInternal *internal;

    /*
     * Codec parameters associated with this stream. Allocated and freed by
     * libavformat in avformat_new_stream() and avformat_free_context()
     * respectively.
     *
     * - demuxing: filled by libavformat on stream creation or in
     *             avformat_find_stream_info()
     * - muxing: filled by the caller before avformat_write_header()
     */
    AVCodecParameters *codecpar;
} AVStream;

int index:该流的标示。
AVCodecContext *codec:该流的编解码器上下文。
AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间
start_time:流中第一个pts的时间。
nb_frames:这个流中的帧的数目。
AVRational avg_frame_rate;平均帧率。
int64_t duration:该流的时间长度
AVDictionary *metadata:元数据信息
AVPacket attached_pic:附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。

重要的数据结构框图如下:
这里写图片描述
下面来看这个函数的代码,源码在libavformat/utils.c文件中。
这个函数非常的长,我们分开来看.

三、函数源码分析

part 1 参数定义

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)
{
    int i, count = 0, ret = 0, j;
    int64_t read_size;
    AVStream *st;
    AVCodecContext *avctx;
    AVPacket pkt1, *pkt;
    int64_t old_offset  = avio_tell(ic->pb);
    // new streams might appear, no options for those
    int orig_nb_streams = ic->nb_streams;
    int flush_codecs;
    int64_t max_analyze_duration = ic->max_analyze_duration;
    int64_t max_stream_analyze_duration;
    int64_t max_subtitle_analyze_duration;
    int64_t probesize = ic->probesize;
    int eof_reached = 0;

    flush_codecs = probesize > 0;
    ...

这里定义了一些参数:
probesize就是探测的大小,为了获得流的信息,这个函数会尝试的读一些包出来,然后分析这些数据,probesize限制 了最大允许读出的数据的大小。
orig_nb_streams是这个文件中的流的数量。普通的电影会包含三个流:音频流,视频流和字幕流。

接着往下看。

part 2 设置max_stream_analyze_duration 等

    max_stream_analyze_duration = max_analyze_duration;
    max_subtitle_analyze_duration = max_analyze_duration;
    if (!max_analyze_duration) {
        max_stream_analyze_duration =
        max_analyze_duration        = 5*AV_TIME_BASE;
        max_subtitle_analyze_duration = 30*AV_TIME_BASE;
        if (!strcmp(ic->iformat->name, "flv"))
            max_stream_analyze_duration = 90*AV_TIME_BASE;
        if (!strcmp(ic->iformat->name, "mpeg") || !strcmp(ic->iformat->name, "mpegts"))
            max_stream_analyze_duration = 7*AV_TIME_BASE;
    }

这里定义了最大分析时长的限制。max_stream_analyze_duration 等于max_analyze_duration 等于30倍的timebase。此外,如果文件格式为flv或者Mpeg,那么他们会有各自的最大分析时长的限制。
继续往下看:

part 3 第一次遍历流


    for (i = 0; i < ic->nb_streams; i++) {
        const AVCodec *codec;
        AVDictionary *thread_opt = NULL;
        st = ic->streams[i];
        avctx = st->internal->avctx;

        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
            st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
/*            if (!st->time_base.num)
                st->time_base = */
            if (!avctx->time_base.num)
                avctx->time_base = st->time_base;
        }

        /* check if the caller has overridden the codec id */
#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS
        if (st->codec->codec_id != st->internal->orig_codec_id) {
            st->codecpar->codec_id   = st->codec->codec_id;
            st->codecpar->codec_type = st->codec->codec_type;
            st->internal->orig_codec_id = st->codec->codec_id;
        }
FF_ENABLE_DEPRECATION_WARNINGS
#endif
        // only for the split stuff
        if (!st->parser && !(ic->flags & AVFMT_FLAG_NOPARSE) && st->request_probe <= 0) {
            st->parser = av_parser_init(st->codecpar->codec_id);
            if (st->parser) {
                if (st->need_parsing == AVSTREAM_PARSE_HEADERS) {
                    st->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
                } else if (st->need_parsing == AVSTREAM_PARSE_FULL_RAW) {
                    st->parser->flags |= PARSER_FLAG_USE_CODEC_TS;
                }
            } else if (st->need_parsing) {
                av_log(ic, AV_LOG_VERBOSE, "parser not found for codec "
                       "%s, packets or times may be invalid.\n",
                       avcodec_get_name(st->codecpar->codec_id));
            }
        }

        if (st->codecpar->codec_id != st->internal->orig_codec_id)
            st->internal->orig_codec_id = st->codecpar->codec_id;

        ret = avcodec_parameters_to_context(avctx, st->codecpar);
        if (ret < 0)
            goto find_stream_info_err;
        if (st->request_probe <= 0)
            st->internal->avctx_inited = 1;

        codec = find_probe_decoder(ic, st, st->codecpar->codec_id);

        /* Force thread count to 1 since the H.264 decoder will not extract
         * SPS and PPS to extradata during multi-threaded decoding. */
        av_dict_set(options ? &options[i] : &thread_opt, "threads", "1", 0);

        if (ic->codec_whitelist)
            av_dict_set(options ? &options[i] : &thread_opt, "codec_whitelist", ic->codec_whitelist, 0);

        /* Ensure that subtitle_header is properly set. */
        if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE
            && codec && !avctx->codec) {
            if (avcodec_open2(avctx, codec, options ? &options[i] : &thread_opt) < 0)
                av_log(ic, AV_LOG_WARNING,
                       "Failed to open codec in av_find_stream_info\n");
        }

        // Try to just open decoders, in case this is enough to get parameters.
        if (!has_codec_parameters(st, NULL) && st->request_probe <= 0) {
            if (codec && !avctx->codec)
                if (avcodec_open2(avctx, codec, options ? &options[i] : &thread_opt) < 0)
                    av_log(ic, AV_LOG_WARNING,
                           "Failed to open codec in av_find_stream_info\n");
        }
        if (!options)
            av_dict_free(&thread_opt);
    }

这个函数有很多次遍历流的循环,这里是第一次。第一次循环做了如下事情:
1.获得编码器上下文环境avctx,设置avctx的time_base,code_id,codec_type,orig_codec_id。
2.如果解析器paser为空,那么会初始化解析器。
3.把解析器中的参数对应的拷贝到编解码器上下文环境中。调用的是avcodec_parameters_to_context函数,这个函数如下:

int avcodec_parameters_to_context(AVCodecContext *codec,
                                  const AVCodecParameters *par)
{
    codec->codec_type = par->codec_type;
    codec->codec_id   = par->codec_id;
    codec->codec_tag  = par->codec_tag;

    codec->bit_rate              = par->bit_rate;
    codec->bits_per_coded_sample = par->bits_per_coded_sample;
    codec->bits_per_raw_sample   = par->bits_per_raw_sample;
    codec->profile               = par->profile;
    codec->level                 = par->level;

    switch (par->codec_type) {
    case AVMEDIA_TYPE_VIDEO:
        codec->pix_fmt                = par->format;
        codec->width                  = par->width;
        codec->height                 = par->height;
        codec->field_order            = par->field_order;
        codec->color_range            = par->color_range;
        codec->color_primaries        = par->color_primaries;
        codec->color_trc              = par->color_trc;
        codec->colorspace             = par->color_space;
        codec->chroma_sample_location = par->chroma_location;
        codec->sample_aspect_ratio    = par->sample_aspect_ratio;
        codec->has_b_frames           = par->video_delay;
        break;
    case AVMEDIA_TYPE_AUDIO:
        codec->sample_fmt       = par->format;
        codec->channel_layout   = par->channel_layout;
        codec->channels         = par->channels;
        codec->sample_rate      = par->sample_rate;
        codec->block_align      = par->block_align;
        codec->frame_size       = par->frame_size;
        codec->delay            =
        codec->initial_padding  = par->initial_padding;
        codec->trailing_padding = par->trailing_padding;
        codec->seek_preroll     = par->seek_preroll;
        break;
    case AVMEDIA_TYPE_SUBTITLE:
        codec->width  = par->width;
        codec->height = par->height;
        break;
    }

可见就是根据编码器类型做对应的参数的拷贝。
4根据编码器ID查找编码器.
这里调用的是find_probe_decoder函数,该函数也定义在libavformat/utils.c中。可以看一下查找编解码器的过程:

static const AVCodec *find_probe_decoder(AVFormatContext *s, const AVStream *st, enum AVCodecID codec_id)
{
    const AVCodec *codec;

#if CONFIG_H264_DECODER
    /* Other parts of the code assume this decoder to be used for h264,
     * so force it if possible. */
    if (codec_id == AV_CODEC_ID_H264)
        return avcodec_find_decoder_by_name("h264");
#endif

    codec = find_decoder(s, st, codec_id);
    if (!codec)
        return NULL;

    if (codec->capabilities & AV_CODEC_CAP_AVOID_PROBING) {
        const AVCodec *probe_codec = NULL;
        while (probe_codec = av_codec_next(probe_codec)) {
            if (probe_codec->id == codec_id &&
                    av_codec_is_decoder(probe_codec) &&
                    !(probe_codec->capabilities & (AV_CODEC_CAP_AVOID_PROBING | AV_CODEC_CAP_EXPERIMENTAL))) {
                return probe_codec;
            }
        }
    }

    return codec;
}

调用find_decoder函数进一步查找:

static const AVCodec *find_decoder(AVFormatContext *s, const AVStream *st, enum AVCodecID codec_id)
{
#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS
    if (st->codec->codec)
        return st->codec->codec;
FF_ENABLE_DEPRECATION_WARNINGS
#endif

    switch (st->codecpar->codec_type) {
    case AVMEDIA_TYPE_VIDEO:
        if (s->video_codec)    return s->video_codec;
        break;
    case AVMEDIA_TYPE_AUDIO:
        if (s->audio_codec)    return s->audio_codec;
        break;
    case AVMEDIA_TYPE_SUBTITLE:
        if (s->subtitle_codec) return s->subtitle_codec;
        break;
    }

    return avcodec_find_decoder(codec_id);
}

如果编码器已经存在就根据编码器类型返回对应的编解码器,否则就根据id进行查找。avcodec_find_decoder函数还是在utils.c文件中:

AVCodec *avcodec_find_decoder(enum AVCodecID id)
{
    return find_encdec(id, 0);
}

find_encdec函数也是在utils.c文件中:

static AVCodec *find_encdec(enum AVCodecID id, int encoder)
{
    AVCodec *p, *experimental = NULL;
    p = first_avcodec;
    id= remap_deprecated_codec_id(id);
    while (p) {
        if ((encoder ? av_codec_is_encoder(p) : av_codec_is_decoder(p)) &&
            p->id == id) {
            if (p->capabilities & AV_CODEC_CAP_EXPERIMENTAL && !experimental) {
                experimental = p;
            } else
                return p;
        }
        p = p->next;
    }
    return experimental;
}

这个函数的功能就是遍历一个编解码器的链表,其首个结构是first_avcodec。编解码器链表是我们在av_register_all()函数中注册的。这个逐个匹配每一个编解码器的id,找到后返回对应的编解码器。
5.打开编解码器
打开使用的是avcodec_open2函数。这个函数我们在前一篇博客ffmpeg学习六:avcodec_open2函数源码分析一文中已经分析过了。

所以我们可以总结下第一次遍历所有的流所做的事情:初始化time_base,codec_id等参数,初始化解析器paser,然后对于每一个流,根据codec_id找对对应的编解码器,然后打开编解码器。也就是说,第一次遍历流,使得我们对于每一个流,都有一个编解码器可用。

part 4 第二次遍历流

    for (i = 0; i < ic->nb_streams; i++) {
#if FF_API_R_FRAME_RATE
        ic->streams[i]->info->last_dts = AV_NOPTS_VALUE;
#endif
        ic->streams[i]->info->fps_first_dts = AV_NOPTS_VALUE;
        ic->streams[i]->info->fps_last_dts  = AV_NOPTS_VALUE;
    }

第二次遍历流是在编解码器已经打开之后,对于每一个流,设置了一些参数。last_dts 是最有的解码时间。fps_first_dts和fps_last_dts 用于帧率的计算。

part 5 死循环

接下来的这个死循环代码很长。我们回顾一下之前的工作,我们遍历了两次流,设置好了每个流需要的编解码器和计算帧率的参数,初始化了每个流的解析器paser。在介绍这个函数的功能的时候,我们说这个函数会尝试的读一些数据进来,并解析这些数据,从这些数据中进一步获得详细的流的信息。到目前为止我们并没有读任何数据进来,那么接下来,想必就是读数据进来并解码分析数据流了吧。

    read_size = 0;
    for (;;) {
        int analyzed_all_streams;
        if (ff_check_interrupt(&ic->interrupt_callback)) {
            ret = AVERROR_EXIT;
            av_log(ic, AV_LOG_DEBUG, "interrupted\n");
            break;
        }

        /* check if one codec still needs to be handled */
        for (i = 0; i < ic->nb_streams; i++) {
            int fps_analyze_framecount = 20;

            st = ic->streams[i];
            if (!has_codec_parameters(st, NULL))
                break;
            /* If the timebase is coarse (like the usual millisecond precision
             * of mkv), we need to analyze more frames to reliably arrive at
             * the correct fps. */
            if (av_q2d(st->time_base) > 0.0005)
                fps_analyze_framecount *= 2;
            if (!tb_unreliable(st->internal->avctx))
                fps_analyze_framecount = 0;
            if (ic->fps_probe_size >= 0)
                fps_analyze_framecount = ic->fps_probe_size;
            if (st->disposition & AV_DISPOSITION_ATTACHED_PIC)
                fps_analyze_framecount = 0;
            /* variable fps and no guess at the real fps */
            if (!(st->r_frame_rate.num && st->avg_frame_rate.num) &&
                st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                int count = (ic->iformat->flags & AVFMT_NOTIMESTAMPS) ?
                    st->info->codec_info_duration_fields/2 :
                    st->info->duration_count;
                if (count < fps_analyze_framecount)
                    break;
            }
            if (st->parser && st->parser->parser->split &&
                !st->internal->avctx->extradata)
                break;
            if (st->first_dts == AV_NOPTS_VALUE &&
                !(ic->iformat->flags & AVFMT_NOTIMESTAMPS) &&
                st->codec_info_nb_frames < ((st->disposition & AV_DISPOSITION_ATTACHED_PIC) ? 1 : ic->max_ts_probe) &&
                (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
                 st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO))
                break;
        }
        analyzed_all_streams = 0;
        if (i == ic->nb_streams) {
            analyzed_all_streams = 1;
            /* NOTE: If the format has no header, then we need to read some
             * packets to get most of the streams, so we cannot stop here. */
            if (!(ic->ctx_flags & AVFMTCTX_NOHEADER)) {
                /* If we found the info for all the codecs, we can stop. */
                ret = count;
                av_log(ic, AV_LOG_DEBUG, "All info found\n");
                flush_codecs = 0;
                break;
            }
        }
        /* We did not get all the codec info, but we read too much data. */
        if (read_size >= probesize) {
            ret = count;
            av_log(ic, AV_LOG_DEBUG,
                   "Probe buffer size limit of %"PRId64" bytes reached\n", probesize);
            for (i = 0; i < ic->nb_streams; i++)
                if (!ic->streams[i]->r_frame_rate.num &&
                    ic->streams[i]->info->duration_count <= 1 &&
                    ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
                    strcmp(ic->iformat->name, "image2"))
                    av_log(ic, AV_LOG_WARNING,
                           "Stream #%d: not enough frames to estimate rate; "
                           "consider increasing probesize\n", i);
            break;
        }

        /* NOTE: A new stream can be added there if no header in file
         * (AVFMTCTX_NOHEADER). */
        ret = read_frame_internal(ic, &pkt1);
        if (ret == AVERROR(EAGAIN))
            continue;

        if (ret < 0) {
            /* EOF or error*/
            eof_reached = 1;
            break;
        }

        pkt = &pkt1;

        if (!(ic->flags & AVFMT_FLAG_NOBUFFER)) {
            ret = add_to_pktbuf(&ic->internal->packet_buffer, pkt,
                                &ic->internal->packet_buffer_end, 0);
            if (ret < 0)
                goto find_stream_info_err;
        }

        st = ic->streams[pkt->stream_index];
        if (!(st->disposition & AV_DISPOSITION_ATTACHED_PIC))
            read_size += pkt->size;

        avctx = st->internal->avctx;
        if (!st->internal->avctx_inited) {
            ret = avcodec_parameters_to_context(avctx, st->codecpar);
            if (ret < 0)
                goto find_stream_info_err;
            st->internal->avctx_inited = 1;
        }

        if (pkt->dts != AV_NOPTS_VALUE && st->codec_info_nb_frames > 1) {
            /* check for non-increasing dts */
            if (st->info->fps_last_dts != AV_NOPTS_VALUE &&
                st->info->fps_last_dts >= pkt->dts) {
                av_log(ic, AV_LOG_DEBUG,
                       "Non-increasing DTS in stream %d: packet %d with DTS "
                       "%"PRId64", packet %d with DTS %"PRId64"\n",
                       st->index, st->info->fps_last_dts_idx,
                       st->info->fps_last_dts, st->codec_info_nb_frames,
                       pkt->dts);
                st->info->fps_first_dts =
                st->info->fps_last_dts  = AV_NOPTS_VALUE;
            }
            /* Check for a discontinuity in dts. If the difference in dts
             * is more than 1000 times the average packet duration in the
             * sequence, we treat it as a discontinuity. */
            if (st->info->fps_last_dts != AV_NOPTS_VALUE &&
                st->info->fps_last_dts_idx > st->info->fps_first_dts_idx &&
                (pkt->dts - st->info->fps_last_dts) / 1000 >
                (st->info->fps_last_dts     - st->info->fps_first_dts) /
                (st->info->fps_last_dts_idx - st->info->fps_first_dts_idx)) {
                av_log(ic, AV_LOG_WARNING,
                       "DTS discontinuity in stream %d: packet %d with DTS "
                       "%"PRId64", packet %d with DTS %"PRId64"\n",
                       st->index, st->info->fps_last_dts_idx,
                       st->info->fps_last_dts, st->codec_info_nb_frames,
                       pkt->dts);
                st->info->fps_first_dts =
                st->info->fps_last_dts  = AV_NOPTS_VALUE;
            }

            /* update stored dts values */
            if (st->info->fps_first_dts == AV_NOPTS_VALUE) {
                st->info->fps_first_dts     = pkt->dts;
                st->info->fps_first_dts_idx = st->codec_info_nb_frames;
            }
            st->info->fps_last_dts     = pkt->dts;
            st->info->fps_last_dts_idx = st->codec_info_nb_frames;
        }
        if (st->codec_info_nb_frames>1) {
            int64_t t = 0;
            int64_t limit;

            if (st->time_base.den > 0)
                t = av_rescale_q(st->info->codec_info_duration, st->time_base, AV_TIME_BASE_Q);
            if (st->avg_frame_rate.num > 0)
                t = FFMAX(t, av_rescale_q(st->codec_info_nb_frames, av_inv_q(st->avg_frame_rate), AV_TIME_BASE_Q));

            if (   t == 0
                && st->codec_info_nb_frames>30
                && st->info->fps_first_dts != AV_NOPTS_VALUE
                && st->info->fps_last_dts  != AV_NOPTS_VALUE)
                t = FFMAX(t, av_rescale_q(st->info->fps_last_dts - st->info->fps_first_dts, st->time_base, AV_TIME_BASE_Q));

            if (analyzed_all_streams)                                limit = max_analyze_duration;
            else if (avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) limit = max_subtitle_analyze_duration;
            else                                                     limit = max_stream_analyze_duration;

            if (t >= limit) {
                av_log(ic, AV_LOG_VERBOSE, "max_analyze_duration %"PRId64" reached at %"PRId64" microseconds st:%d\n",
                       limit,
                       t, pkt->stream_index);
                if (ic->flags & AVFMT_FLAG_NOBUFFER)
                    av_packet_unref(pkt);
                break;
            }
            if (pkt->duration) {
                if (avctx->codec_type == AVMEDIA_TYPE_SUBTITLE && pkt->pts != AV_NOPTS_VALUE && pkt->pts >= st->start_time) {
                    st->info->codec_info_duration = FFMIN(pkt->pts - st->start_time, st->info->codec_info_duration + pkt->duration);
                } else
                    st->info->codec_info_duration += pkt->duration;
                st->info->codec_info_duration_fields += st->parser && st->need_parsing && avctx->ticks_per_frame ==2 ? st->parser->repeat_pict + 1 : 2;
            }
        }
#if FF_API_R_FRAME_RATE
        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
            ff_rfps_add_frame(ic, st, pkt->dts);
#endif
        if (st->parser && st->parser->parser->split && !avctx->extradata) {
            int i = st->parser->parser->split(avctx, pkt->data, pkt->size);
            if (i > 0 && i < FF_MAX_EXTRADATA_SIZE) {
                avctx->extradata_size = i;
                avctx->extradata      = av_mallocz(avctx->extradata_size +
                                                   AV_INPUT_BUFFER_PADDING_SIZE);
                if (!avctx->extradata)
                    return AVERROR(ENOMEM);
                memcpy(avctx->extradata, pkt->data,
                       avctx->extradata_size);
            }
        }

        /* If still no information, we try to open the codec and to
         * decompress the frame. We try to avoid that in most cases as
         * it takes longer and uses more memory. For MPEG-4, we need to
         * decompress for QuickTime.
         *
         * If AV_CODEC_CAP_CHANNEL_CONF is set this will force decoding of at
         * least one frame of codec data, this makes sure the codec initializes
         * the channel configuration and does not only trust the values from
         * the container. */
        try_decode_frame(ic, st, pkt,
                         (options && i < orig_nb_streams) ? &options[i] : NULL);

        if (ic->flags & AVFMT_FLAG_NOBUFFER)
            av_packet_unref(pkt);

        st->codec_info_nb_frames++;
        count++;
    }

这个死循环做了如下事情:
1.检查用户有没有请求中断。如果有中断请求,就调用中断回调方法,并且函数返回。
2.再一次遍历流,检查是不是还有编解码器需要进一步处理。这里会检查编解码器的各个参数,如果这些参数都就绪,那么就会返回了,也就没有必要再做进一步分析了。如果还有些编解码器的信息不全,那么这里会继续向下执行。
3.读一帧数据进来。使用read_frame_internal函数。这个函数很复杂,以后在分析。
4.把读出来的数据添加到缓冲区。使用的是add_to_pktbuf函数。然后更新读到的总的数据的大小:read_size += pkt->size;
5.再次更新编解码器上下文环境的参数。调用的是avcodec_parameters_to_context函数,这个函数我们已经分析过了。
6.检查dts的连续性。 如果dts中的差异大于序列中平均分组持续时间的1000倍,我们将其视为不连续。
代码为:

            if (st->info->fps_last_dts != AV_NOPTS_VALUE &&
                st->info->fps_last_dts_idx > st->info->fps_first_dts_idx &&
                (pkt->dts - st->info->fps_last_dts) / 1000 >
                (st->info->fps_last_dts     - st->info->fps_first_dts) /
                (st->info->fps_last_dts_idx - st->info->fps_first_dts_idx)) {
                av_log(ic, AV_LOG_WARNING,
                       "DTS discontinuity in stream %d: packet %d with DTS "
                       "%"PRId64", packet %d with DTS %"PRId64"\n",
                       st->index, st->info->fps_last_dts_idx,
                       st->info->fps_last_dts, st->codec_info_nb_frames,
                       pkt->dts);
                st->info->fps_first_dts =
                st->info->fps_last_dts  = AV_NOPTS_VALUE;
            }

7.更新存储的dts的值。代码为:

            if (st->info->fps_first_dts == AV_NOPTS_VALUE) {
                st->info->fps_first_dts     = pkt->dts;
                st->info->fps_first_dts_idx = st->codec_info_nb_frames;
            }

8.调用解析器的aplit方法。加入我们的解析器是h.264,那么这个解析器会像下面这样:

AVCodecParser ff_h264_parser = {
    .codec_ids      = { AV_CODEC_ID_H264 },
    .priv_data_size = sizeof(H264ParseContext),
    .parser_init    = init,
    .parser_parse   = h264_parse,
    .parser_close   = h264_close,
    .split          = h264_split,
};

这个split方法暂时看不懂,以后再来解析。
9.如果这个时候,还是没有找到足够的信息,那么就会尝试解压一些数据出来并做分析。
首先看注释:
如果仍然没有信息,我们尝试打开编解码器并且解压缩帧。 我们需要在大多数情况下尽量避免做这样的事情,因为很需要很多的时间,并使用更多的内存。 对于MPEG-4,我们需要解压QuickTime。
如果设置AV_CODEC_CAP_CHANNEL_CONF,这将强制解码至少一帧编解码器数据,这确保编解码器初始化通道配置,并且不仅信任来自容器的值
尝试解压一帧的数据使用的是try_decode_frame函数。有些参数,如vidoe的pix_fmt是需要调用h264_decode_frame才可以获取其pix_fmt的。
总结下这个死循环做的工作:其中会检测是不是所有流的信息都已经完备,如果完备就返回,如果完备,就尝试的解压一帧的数据。并从中获取pix_fmt等必须通过解压一帧的数据才能获得的参数。

part 6 检查是否到达文件尾部

    if (eof_reached) {
        int stream_index;
        for (stream_index = 0; stream_index < ic->nb_streams; stream_index++) {
            st = ic->streams[stream_index];
            avctx = st->internal->avctx;
            if (!has_codec_parameters(st, NULL)) {
                const AVCodec *codec = find_probe_decoder(ic, st, st->codecpar->codec_id);
                if (codec && !avctx->codec) {
                    if (avcodec_open2(avctx, codec, (options && stream_index < orig_nb_streams) ? &options[stream_index] : NULL) < 0)
                        av_log(ic, AV_LOG_WARNING,
                            "Failed to open codec in av_find_stream_info\n");
                }
            }

            // EOF already reached while reading the stream above.
            // So continue with reoordering DTS with whatever delay we have.
            if (ic->internal->packet_buffer && !has_decode_delay_been_guessed(st)) {
                update_dts_from_pts(ic, stream_index, ic->internal->packet_buffer);
            }
        }
    }

如果到了文件尾部,又会遍历一次流,检测其编解码器参数,如果其参数不完整,就会再次调用avcodec_open2来初始化编解码器的各个参数。

part 7 刷新解码器

    if (flush_codecs) {
        AVPacket empty_pkt = { 0 };
        int err = 0;
        av_init_packet(&empty_pkt);

        for (i = 0; i < ic->nb_streams; i++) {

            st = ic->streams[i];

            /* flush the decoders */
            if (st->info->found_decoder == 1) {
                do {
                    err = try_decode_frame(ic, st, &empty_pkt,
                                            (options && i < orig_nb_streams)
                                            ? &options[i] : NULL);
                } while (err > 0 && !has_codec_parameters(st, NULL));

                if (err < 0) {
                    av_log(ic, AV_LOG_INFO,
                        "decoding for stream %d failed\n", st->index);
                }
            }
        }
    }

有一些帧可能在缓存区中,需要把它们flush掉。

part 8 关闭可能可打开的编解码器

    for (i = 0; i < ic->nb_streams; i++) {
        st = ic->streams[i];
        avcodec_close(st->internal->avctx);
    }

再次遍历流,逐个调用avcodec_close方法来关闭流。

part 9 计算rfps

ff_rfps_calculate(ic);

part10 再次遍历流

    for (i = 0; i < ic->nb_streams; i++) {
        st = ic->streams[i];
        avctx = st->internal->avctx;
        if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
            if (avctx->codec_id == AV_CODEC_ID_RAWVIDEO && !avctx->codec_tag && !avctx->bits_per_coded_sample) {
                uint32_t tag= avcodec_pix_fmt_to_codec_tag(avctx->pix_fmt);
                if (avpriv_find_pix_fmt(avpriv_get_raw_pix_fmt_tags(), tag) == avctx->pix_fmt)
                    avctx->codec_tag= tag;
            }

            /* estimate average framerate if not set by demuxer */
            if (st->info->codec_info_duration_fields &&
                !st->avg_frame_rate.num &&
                st->info->codec_info_duration) {
                int best_fps      = 0;
                double best_error = 0.01;

                if (st->info->codec_info_duration        >= INT64_MAX / st->time_base.num / 2||
                    st->info->codec_info_duration_fields >= INT64_MAX / st->time_base.den ||
                    st->info->codec_info_duration        < 0)
                    continue;
                av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
                          st->info->codec_info_duration_fields * (int64_t) st->time_base.den,
                          st->info->codec_info_duration * 2 * (int64_t) st->time_base.num, 60000);

                /* Round guessed framerate to a "standard" framerate if it's
                 * within 1% of the original estimate. */
                for (j = 0; j < MAX_STD_TIMEBASES; j++) {
                    AVRational std_fps = { get_std_framerate(j), 12 * 1001 };
                    double error       = fabs(av_q2d(st->avg_frame_rate) /
                                              av_q2d(std_fps) - 1);

                    if (error < best_error) {
                        best_error = error;
                        best_fps   = std_fps.num;
                    }
                }
                if (best_fps)
                    av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
                              best_fps, 12 * 1001, INT_MAX);
            }

            if (!st->r_frame_rate.num) {
                if (    avctx->time_base.den * (int64_t) st->time_base.num
                    <= avctx->time_base.num * avctx->ticks_per_frame * (int64_t) st->time_base.den) {
                    st->r_frame_rate.num = avctx->time_base.den;
                    st->r_frame_rate.den = avctx->time_base.num * avctx->ticks_per_frame;
                } else {
                    st->r_frame_rate.num = st->time_base.den;
                    st->r_frame_rate.den = st->time_base.num;
                }
            }
            if (st->display_aspect_ratio.num && st->display_aspect_ratio.den) {
                AVRational hw_ratio = { avctx->height, avctx->width };
                st->sample_aspect_ratio = av_mul_q(st->display_aspect_ratio,
                                                   hw_ratio);
            }
        } else if (avctx->codec_type == AVMEDIA_TYPE_AUDIO) {
            if (!avctx->bits_per_coded_sample)
                avctx->bits_per_coded_sample =
                    av_get_bits_per_sample(avctx->codec_id);
            // set stream disposition based on audio service type
            switch (avctx->audio_service_type) {
            case AV_AUDIO_SERVICE_TYPE_EFFECTS:
                st->disposition = AV_DISPOSITION_CLEAN_EFFECTS;
                break;
            case AV_AUDIO_SERVICE_TYPE_VISUALLY_IMPAIRED:
                st->disposition = AV_DISPOSITION_VISUAL_IMPAIRED;
                break;
            case AV_AUDIO_SERVICE_TYPE_HEARING_IMPAIRED:
                st->disposition = AV_DISPOSITION_HEARING_IMPAIRED;
                break;
            case AV_AUDIO_SERVICE_TYPE_COMMENTARY:
                st->disposition = AV_DISPOSITION_COMMENT;
                break;
            case AV_AUDIO_SERVICE_TYPE_KARAOKE:
                st->disposition = AV_DISPOSITION_KARAOKE;
                break;
            }
        }
    }

这个循环用来处理音频流和视频流。
对视频流而言,首先会获得原始数据的图像格式,然后据此找到对应的编解码器的tag。之后会计算平均帧率。
对音频流而言,基于音频流的服务类型初始化disposition。disposition的作用暂时不知。

part 11 计算时间相关的参数

    if (probesize)
        estimate_timings(ic, old_offset);

estimate_timings函数如下:

static void estimate_timings(AVFormatContext *ic, int64_t old_offset)
{
    int64_t file_size;

    /* get the file size, if possible */
    if (ic->iformat->flags & AVFMT_NOFILE) {
        file_size = 0;
    } else {
        file_size = avio_size(ic->pb);
        file_size = FFMAX(0, file_size);
    }

    if ((!strcmp(ic->iformat->name, "mpeg") ||
         !strcmp(ic->iformat->name, "mpegts")) &&
        file_size && ic->pb->seekable) {
        /* get accurate estimate from the PTSes */
        estimate_timings_from_pts(ic, old_offset);
        ic->duration_estimation_method = AVFMT_DURATION_FROM_PTS;
    } else if (has_duration(ic)) {
        /* at least one component has timings - we use them for all
         * the components */
        fill_all_stream_timings(ic);
        ic->duration_estimation_method = AVFMT_DURATION_FROM_STREAM;
    } else {
        /* less precise: use bitrate info */
        estimate_timings_from_bit_rate(ic);
        ic->duration_estimation_method = AVFMT_DURATION_FROM_BITRATE;
    }
    update_stream_timings(ic);

    {
        int i;
        AVStream av_unused *st;
        for (i = 0; i < ic->nb_streams; i++) {
            st = ic->streams[i];
            av_log(ic, AV_LOG_TRACE, "stream %d: start_time: %0.3f duration: %0.3f\n", i,
                   (double) st->start_time * av_q2d(st->time_base),
                   (double) st->duration   * av_q2d(st->time_base));
        }
        av_log(ic, AV_LOG_TRACE,
                "format: start_time: %0.3f duration: %0.3f bitrate=%"PRId64" kb/s\n",
                (double) ic->start_time / AV_TIME_BASE,
                (double) ic->duration   / AV_TIME_BASE,
                (int64_t)ic->bit_rate / 1000);
    }
}

主要是调用update_stream_timings函数更新流的时间相关的参数,之后的代码块是打印日志。
update_stream_timings函数定义在libavfomat/utils.c中。

/**
 * Estimate the stream timings from the one of each components.
 *
 * Also computes the global bitrate if possible.
 */
static void update_stream_timings(AVFormatContext *ic)
{
    int64_t start_time, start_time1, start_time_text, end_time, end_time1, end_time_text;
    int64_t duration, duration1, filesize;
    int i;
    AVStream *st;
    AVProgram *p;

    start_time = INT64_MAX;
    start_time_text = INT64_MAX;
    end_time   = INT64_MIN;
    end_time_text   = INT64_MIN;
    duration   = INT64_MIN;
    for (i = 0; i < ic->nb_streams; i++) {
        st = ic->streams[i];
        if (st->start_time != AV_NOPTS_VALUE && st->time_base.den) {
            start_time1 = av_rescale_q(st->start_time, st->time_base,
                                       AV_TIME_BASE_Q);
            if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE || st->codecpar->codec_type == AVMEDIA_TYPE_DATA) {
                if (start_time1 < start_time_text)
                    start_time_text = start_time1;
            } else
                start_time = FFMIN(start_time, start_time1);
            end_time1 = av_rescale_q_rnd(st->duration, st->time_base,
                                         AV_TIME_BASE_Q,
                                         AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
            if (end_time1 != AV_NOPTS_VALUE && (end_time1 > 0 ? start_time1 <= INT64_MAX - end_time1 : start_time1 >= INT64_MIN - end_time1)) {
                end_time1 += start_time1;
                if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE || st->codecpar->codec_type == AVMEDIA_TYPE_DATA)
                    end_time_text = FFMAX(end_time_text, end_time1);
                else
                    end_time = FFMAX(end_time, end_time1);
            }
            for (p = NULL; (p = av_find_program_from_stream(ic, p, i)); ) {
                if (p->start_time == AV_NOPTS_VALUE || p->start_time > start_time1)
                    p->start_time = start_time1;
                if (p->end_time < end_time1)
                    p->end_time = end_time1;
            }
        }
        if (st->duration != AV_NOPTS_VALUE) {
            duration1 = av_rescale_q(st->duration, st->time_base,
                                     AV_TIME_BASE_Q);
            duration  = FFMAX(duration, duration1);
        }
    }
    if (start_time == INT64_MAX || (start_time > start_time_text && start_time - start_time_text < AV_TIME_BASE))
        start_time = start_time_text;
    else if (start_time > start_time_text)
        av_log(ic, AV_LOG_VERBOSE, "Ignoring outlier non primary stream starttime %f\n", start_time_text / (float)AV_TIME_BASE);

    if (end_time == INT64_MIN || (end_time < end_time_text && end_time_text - end_time < AV_TIME_BASE)) {
        end_time = end_time_text;
    } else if (end_time < end_time_text) {
        av_log(ic, AV_LOG_VERBOSE, "Ignoring outlier non primary stream endtime %f\n", end_time_text / (float)AV_TIME_BASE);
    }

    if (start_time != INT64_MAX) {
        ic->start_time = start_time;
        if (end_time != INT64_MIN) {
            if (ic->nb_programs > 1) {
                for (i = 0; i < ic->nb_programs; i++) {
                    p = ic->programs[i];
                    if (p->start_time != AV_NOPTS_VALUE && p->end_time > p->start_time)
                        duration = FFMAX(duration, p->end_time - p->start_time);
                }
            } else
                duration = FFMAX(duration, end_time - start_time);
        }
    }
    if (duration != INT64_MIN && duration > 0 && ic->duration == AV_NOPTS_VALUE) {
        ic->duration = duration;
    }
    if (ic->pb && (filesize = avio_size(ic->pb)) > 0 && ic->duration > 0) {
        /* compute the bitrate */
        double bitrate = (double) filesize * 8.0 * AV_TIME_BASE /
                         (double) ic->duration;
        if (bitrate >= 0 && bitrate <= INT64_MAX)
            ic->bit_rate = bitrate;
    }
}

时间相关的计算非常复杂,是在看不懂。这里还计算了波特率: bitrate = (double) filesize * 8.0 * AV_TIME_BASE / (double) ic->duration;
这里很好理解。filesize*8是这个文件的所有bit数。一字节=8bit。
然后比特率=bit数/时间。
这里时间为ic->duration/AV_TIME_BASE 。

part 12 更新数据

   /* update the stream parameters from the internal codec contexts */
    for (i = 0; i < ic->nb_streams; i++) {
        st = ic->streams[i];

        if (st->internal->avctx_inited) {
            int orig_w = st->codecpar->width;
            int orig_h = st->codecpar->height;
            ret = avcodec_parameters_from_context(st->codecpar, st->internal->avctx);
            if (ret < 0)
                goto find_stream_info_err;
            // The decoder might reduce the video size by the lowres factor.
            if (av_codec_get_lowres(st->internal->avctx) && orig_w) {
                st->codecpar->width = orig_w;
                st->codecpar->height = orig_h;
            }
        }

#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS
        ret = avcodec_parameters_to_context(st->codec, st->codecpar);
        if (ret < 0)
            goto find_stream_info_err;

        // The old API (AVStream.codec) "requires" the resolution to be adjusted
        // by the lowres factor.
        if (av_codec_get_lowres(st->internal->avctx) && st->internal->avctx->width) {
            av_codec_set_lowres(st->codec, av_codec_get_lowres(st->internal->avctx));
            st->codec->width = st->internal->avctx->width;
            st->codec->height = st->internal->avctx->height;
        }

        if (st->codec->codec_tag != MKTAG('t','m','c','d')) {
            st->codec->time_base = st->internal->avctx->time_base;
            st->codec->ticks_per_frame = st->internal->avctx->ticks_per_frame;
        }
        st->codec->framerate = st->avg_frame_rate;

        if (st->internal->avctx->subtitle_header) {
            st->codec->subtitle_header = av_malloc(st->internal->avctx->subtitle_header_size);
            if (!st->codec->subtitle_header)
                goto find_stream_info_err;
            st->codec->subtitle_header_size = st->internal->avctx->subtitle_header_size;
            memcpy(st->codec->subtitle_header, st->internal->avctx->subtitle_header,
                   st->codec->subtitle_header_size);
        }

        // Fields unavailable in AVCodecParameters
        st->codec->coded_width = st->internal->avctx->coded_width;
        st->codec->coded_height = st->internal->avctx->coded_height;
        st->codec->properties = st->internal->avctx->properties;
FF_ENABLE_DEPRECATION_WARNINGS
#endif

        st->internal->avctx_inited = 0;

最后就是更新各个结构的数据了。AVStream中的AVCodecContext结构体,以及AVStream中AVStreamInternal中的AVCodecContext结构体的数据要统一起来。AVStream中的AVCodecParameters和AVStream中AVStreamInternal中的AVCodecContext结构体的数据也要统一起来。以上主要就是跟新这几个结构体中的数据。

四、总结

最后总结一下吧:
avformat_open_input函数会读文件头,对mp4文件而言,它会解析所有的box。但它知识把读到的结果保存在对应的数据结构下。这个时候,AVStream中的很多字段都是空白的。
avformat_find_stream_info则检测这些重要字段,如果是空白的,就设法填充它们。因为我们解析文件头的时候,已经掌握了大量的信息,avformat_find_stream_info就是通过这些信息来填充自己的成员,当重要的成员都填充完毕后,该函数就返回了。这中情况下,该函数效率很高。但对于某些文件,单纯的从文件头中获取信息是不够的,比如vidoe的pix_fmt是需要调用h264_decode_frame才可以获取其pix_fmt的。因此,这个时候这个函数就会读一些数据进来,然后分析这些数据,并尝试解码这些数据,最终从中提取到所需要的信息。在所有的信息都已经获取到以后,avformat_find_stream_info函数会计算start_time,波特率等信息,其中时间相关的计算很复杂,没能看懂,以后再研究吧。计算结束后会更新相关结构体的数据成员。
虽然由于能力限制,没能够彻底搞懂这个函数的方方面面,但是,通过这次分析,更好的理解了这个函数的功能,对这个函数做的事情有了一定的了解。这也是这次分析的主要收获了,至于那些搞不懂的,只能留着以后慢慢研究了。

作者:u011913612 发表于2016/12/14 17:33:17 原文链接
阅读:31 评论:0 查看评论

运维笔记23 (shell脚本,expect的简易用法)

$
0
0

概述:

shell是我们和linux接触的通道,灵活使用使用shell就能更好的掌控linux,想学好shell首先是要理解熟记很多的命令,这就相当于英语积累的单词一样,单词记得多了,英语自然就好了,但是想要真正变成英语达人还要了解些语法,shell也是一样,命令是单词,也有他的语法,我们这次主要讨论语法部分。

 expect是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。expect是不断发展的,随着时间的流逝,其功能越来越强大,已经成为系统管理员的的一个强大助手。简单的说,如果没有expect的话很多需要交互的场景我们就需要一直守在计算机前面进行输入,如果输入是主要步骤还好,我们输入完就结束任务了,那万一,我们有一个任务是在凌晨3点以root身份执行一个脚本呢,我们需要的输入只是一个密码,却要一直在电脑前等到3点,想想都很亏。

1 bash的一些内建参数

1.1 "!"的妙用

!*:上一个命令的所有参数

[root@foundation3 mnt]# ls -a -l
total 41616
drwxrwxrwx.  3 root root       84 Dec 13 17:16 .
。。。省略
[root@foundation3 mnt]# echo !*
echo -a -l
-a -l
!^:上一条命令的第一个参数

[root@foundation3 mnt]# ls -a -l
total 41616
drwxrwxrwx.  3 root root       84 Dec 13 17:16 .
。。。省略
[root@foundation3 mnt]# echo !^
echo -a
-a
!$:上一条命令的最后一个参数
[root@foundation3 mnt]# ls -a -l
total 41616
drwxrwxrwx.  3 root root       84 Dec 13 17:16 .
。。。省略
[root@foundation3 mnt]# echo !$
echo -l
-l
!#:意义不明。。。。

[root@foundation3 mnt]# echo !#
echo echo 
echo
!<comm>:<num>:显示上一个comm命令的第num个参数

[root@foundation3 mnt]# cp scan /media/
[root@foundation3 mnt]# echo !cp:2
echo /media/
/media/
[root@foundation3 mnt]# echo !cp:1
echo scan
scan
!(file):除去file代表的文件

[root@foundation3 tmp]# ls
file10.pdf  file1.txt  file3.pdf  file4.txt  file6.pdf  file7.txt  file9.pdf
file10.txt  file2.pdf  file3.txt  file5.pdf  file6.txt  file8.pdf  file9.txt
file1.pdf   file2.txt  file4.pdf  file5.txt  file7.pdf  file8.txt  file.pdf
[root@foundation3 tmp]# rm -rf !(*.pdf)
[root@foundation3 tmp]# ls
file10.pdf  file2.pdf  file4.pdf  file6.pdf  file8.pdf  file.pdf
file1.pdf   file3.pdf  file5.pdf  file7.pdf  file9.pdf
上面的演示就是把.pdf结尾以外的文件都删除。

1.2 shell的位置参数

位置参数的个数:$#(通常用来判断是否加入了参数)

某个位置参数:$0,$1,$2 ...

所有位置参数:$@,$*

退出的状态:$? (执行shell成功后退出状态是0,如果出现错误的话,退出状态就是非0,退出状态由exit num的num决定)
2.shell的test条件判断

test判断条件可以用[]代替,用法是完全一样的如下:

[root@foundation3 tmp]# a=1;b=2
[root@foundation3 tmp]# [ $a -eq $b ];echo $?
1
[root@foundation3 tmp]# test $a -eq $b;echo $?
1
上面的两种用法是完全一样的


2.1 字符比较:
使用=和!=
[root@foundation3 tmp]# str1="mo";str2="momo"
[root@foundation3 tmp]# [ $str1 != $str2 ];echo $?
0
2.2 数值比较:

-eq,-ne,-lt,-le,-gt,-ge

分别是相等(equal),不等(not equal),小于(less than),小于等于(less equal),大于(greater than),大于等于(greater equal)

和英语结合起来记忆就非常简单了。

2.3文件状态运算符

-b:块文件

-c:字符文件

-e:文件存在

-f:普通文件

-d:目录

-r:文件可读

-w:文件可写

-x:文件可执行

-s:文件大小非0为真

-L:文件为软链接的时候为真

2.3逻辑运算符

逻辑运算符有-o,-a,!,&&,||

这里也是经常出错的地方因为,有相当于两套逻辑运算,前面讲了那么多现在我们来练习一下。

练习1:

判断一个文件既是普通文件又是软链接文件

写法1

[root@foundation3 tmp]# [ -f b ] && [ -L b ];echo $?
0
[root@foundation3 tmp]# ll b
lrwxrwxrwx 1 root root 1 Dec 14 15:36 b -> a
写法2

[root@foundation3 tmp]# [ -f b -a -L b ];echo $? 
0

练习2:

判断一个数的范围(x>=3,x<=5)

写法1:

[root@foundation3 tmp]# [ $x -ge 3 ] && [ $x -le 5 ];echo $?
1

写法2:

[root@foundation3 tmp]# [ $x -ge 3 -a $x -le 5 ];echo $?
1
[root@foundation3 tmp]# [ 4 -ge 3 -a 4 -le 5 ];echo $?
0
[root@foundation3 tmp]# echo $x
10


3.shell的数值运算
这之前先理解下$与括号的用法

$():命令替换的意思与双反引号意思一样``。

${}:引用变量比如变量赋值的时候是a=1,使用的时候就要使用$a或者${a}。

$[]:表示数值运算

(()):表示数字运算,是bash的内建功能效率很高。

let num=1+2:表示数值运算

expr 1+2:命令表示数值运算

综上,总共有四种之多的方法表示数值运算,是不是有点记混了,不急咱们一个个看区别,多敲就会记住了。

$[]:

[root@foundation3 mnt]# echo $[1+2]
3
[root@foundation3 mnt]# echo $[$[1+2]+$[3+4]]
10
[root@foundation3 mnt]# echo [$[1+2]+$[3+4]]
[3+7]
[root@foundation3 mnt]# echo $[1+2]+$[3+4]
3+7

[root@foundation3 mnt]# echo $[$j+=1]
-bash: 3+=1: attempted assignment to non-variable (error token is "+=1")
[root@foundation3 mnt]# echo $[$j+1]
4
可见在[]中一定还要用$引用变量

(()):

[root@foundation3 mnt]# echo $((1+2))
3
[root@foundation3 mnt]# echo $j
6
[root@foundation3 mnt]# ((j++))
[root@foundation3 mnt]# echo $j
7
(())就不用$引用变量

let:

[root@foundation3 mnt]# let a=2
[root@foundation3 mnt]# echo $a
2
[root@foundation3 mnt]# let a=a+2
[root@foundation3 mnt]# echo $a
4
[root@foundation3 mnt]# let a++
[root@foundation3 mnt]# echo $a
5
let也不需要$引用
expr:

expr的功能很强大,我们只是简单用一下他的运算功能

由于expr是个命令所以输入的数值和运算符都要有空格,也就是通过命令行参数的方式传到expr里面

[root@foundation3 mnt]# expr  1 + 2
3
[root@foundation3 mnt]# expr $a + 1
1

4.if语句

if语句用来检查后面命令的退出值的,如果为true则运行then后的语句,一直到fi或者else。而且if经常和test搭配使用。

用一个题目来看下if的用法

如果http服务开启的话就关闭,如果httpd关闭则开启

if systemctl is-active httpd;then
    systemctl stop httpd
else 
    systemctl start httpd
fi
5.case语句

这个语句相当于简化了多分支if的写法,有时候需要多次判断一个值的情况,使用if写起来会有点繁琐,比如下面的情况。

#!/bin/bash

arg=$1
case "$arg" in
    apple )
        echo "apple"
        ;;
    orange )
        echo "orange"
        ;;
    banana )
        echo "banana"
        ;;
    pig )
        echo "pig"
        ;;
    * )
        echo "else"
        ;;
esac

6.expect脚本

expect脚本功能十分强大,但我们不用掌握太多,只用掌握如下的几条语句,暂时就够用了。

spawn:开启一个进程,接下来会对这个进程进行监控

set a ?:设置一个变量的值

[lindex $argv 0]:接收命令行传进来的参数

expect,send:这两条是核心命令了,其实就相当于对一个对话的处理

a:hello

b:nihao

a:goodbye

b:byebye

用脚本表示就是

expect "hello" {send "nihao"}

expect "goodbye" {send "byebye"}

最后有一个关于expect的小脚本,需求是,扫描你当前所在局域网的主机,如果可以ping通的,就要登陆进去,并且创建好一个用户,如果这个用户存在了,不要更改人家的密码。

代码如下:

scan (expect脚本):

#!/usr/bin/expect
set timeout 1
set RET 0
set ipaddr [lindex $argv 0]
set password [lindex $argv 1]
set user "mo"
set u_pass "123"
spawn ssh root@$ipaddr

expect  {
    "yes/no"
    {send "yes\r";exp_continue}
    "password"
    {send "$password\r"}
}
expect "*Permission denied" {set RET 1}

expect "]#" {send "id $user\r"}
expect {
    "uid"
    {send "exit\r"}
    "no such user"
    {send "useradd $user\r"}
}
#expect "]#" {send "passwd $user\r";exp_continue}
#expect "New password:" {send "$u_pass\r";exp_continue}
#expect "Retype new password:" {send "$u_pass\r";exp_continue}
expect "]#" {
        send "echo $u_pass | passwd --stdin $user\r"
            }
expect "]#" { send "exit\r"}

scan_class.sh(shell脚本)

#!/bin/bash
db_name="scan_data"
ip_pre="172.25.254."
check_data()
{
    mysql -uscan -predhat -e "select password from scan_data.user where ipaddr=\"$1\";" -NE | grep -v "row"
}

if [ -z `mysql -uscan -predhat -e "show databases;" -NE | grep $db_name` ];then
    echo "database not exist"
fi
if [ ! -e /mnt/scan ];then
    echo "scan shell is not found"
fi
for i in {10..11};do
    ip=${ip_pre}$i
    if ping -c1 -w1 >/dev/null $ip;then
        pass=`check_data $ip`
        /mnt/scan $ip $pass | grep -vE "^spawn|^root|^Last|[[]root|^logout|^Connect|^The|^ECDSA|^Warning|^Are" | sed "s/^uid.*/user is already exist in $ip/g" | sed "s/.*no such user.*$/user add success/g" | sed "s/^Permission denied.*/$ip password is wrong/g"
    fi
done
简单测试一下

[root@foundation3 mnt]# ./scan_class.sh 
172.25.254.10 password is wrong
user is already exist in 172.25.254.11
我对我的两台虚拟机进行了测试第一台密码错误了,第二台已经拥有了那个用户。

[root@foundation3 mnt]# ./scan_class.sh 
user add success
user add success
添加成功的信息,额。。。好像没有ip很别扭,加上吧。

[root@foundation3 mnt]# ./scan_class.sh 
172.25.254.10 user add success
172.25.254.11 user add success

这个脚本的主机账户信息是存储在数据库中的,所以每次只要通过sql语句查找ip对应的密码即可连接。但是由于在shell中为了防止每次查找数据都输入密码,所以选择了将数据可密码暴露的形式,为了安全起见,特意创建一个新用户来作为这个脚本的查询用户。

数据库如下:

+------+---------------+----------+
| uid  | ipaddr        | password |
+------+---------------+----------+
| 0    | 172.25.254.10 | redhat   |
| 1    | 172.25.254.11 | redhat   |
+------+---------------+----------+










作者:No_red 发表于2016/12/14 17:33:43 原文链接
阅读:34 评论:0 查看评论

Hbase操作table常见方法示例

$
0
0


首先上我的输出类:


/**
 * 功能:电池历史数据数据结构
 * Created by liuhuichao on 2016/12/5.
 */
public class ResBatteryDataHistory implements Serializable {


    private String batteryNo;

    private Integer batteryType;

    private Float voltageDeviation;

    private Float totalVoltage;

    private String createTime;

    private Integer createUser;

    private Integer source;

    private Float vol1;

    private Float vol2;

    private Float vol3;

    private Float vol4;

    private Float vol5;

    private Float vol6;

    private Float vol7;

    private Float vol8;

    private Float vol9;

    private Float vol10;

    private Float vol11;

    private Float vol12;

    private Float vol13;

    private Float vol14;

    private Float vol15;

    private Float vol16;

    private Float vol17;

    private Float vol18;

    private Float vol19;

    private Float temprature1;

    private Float temprature2;

    private Float chargeNum;

    private Float longtitude;

    private Float latitude;

    private String remarks;

    private Float totalCurrent;

    private Float soc;

    private String testUserName;

    private Integer cityId;

    private Integer vehicleId;


    private static final long serialVersionUID = 1L;

    public String getBatteryNo() {
        return batteryNo;
    }

    public void setBatteryNo(String batteryNo) {
        this.batteryNo = batteryNo;
    }

    public Integer getBatteryType() {
        return batteryType;
    }

    public void setBatteryType(Integer batteryType) {
        this.batteryType = batteryType;
    }

    public Float getVoltageDeviation() {
        return voltageDeviation;
    }

    public void setVoltageDeviation(Float voltageDeviation) {
        this.voltageDeviation = voltageDeviation;
    }

    public Float getTotalVoltage() {
        return totalVoltage;
    }

    public void setTotalVoltage(Float totalVoltage) {
        this.totalVoltage = totalVoltage;
    }

    public String getCreateTime() {
        return createTime;
    }

    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }

    public Integer getCreateUser() {
        return createUser;
    }

    public void setCreateUser(Integer createUser) {
        this.createUser = createUser;
    }

    public Integer getSource() {
        return source;
    }

    public void setSource(Integer source) {
        this.source = source;
    }

    public Float getVol1() {
        return vol1;
    }

    public void setVol1(Float vol1) {
        this.vol1 = vol1;
    }

    public Float getVol2() {
        return vol2;
    }

    public void setVol2(Float vol2) {
        this.vol2 = vol2;
    }

    public Float getVol3() {
        return vol3;
    }

    public void setVol3(Float vol3) {
        this.vol3 = vol3;
    }

    public Float getVol4() {
        return vol4;
    }

    public void setVol4(Float vol4) {
        this.vol4 = vol4;
    }

    public Float getVol5() {
        return vol5;
    }

    public void setVol5(Float vol5) {
        this.vol5 = vol5;
    }

    public Float getVol6() {
        return vol6;
    }

    public void setVol6(Float vol6) {
        this.vol6 = vol6;
    }

    public Float getVol7() {
        return vol7;
    }

    public void setVol7(Float vol7) {
        this.vol7 = vol7;
    }

    public Float getVol8() {
        return vol8;
    }

    public void setVol8(Float vol8) {
        this.vol8 = vol8;
    }

    public Float getVol9() {
        return vol9;
    }

    public void setVol9(Float vol9) {
        this.vol9 = vol9;
    }

    public Float getVol10() {
        return vol10;
    }

    public void setVol10(Float vol10) {
        this.vol10 = vol10;
    }

    public Float getVol11() {
        return vol11;
    }

    public void setVol11(Float vol11) {
        this.vol11 = vol11;
    }

    public Float getVol12() {
        return vol12;
    }

    public void setVol12(Float vol12) {
        this.vol12 = vol12;
    }

    public Float getVol13() {
        return vol13;
    }

    public void setVol13(Float vol13) {
        this.vol13 = vol13;
    }

    public Float getVol14() {
        return vol14;
    }

    public void setVol14(Float vol14) {
        this.vol14 = vol14;
    }

    public Float getVol15() {
        return vol15;
    }

    public void setVol15(Float vol15) {
        this.vol15 = vol15;
    }

    public Float getVol16() {
        return vol16;
    }

    public void setVol16(Float vol16) {
        this.vol16 = vol16;
    }

    public Float getVol17() {
        return vol17;
    }

    public void setVol17(Float vol17) {
        this.vol17 = vol17;
    }

    public Float getVol18() {
        return vol18;
    }

    public void setVol18(Float vol18) {
        this.vol18 = vol18;
    }

    public Float getVol19() {
        return vol19;
    }

    public void setVol19(Float vol19) {
        this.vol19 = vol19;
    }

    public Float getTemprature1() {
        return temprature1;
    }

    public void setTemprature1(Float temprature1) {
        this.temprature1 = temprature1;
    }

    public Float getTemprature2() {
        return temprature2;
    }

    public void setTemprature2(Float temprature2) {
        this.temprature2 = temprature2;
    }

    public Float getChargeNum() {
        return chargeNum;
    }

    public void setChargeNum(Float chargeNum) {
        this.chargeNum = chargeNum;
    }

    public Float getLongtitude() {
        return longtitude;
    }

    public void setLongtitude(Float longtitude) {
        this.longtitude = longtitude;
    }

    public Float getLatitude() {
        return latitude;
    }

    public void setLatitude(Float latitude) {
        this.latitude = latitude;
    }

    public String getRemarks() {
        return remarks;
    }

    public void setRemarks(String remarks) {
        this.remarks = remarks;
    }

    public Float getTotalCurrent() {
        return totalCurrent;
    }

    public void setTotalCurrent(Float totalCurrent) {
        this.totalCurrent = totalCurrent;
    }

    public Float getSoc() {
        return soc;
    }

    public void setSoc(Float soc) {
        this.soc = soc;
    }

    public String getTestUserName() {
        return testUserName;
    }

    public void setTestUserName(String testUserName) {
        this.testUserName = testUserName;
    }

    public Integer getCityId() {
        return cityId;
    }

    public void setCityId(Integer cityId) {
        this.cityId = cityId;
    }

    public Integer getVehicleId() {
        return vehicleId;
    }

    public void setVehicleId(Integer vehicleId) {
        this.vehicleId = vehicleId;
    }

    @Override
    public String toString() {
        return "ResBatteryDataHistory{" +
                "batteryNo:'" + batteryNo + '\'' +
                ", batteryType:" + batteryType +
                ", voltageDeviation:" + voltageDeviation +
                ", totalVoltage:" + totalVoltage +
                ", createTime:'" + createTime + '\'' +
                ", createUser:" + createUser +
                ", source:" + source +
                ", vol1:" + vol1 +
                ", vol2:" + vol2 +
                ", vol3:" + vol3 +
                ", vol4:" + vol4 +
                ", vol5:" + vol5 +
                ", vol6:" + vol6 +
                ", vol7:" + vol7 +
                ", vol8:" + vol8 +
                ", vol9:" + vol9 +
                ", vol10:" + vol10 +
                ", vol11:" + vol11 +
                ", vol12:" + vol12 +
                ", vol13:" + vol13 +
                ", vol14:" + vol14 +
                ", vol15:" + vol15 +
                ", vol16:" + vol16 +
                ", vol17:" + vol17 +
                ", vol18:" + vol18 +
                ", vol19:" + vol19 +
                ", temprature1:" + temprature1 +
                ", temprature2:" + temprature2 +
                ", chargeNum:" + chargeNum +
                ", longtitude:" + longtitude +
                ", latitude:" + latitude +
                ", remarks:'" + remarks + '\'' +
                ", totalCurrent:" + totalCurrent +
                ", soc:" + soc +
                ", testUserName:'" + testUserName + '\'' +
                ", cityId:" + cityId +
                ", vehicleId:" + vehicleId +
                '}';
    }
}



封装查询条件类:


/**
 *
 * HBase条件查询封装实体类
 * Created by liuhuichao on 2016/12/13.
 */
public class QueryCondition {

    String family; //列族
    String qualifier; //列修饰符
    CompareFilter.CompareOp  compareOp; //操作符
    byte[] value; //列值
    FilterList.Operator operator;  //连接操作符


    public QueryCondition(String family, String qualifier, CompareFilter.CompareOp compareOp, byte[] value) {
        this.family = family;
        this.qualifier = qualifier;
        this.compareOp = compareOp;
        this.value = value;
    }

    public QueryCondition(String family, String qualifier, CompareFilter.CompareOp compareOp, byte[] value, FilterList.Operator operator) {
        this.family = family;
        this.qualifier = qualifier;
        this.compareOp = compareOp;
        this.value = value;
        this.operator = operator;
    }

    public String getFamily() {
        return family;
    }

    public void setFamily(String family) {
        this.family = family;
    }

    public String getQualifier() {
        return qualifier;
    }

    public void setQualifier(String qualifier) {
        this.qualifier = qualifier;
    }

    public CompareFilter.CompareOp getCompareOp() {
        return compareOp;
    }

    public void setCompareOp(CompareFilter.CompareOp compareOp) {
        this.compareOp = compareOp;
    }

    public byte[] getValue() {
        return value;
    }

    public void setValue(byte[] value) {
        this.value = value;
    }

    public FilterList.Operator getOperator() {
        return operator;
    }

    public void setOperator(FilterList.Operator operator) {
        this.operator = operator;
    }
}



查询操作类:


/**
 * 功能:管理电池历史数据
 * Created by liuhuichao on 2016/12/8.
 */
public class HBaseBatteryDataManager extends Thread {

    private static class HBaseBatteryDataMasterHolder{
        private static final HBaseBatteryDataManager INSTANCE = new HBaseBatteryDataManager();
    }

    public  final String  colunm_family_baseData="baseData";//列族baseData
    public  final String  colunm_family_extraData="extraData";//列族extraData

    /**
     * 构造:配置初始化
     */
    private HBaseBatteryDataManager() {


        //集群环境下使用的配置代码
      /*  config= HBaseConfiguration.create();
        config.set("hbase.master",""); //HMaster地址
        config.set("hbase.zookeeper.property.clientPort",""); //Zookeeper端口设置
        config.set("hbase.zookeeper.quorum","");//Zookeeper队列名称
        try{
            table=new HTable(config, Bytes.toBytes("demo_table_name")); //连接表
            admin=new HBaseAdmin(config);
        }catch (IOException e){
            e.printStackTrace();
        }*/


        //单机环境下的config
        config= HBaseConfiguration.create();
        config.set("hbase.zookeeper.quorum","lhc-centos");
        try{
            table=new HTable(config, Bytes.toBytes("batteryTest")); //连接表
        }catch (IOException e){
            e.printStackTrace();
        }

    }

    /**
     * 外部调用这个类的方法
     * @return
     */
    public static final HBaseBatteryDataManager getInstance() {
        return HBaseBatteryDataMasterHolder.INSTANCE;
    }

    private Configuration config;
    public HTable table;
    public HBaseAdmin admin;

    /**
     * 增加一条电池历史数据
     * @param resBatteryDataHistory
     */
    public void addBatteryData(ResBatteryDataHistory resBatteryDataHistory) throws IOException {
        if(resBatteryDataHistory==null){
            return;
        }
        table.put(getSavePut(resBatteryDataHistory));
        table.close();
    }

    /**
     * 批量添加电池数据
     * @param batteryList
     */
    public void addBatteryDataList(List<ResBatteryDataHistory> batteryList) throws IOException {
        List<Put> puts = new ArrayList<Put>();
        for(ResBatteryDataHistory battery : batteryList){
            puts.add(getSavePut(battery));
        }
        table.put(puts);
        table.close();
    }

    /**
     * 根据rowKey获取电池历史数据
     * @param rowKey
     * @return
     */
    public ResBatteryDataHistory getBatteryDataHistoryByRowKey(String rowKey) throws IOException {
        Get get=new Get(Bytes.toBytes(rowKey));
        Result result=table.get(get);
        if(result==null){
            return null;
        }
        return convertToData(result);
    }

    /**
     * 获取电池数据某个单元格的数据
     * @param rowKey
     * @param colFamily
     * @param col
     * @return
     */
    public  byte[] getCellData(String rowKey,String colFamily,String col) throws IOException {
        Get get = new Get(Bytes.toBytes(rowKey));
        get.addColumn(Bytes.toBytes(colFamily),Bytes.toBytes(col));
        //获取的result数据是结果集,还需要格式化输出想要的数据才行
        Result result = table.get(get);
        byte[] cellValue= result.getValue(colFamily.getBytes(),col==null?null:col.getBytes());
        table.close();
        return cellValue;
    }

    /**
     * 更新表中的一个单元格
     * @param rowKey
     * @param familyName
     * @param columnName
     * @param value
     */
   public void  updateCell(String rowKey,String familyName, String columnName, byte[] value) throws IOException {
       Put put = new Put(Bytes.toBytes(rowKey));
       put.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(columnName),
               value);
       table.put(put);
       table.close();
   }

    /**
     * 删除一个单元格
     * @param rowKey
     * @param familyName
     * @param columnName
     */
    public void deleteCell( String rowKey,String familyName, String columnName) throws IOException {
        Delete delete = new Delete(Bytes.toBytes(rowKey));
        delete.addColumn(Bytes.toBytes(familyName),
                Bytes.toBytes(columnName));
        table.delete(delete);
        table.close();
    }

    /**
     * 删除一行
     * @param rowKey
     */
    public void deleteAllColumns(String rowKey) throws IOException {
        Delete delete = new Delete(Bytes.toBytes(rowKey));
        table.delete(delete);
        table.close();
    }

    /**
     * 获取所有数据
     * @return
     */
    public List<ResBatteryDataHistory> getResultScans() throws IOException {
        Scan scan = new Scan();
        ResultScanner resultScanner = null;
        List<ResBatteryDataHistory> list=new ArrayList<>() ;
        long beginTime=System.currentTimeMillis();
        resultScanner=table.getScanner(scan);
        long endTime=System.currentTimeMillis();
        double spendTime=(endTime-beginTime)/1000.0;
        System.out.println("扫描表共计时间:------"+spendTime);
        if(resultScanner==null){
            return null;
        }
        for (Result result : resultScanner){
            list.add(convertToData(result));
        }
        table.close();
        return list;
    }


    /**
     *  * 根据单个列查询数据 【and查询】
     *      例如,查询extraData:create_user==‘1’的所有数据
     * @param queryConditionList  条件拼接列表
     * @return
     * @throws IOException
     */
    public List<ResBatteryDataHistory> QueryDataByConditionsAnd(List<QueryCondition> queryConditionList) throws IOException {
        if(queryConditionList==null || queryConditionList.size()<1){
            return null;
        }
        ResultScanner rs = null;
        List<ResBatteryDataHistory> list=new ArrayList<>() ;
        List<Filter> filters = new ArrayList<Filter>();
        for(QueryCondition query : queryConditionList){
            SingleColumnValueFilter filter = new SingleColumnValueFilter(
                    Bytes.toBytes(query.getFamily()), Bytes.toBytes(query.getQualifier()),
                    query.getCompareOp(),query.getValue());
            filter.setFilterIfMissing(true); //设置这些列不存在的数据不返回
            filters.add(filter);
        }
        FilterList filterList = new FilterList(filters);
        Scan scan = new Scan();
        scan.setFilter(filterList);
        rs = table.getScanner(scan);
        for (Result result : rs){
            list.add(convertToData(result));
        }
        table.close();
        return list;
    }


    /**
     * 查询数据 【Or查询】
     * @param queryConditionList
     * @return
     */
    public List<ResBatteryDataHistory> QueryDataByConditionsOr(List<QueryCondition> queryConditionList) throws IOException {
        if(queryConditionList==null || queryConditionList.size()<1){
            return null;
        }
        List<ResBatteryDataHistory> list=new ArrayList<>() ;
        ResultScanner rs = null;
        List<Filter> filters = new ArrayList<Filter>();
        Scan scan = new Scan();
        for(QueryCondition query : queryConditionList){
            SingleColumnValueFilter filter = new SingleColumnValueFilter(
                    Bytes.toBytes(query.getFamily()), Bytes.toBytes(query.getQualifier()),
                    query.getCompareOp(),query.getValue());
            filter.setFilterIfMissing(true);
            filters.add(filter);
        }
        FilterList filterList = new FilterList(
                FilterList.Operator.MUST_PASS_ONE, filters);
        scan.setFilter(filterList);
        rs = table.getScanner(scan);
        for (Result result : rs){
            list.add(convertToData(result));
        }
        table.close();
        return list;
    }

    /**
     * 混合条件查询
     * @param queryConditionList
     * @return
     * @throws IOException
     */
    public List<ResBatteryDataHistory> QueryDataByConditions(List<QueryCondition> queryConditionList) throws IOException {
        if(queryConditionList==null || queryConditionList.size()<1){
            return null;
        }
        List<ResBatteryDataHistory> list=new ArrayList<>() ;
        Scan scan = new Scan();
        FilterList filterList = null;
        FilterList.Operator operator = null;
        List<Filter> filters = new ArrayList<Filter>();
        ResultScanner rs = null;
        for(QueryCondition query : queryConditionList){

            SingleColumnValueFilter filter = new SingleColumnValueFilter(
                    Bytes.toBytes(query.getFamily()), Bytes.toBytes(query.getQualifier()),
                    query.getCompareOp(),query.getValue());
            filter.setFilterIfMissing(true);  //去掉没有这种列的数据

            if(query.getOperator()!=null){//有操作符的时候
                if (operator == null) {
                    operator = query.getOperator();
                    filterList = new FilterList(
                            query.getOperator());
                    filterList.addFilter(filter);
                    System.out.println("filterList==1" + filterList);
                } else if (operator.equals(query.getOperator())) {
                    filterList.addFilter(filter);
                } else {
                    filterList.addFilter(filter);
                    System.out.println("filterList==2" + filterList);
                    FilterList addFilterList = new FilterList(
                            query.getOperator());
                    addFilterList.addFilter(filterList);
                    System.out.println("addFilterList==1" + addFilterList);
                    filterList = addFilterList;
                    System.out.println("filterList==3" + filterList);
                }
            } else {
                if (filterList == null) {
                    filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);// 默认只有一个条件的时候
                }
                filterList.addFilter(filter);
            }
        }
        scan.setFilter(filterList);
        rs = table.getScanner(scan);
        for (Result result : rs){
            list.add(convertToData(result));
        }
        table.close();
        return list;
    }

    /**
     *混合条件分页查询
     * @param queryConditionList
     * @param pageSize
     * @param lastRow
     * @return
     */
    public  List<ResBatteryDataHistory>  QueryDataByConditionsAndPage(List<QueryCondition> queryConditionList, int pageSize,byte[] lastRow) throws IOException {
        List<ResBatteryDataHistory> list=new ArrayList<>() ;
        final byte[] POSTFIX = new byte[] { 0x00 };
        ResultScanner rs = null;
        Scan scan = new Scan();
        FilterList filterList = null;
        FilterList.Operator operator = null;

        //拼接查询条件
        for(QueryCondition query : queryConditionList){
            SingleColumnValueFilter filter = new SingleColumnValueFilter(
                    Bytes.toBytes(query.getFamily()), Bytes.toBytes(query.getQualifier()),
                    query.getCompareOp(),query.getValue());
            filter.setFilterIfMissing(true);  //去掉没有这种列的数据

            if(query.getOperator()!=null){//有操作符的时候
                if (operator == null) {
                    operator = query.getOperator();
                    filterList = new FilterList(
                            query.getOperator());
                    filterList.addFilter(filter);
                    System.out.println("filterList==1" + filterList);
                } else if (operator.equals(query.getOperator())) {
                    filterList.addFilter(filter);
                } else {
                    filterList.addFilter(filter);
                    System.out.println("filterList==2" + filterList);
                    FilterList addFilterList = new FilterList(
                            query.getOperator());
                    addFilterList.addFilter(filterList);
                    System.out.println("addFilterList==1" + addFilterList);
                    filterList = addFilterList;
                    System.out.println("filterList==3" + filterList);
                }
            } else {
                if (filterList == null) {
                    filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);// 默认只有一个条件的时候
                }
                filterList.addFilter(filter);
            }
        }

        FilterList pageFilterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);// 默认只有一个条件的时候
        Filter pageFilter = new PageFilter(pageSize);
        pageFilterList.addFilter(pageFilter);
        pageFilterList.addFilter(filterList);
        if (lastRow != null) {
            // 注意这里添加了POSTFIX操作,不然死循环了
            byte[] startRow = Bytes.add(lastRow, POSTFIX);
            scan.setStartRow(startRow);
        }
        System.out.println(pageFilterList + ":pageFilterList");
        scan.setFilter(pageFilterList);
        rs = table.getScanner(scan);
        for (Result result : rs){
            list.add(convertToData(result));
        }
        table.close();
        return list;
    }

    /**
     * 分页查询——无条件
     * @param pageSize
     * @param lastRow
     * @return
     * @throws IOException
     */
    public  List<ResBatteryDataHistory>  QueryDataByPage( int pageSize,byte[] lastRow) throws IOException {
        List<ResBatteryDataHistory> list=new ArrayList<>() ;
        final byte[] POSTFIX = new byte[] { 0x00 };
        Scan scan = new Scan();
        ResultScanner resultScanner = null;
        Table table = null;
        Filter filter = new PageFilter(pageSize);
        scan.setFilter(filter);
        if (lastRow != null) {
            // 注意这里添加了POSTFIX操作,不然死循环了
            byte[] startRow = Bytes.add(lastRow, POSTFIX);
            scan.setStartRow(startRow);
        }
        resultScanner = table.getScanner(scan);
        for (Result result : resultScanner){
            list.add(convertToData(result));
        }
        table.close();
        return list;
    }


    /**
     * 查询总行数
     * @param pageSize
     * @return
     */
    public int QueryDataByPage(int pageSize) throws IOException {
        final byte[] POSTFIX = new byte[] { 0x00 };
        int totalRows = 0;
        Filter filter = new PageFilter(pageSize);
        byte[] lastRow = null;
        while (true) {
            Scan scan = new Scan();
            scan.setFilter(filter);
            if (lastRow != null) {
                // 注意这里添加了POSTFIX操作,不然死循环了
                byte[] startRow = Bytes.add(lastRow, POSTFIX);
                scan.setStartRow(startRow);
            }
            ResultScanner scanner = table.getScanner(scan);
            int localRows = 0;
            Result result;
            while ((result = scanner.next()) != null) {
                System.out.println(localRows++ + ":" + result);
                totalRows++;
                lastRow = result.getRow();
            }
            scanner.close();
            if (localRows == 0)
                break;
        }
        return totalRows;
    }

    /**
     * 将Result转换成电池历史数据对象
     * @param result
     * @return
     */
    private ResBatteryDataHistory convertToData(Result result){
        ResBatteryDataHistory resBatteryDataHistory=new ResBatteryDataHistory();
        if(result==null || result.list().size()<1){
            return null;
        }
        for(KeyValue kv :result.raw()){
            if(Bytes.toString(kv.getFamily()).equals(colunm_family_baseData)){
                switch (Bytes.toString(kv.getQualifier())){
                    case "battery_no":
                        resBatteryDataHistory.setBatteryNo(Bytes.toString(kv.getValue()));
                        break;
                    case "battery_type":
                        resBatteryDataHistory.setBatteryType(Bytes.toInt(kv.getValue()));
                        break;
                    case "voltage_deviation":
                        resBatteryDataHistory.setVoltageDeviation(Bytes.toFloat(kv.getValue()));
                        break;
                    case "total_voltage":
                        resBatteryDataHistory.setTotalVoltage(Bytes.toFloat(kv.getValue()));
                        break;
                    case "total_current":
                        resBatteryDataHistory.setTotalCurrent(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol1":
                        resBatteryDataHistory.setVol1(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol2":
                        resBatteryDataHistory.setVol2(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol3":
                        resBatteryDataHistory.setVol3(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol4":
                        resBatteryDataHistory.setVol4(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol5":
                        resBatteryDataHistory.setVol5(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol6":
                        resBatteryDataHistory.setVol6(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol7":
                        resBatteryDataHistory.setVol7(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol8":
                        resBatteryDataHistory.setVol8(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol9":
                        resBatteryDataHistory.setVol9(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol10":
                        resBatteryDataHistory.setVol10(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol11":
                        resBatteryDataHistory.setVol11(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol12":
                        resBatteryDataHistory.setVol12(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol13":
                        resBatteryDataHistory.setVol13(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol14":
                        resBatteryDataHistory.setVol14(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol15":
                        resBatteryDataHistory.setVol15(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol16":
                        resBatteryDataHistory.setVol16(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol17":
                        resBatteryDataHistory.setVol17(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol18":
                        resBatteryDataHistory.setVol18(Bytes.toFloat(kv.getValue()));
                        break;
                    case "vol19":
                        resBatteryDataHistory.setVol19(Bytes.toFloat(kv.getValue()));
                        break;
                    case "temprature1":
                        resBatteryDataHistory.setTemprature1(Bytes.toFloat(kv.getValue()));
                        break;
                    case "temprature2":
                        resBatteryDataHistory.setTemprature2(Bytes.toFloat(kv.getValue()));
                        break;
                    case "charge_num":
                        resBatteryDataHistory.setChargeNum(Bytes.toFloat(kv.getValue()));
                        break;
                    case "soc":
                        resBatteryDataHistory.setSoc(Bytes.toFloat(kv.getValue()));
                        break;
                    case "create_time":
                        resBatteryDataHistory.setCreateTime(Bytes.toString(kv.getValue()));
                        break;
                    case "city_id":
                        resBatteryDataHistory.setCityId(Bytes.toInt(kv.getValue()));
                        break;
                    case "vehicle_id":
                        resBatteryDataHistory.setVehicleId(Bytes.toInt(kv.getValue()));
                        break;
                }
            }
            if(Bytes.toString(kv.getFamily()).equals(colunm_family_extraData)){
                switch (Bytes.toString(kv.getQualifier())){
                    case "create_user" :
                        resBatteryDataHistory.setCreateUser(Bytes.toInt(kv.getValue()));
                        break;
                    case "source" :
                        resBatteryDataHistory.setSource(Bytes.toInt(kv.getValue()));
                        break;
                    case "longtitude" :
                        resBatteryDataHistory.setLongtitude(Bytes.toFloat(kv.getValue()));
                        break;
                    case "latitude" :
                        resBatteryDataHistory.setLatitude(Bytes.toFloat(kv.getValue()));
                        break;
                    case "remarks" :
                        resBatteryDataHistory.setRemarks(Bytes.toString(kv.getValue()));
                        break;
                    case "test_user_name" :
                        resBatteryDataHistory.setTestUserName(Bytes.toString(kv.getValue()));
                        break;
                }
            }
        }
        return  resBatteryDataHistory;
    }

    /**
     * 获取插入数据的Put对象
     * @param resBatteryDataHistory
     * @return
     */
    private Put getSavePut(ResBatteryDataHistory resBatteryDataHistory){
        Put put=new Put(Bytes.toBytes(resBatteryDataHistory.getBatteryNo()+System.currentTimeMillis()));
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("battery_no"),Bytes.toBytes(resBatteryDataHistory.getBatteryNo()));//电池编号
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("battery_type"),Bytes.toBytes(resBatteryDataHistory.getBatteryType()));//电池类型
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("voltage_deviation"),Bytes.toBytes(resBatteryDataHistory.getVoltageDeviation()));//压差
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("total_voltage"),Bytes.toBytes(resBatteryDataHistory.getTotalVoltage()));//总电压
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("total_current"),Bytes.toBytes(resBatteryDataHistory.getTotalCurrent()));//总电流
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol1"),Bytes.toBytes(resBatteryDataHistory.getVol1()));//电芯电压1
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol2"),Bytes.toBytes(resBatteryDataHistory.getVol2()));//电芯电压2
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol3"),Bytes.toBytes(resBatteryDataHistory.getVol3()));//电芯电压3
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol4"),Bytes.toBytes(resBatteryDataHistory.getVol4()));//电芯电压4
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol5"),Bytes.toBytes(resBatteryDataHistory.getVol5()));//电芯电压5
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol6"),Bytes.toBytes(resBatteryDataHistory.getVol6()));//电芯电压6
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol7"),Bytes.toBytes(resBatteryDataHistory.getVol7()));//电芯电压7
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol8"),Bytes.toBytes(resBatteryDataHistory.getVol8()));//电芯电压8
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol9"),Bytes.toBytes(resBatteryDataHistory.getVol9()));//电芯电压9
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol10"),Bytes.toBytes(resBatteryDataHistory.getVol10()));//电芯电压10
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol11"),Bytes.toBytes(resBatteryDataHistory.getVol11()));//电芯电压11
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol12"),Bytes.toBytes(resBatteryDataHistory.getVol12()));//电芯电压12
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol13"),Bytes.toBytes(resBatteryDataHistory.getVol13()));//电芯电压13
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol14"),Bytes.toBytes(resBatteryDataHistory.getVol14()));//电芯电压14
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol15"),Bytes.toBytes(resBatteryDataHistory.getVol15()));//电芯电压15
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol16"),Bytes.toBytes(resBatteryDataHistory.getVol16()));//电芯电压16
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol17"),Bytes.toBytes(resBatteryDataHistory.getVol17()));//电芯电压17
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol18"),Bytes.toBytes(resBatteryDataHistory.getVol18()));//电芯电压18
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vol19"),Bytes.toBytes(resBatteryDataHistory.getVol19()));//电芯电压19
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("temprature1"),Bytes.toBytes(resBatteryDataHistory.getTemprature1()));//温度1
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("temprature2"),Bytes.toBytes(resBatteryDataHistory.getTemprature2()));//温度2
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("charge_num"),Bytes.toBytes(resBatteryDataHistory.getChargeNum()));//充电次数
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("soc"),Bytes.toBytes(resBatteryDataHistory.getSoc()));//总电压
        put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("create_time"),Bytes.toBytes(resBatteryDataHistory.getCreateTime()));//创建时间
        if(resBatteryDataHistory.getCityId()!=null){
            put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("city_id"),Bytes.toBytes(resBatteryDataHistory.getCityId()));//城市ID
        }
        if(resBatteryDataHistory.getVehicleId()!=null){
            put.addColumn(Bytes.toBytes(colunm_family_baseData),Bytes.toBytes("vehicle_id"),Bytes.toBytes(resBatteryDataHistory.getVehicleId()));//车辆ID
        }
        put.addColumn(Bytes.toBytes(colunm_family_extraData),Bytes.toBytes("create_user"),Bytes.toBytes(resBatteryDataHistory.getCreateUser()));//创建人
        put.addColumn(Bytes.toBytes(colunm_family_extraData),Bytes.toBytes("source"),Bytes.toBytes(resBatteryDataHistory.getSource()));//数据来源
        put.addColumn(Bytes.toBytes(colunm_family_extraData),Bytes.toBytes("longtitude"),Bytes.toBytes(resBatteryDataHistory.getLongtitude()));//精度
        put.addColumn(Bytes.toBytes(colunm_family_extraData),Bytes.toBytes("latitude"),Bytes.toBytes(resBatteryDataHistory.getLatitude()));//纬度
        put.addColumn(Bytes.toBytes(colunm_family_extraData),Bytes.toBytes("remarks"),Bytes.toBytes(resBatteryDataHistory.getRemarks()));//备注
        put.addColumn(Bytes.toBytes(colunm_family_extraData),Bytes.toBytes("test_user_name"),Bytes.toBytes(resBatteryDataHistory.getTestUserName()));//测试用户
        return put;
    }


   }




单元测试代码:


/**
 * 功能:测试HBase的工具类
 * Created by liuhuichao on 2016/12/14.
 */
public class HBaseBatteryManagerTest {

    HBaseBatteryDataManager manager= HBaseBatteryDataManager.getInstance();

    /**
     * 测试Hbase的单条Put操作
     */
    @Test
    public void testPut() throws IOException {
        ResBatteryDataHistory battery=new ResBatteryDataHistory();
        battery.setBatteryNo("4015106002");
        battery.setBatteryType(0);
        battery.setVoltageDeviation(1F);
        battery.setTotalVoltage(1F);
        battery.setCreateTime("2016-12-12 11:11:11");
        battery.setCreateUser(250);
        battery.setSource(3);
        battery.setVol1(1F);
        battery.setVol2(1F);
        battery.setVol3(1F);
        battery.setVol4(1F);
        battery.setVol5(1F);
        battery.setVol6(1F);
        battery.setVol7(1F);
        battery.setVol8(1F);
        battery.setVol9(1F);
        battery.setVol10(1F);
        battery.setVol11(1F);
        battery.setVol12(1F);
        battery.setVol13(1F);
        battery.setVol14(1F);
        battery.setVol15(1F);
        battery.setVol16(1F);
        battery.setVol17(1F);
        battery.setVol18(1F);
        battery.setVol19(1F);
        battery.setTemprature1(1F);
        battery.setTemprature2(1F);
        battery.setChargeNum(11F);
        battery.setLongtitude(12F);
        battery.setLatitude(12F);
        battery.setRemarks("lhc-代码测试");
        battery.setTotalCurrent(22F);
        battery.setSoc(22F);
        battery.setTestUserName("lhc");
        battery.setCityId(11011);
        battery.setVehicleId(11011);
        manager.addBatteryData(battery);
    }

    @Test
    public void addBatteryDataListTest() throws IOException {
        List<ResBatteryDataHistory> batteryList=new ArrayList<>();
        for(int i=20000;i<200000;i++){
            ResBatteryDataHistory battery=new ResBatteryDataHistory();
            battery.setBatteryNo(String.valueOf(i));
            battery.setBatteryType(0);
            battery.setVoltageDeviation(1F);
            battery.setTotalVoltage(1F);
            battery.setCreateTime("2016-12-12 11:11:11");
            battery.setCreateUser(250);
            battery.setSource(3);
            battery.setVol1(1F);
            battery.setVol2(1F);
            battery.setVol3(1F);
            battery.setVol4(1F);
            battery.setVol5(1F);
            battery.setVol6(1F);
            battery.setVol7(1F);
            battery.setVol8(1F);
            battery.setVol9(1F);
            battery.setVol10(1F);
            battery.setVol11(1F);
            battery.setVol12(1F);
            battery.setVol13(1F);
            battery.setVol14(1F);
            battery.setVol15(1F);
            battery.setVol16(1F);
            battery.setVol17(1F);
            battery.setVol18(1F);
            battery.setVol19(1F);
            battery.setTemprature1(1F);
            battery.setTemprature2(1F);
            battery.setChargeNum(11F);
            battery.setLongtitude(12F);
            battery.setLatitude(12F);
            battery.setRemarks("lhc-代码测试");
            battery.setTotalCurrent(22F);
            battery.setSoc(22F);
            battery.setTestUserName("lhc");
            battery.setCityId(11011);
            battery.setVehicleId(11011);
            batteryList.add(battery);
        }
        long begin=System.currentTimeMillis();
        System.out.println("开始存入:"+begin);
        manager.addBatteryDataList(batteryList);
        long end=System.currentTimeMillis();
        System.out.println("结束存入:"+end);
        System.out.println((end-begin)/1000.0);
    }

    /**
     * 测试根据RowKey获取数据
     * @throws IOException
     */
    @Test
    public void testGetByRowKey() throws IOException {
        System.out.println(manager.getBatteryDataHistoryByRowKey("40151060021481685540182"));
    }

    /**
     * 测试获取单元格数据
     */
    @Test
    public void getCellDataTest() throws IOException {
        System.out.println(Bytes.toFloat(manager.getCellData("40151060021481685540182",manager.colunm_family_baseData,"vol1")));
    }


    /**
     * 更新用户名
     * @throws IOException
     */
    @Test
    public void updateCellTest() throws IOException {
        System.out.println(manager.getBatteryDataHistoryByRowKey("40151060021481685540182"));
        manager.updateCell("40151060021481685540182",manager.colunm_family_extraData,"test_user_name",Bytes.toBytes("liuhuichao128"));
        System.out.println(manager.getBatteryDataHistoryByRowKey("40151060021481685540182"));
    }

    /**
     * 测试表扫描
     * @throws IOException
     */
    @Test
    public void scanTest() throws IOException {
        System.out.println( manager.getResultScans().size());
    }


    /**
     * 测试and查询
     */
    @Test
    public void QueryDataByConditionsAnd() throws IOException {
        List<QueryCondition> queryConditionList=new ArrayList<>();
        queryConditionList.add(new QueryCondition(manager.colunm_family_baseData,"vol1", CompareFilter.CompareOp.EQUAL,Bytes.toBytes(1F)));
        long begin=System.currentTimeMillis();
        System.out.println(manager.QueryDataByConditionsAnd(queryConditionList).size());
        long end=System.currentTimeMillis();
        System.out.println((end-begin)/1000.00);
    }

    /**
     * 测试or查询
     * @throws IOException
     */
    @Test
    public void QueryDataByConditionsOrTest() throws IOException {
        List<QueryCondition> queryConditionList=new ArrayList<>();
        queryConditionList.add(new QueryCondition(manager.colunm_family_baseData,"vol1", CompareFilter.CompareOp.EQUAL,Bytes.toBytes(1F)));
        System.out.println(manager.QueryDataByConditionsOr(queryConditionList));
    }



}



自行体会吧。





作者:lhc2207221755 发表于2016/12/14 17:44:03 原文链接
阅读:31 评论:0 查看评论

Android开发中搜索功能的实现

$
0
0

现在很多的app中都有搜索的功能。也就是说搜索栏下面有一个列表,列表中放的内容可能是游戏,也有可能是其他的内容。这时候,我们可以在搜索框中输入你想要搜索的内容,这时候,下面的列表就会出现你想要的内容。

别担心,实现起来不难,下面是关键的步骤:


搜索框及列表界面怎么设计,我在这里就不多说了,因为重点是搜索这个功能的实现,布局界面的话,都可以在xml里面自己好好设计就行了。而我上一篇的博客就介绍了自定义搜索框的一个样式,大家可以参考。

  • 首先,显示界面就是一个搜索框(我用的是EditText控件),搜索框下面有一个列表,列表中我简单的添加了几条数据以供测试用。主活动中的代码如下:
package com.example.mysearchview;

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

import android.app.Activity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends Activity {

    private EditText et_ss;
    private ListView lsv_ss;
    private List<String> list = new ArrayList<String>();
    boolean isFilter;
    private MyAdapter adapter = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setViews();// 控件初始化
        setData();// 给listView设置adapter
        setListeners();// 设置监听
    }

    /**
     * 数据初始化并设置adapter
     */
    private void setData() {
        initData();// 初始化数据

        // 这里创建adapter的时候,构造方法参数传了一个接口对象,这很关键,回调接口中的方法来实现对过滤后的数据的获取
        adapter = new MyAdapter(list, this, new FilterListener() {
            // 回调方法获取过滤后的数据
            public void getFilterData(List<String> list) {
                // 这里可以拿到过滤后数据,所以在这里可以对搜索后的数据进行操作
                Log.e("TAG", "接口回调成功");
                Log.e("TAG", list.toString());
                setItemClick(list);
            }
        });
        lsv_ss.setAdapter(adapter);
    }

    /**
     * 给listView添加item的单击事件
     * @param filter_lists  过滤后的数据集
     */
    protected void setItemClick(final List<String> filter_lists) {
        lsv_ss.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
                // 点击对应的item时,弹出toast提示所点击的内容
                Toast.makeText(MainActivity.this, filter_lists.get(position), Toast.LENGTH_SHORT).show();
            }
        });
    }

    /**
     * 简单的list集合添加一些测试数据
     */
    private void initData() {
        list.add("看着飞舞的尘埃   掉下来");
        list.add("没人发现它存在");
        list.add("多自由自在");
        list.add("可世界都爱热热闹闹");
        list.add("容不下   我百无聊赖");
        list.add("不应该   一个人 发呆");
        list.add("只有我   守着安静的沙漠");
        list.add("等待着花开");
        list.add("只有我   看着别人的快乐");
    }

    private void setListeners() {
        // 没有进行搜索的时候,也要添加对listView的item单击监听
        setItemClick(list);

        /**
         * 对编辑框添加文本改变监听,搜索的具体功能在这里实现
         * 很简单,文本该变的时候进行搜索。关键方法是重写的onTextChanged()方法。
         */
        et_ss.addTextChangedListener(new TextWatcher() {

            /**
             * 
             * 编辑框内容改变的时候会执行该方法
             */
            @Override
            public void onTextChanged(CharSequence s, int start, int before,
                    int count) {
                // 如果adapter不为空的话就根据编辑框中的内容来过滤数据
                if(adapter != null){
                    adapter.getFilter().filter(s);
                }
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count,
                    int after) {
                // TODO Auto-generated method stub
            }

            @Override
            public void afterTextChanged(Editable s) {
                // TODO Auto-generated method stub
            }
        });
    }

    /**
     * 控件初始化
     */
    private void setViews() {
        et_ss = (EditText) findViewById(R.id.et_ss);// EditText控件
        lsv_ss = (ListView)findViewById(R.id.lsv_ss);// ListView控件
    }

}

上面的代码,主要就是界面上有一个搜索框,搜索框下面有一个列表。当在搜索框中输入内容的时候,此时下面的列表显示的内容会自动匹配你输入在搜索框中的内容。

如下图所示:
这里写图片描述

这里写图片描述


  • 接下来就是adapter这个类了,这个类我实现了Filterable接口,然后重写了getFilter()方法,在adapter中定义了一个内部类MyFilter继承Filter类,并重写相关方法,实现数据的过滤。代码如下所示:
package com.example.mysearchview;

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

import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;

public class MyAdapter extends BaseAdapter implements Filterable {

    private List<String> list = new ArrayList<String>();
    private Context context;
    private MyFilter filter = null;// 创建MyFilter对象
    private FilterListener listener = null;// 接口对象

    public MyAdapter(List<String> list, Context context, FilterListener filterListener) {
        this.list = list;
        this.context = context;
        this.listener = filterListener;
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return list.size();
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return list.get(position);
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = LayoutInflater.from(context).inflate(R.layout.item_listview_ss, null);
            holder = new ViewHolder();
            holder.tv_ss = (TextView) convertView.findViewById(R.id.tv_ss);
            convertView.setTag(holder);
        }
        holder = (ViewHolder) convertView.getTag();
        holder.tv_ss.setText(list.get(position));
        return convertView;
    }

    /**
     * 自定义MyAdapter类实现了Filterable接口,重写了该方法
     */
    @Override
    public Filter getFilter() {
        // 如果MyFilter对象为空,那么重写创建一个
        if (filter == null) {
            filter = new MyFilter(list);
        }
        return filter;
    }

    /**
     * 创建内部类MyFilter继承Filter类,并重写相关方法,实现数据的过滤
     * @author 邹奇
     *
     */
    class MyFilter extends Filter {

        // 创建集合保存原始数据
        private List<String> original = new ArrayList<String>();

        public MyFilter(List<String> list) {
            this.original = list;
        }

        /**
         * 该方法返回搜索过滤后的数据
         */
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            // 创建FilterResults对象
            FilterResults results = new FilterResults();

            /**
             * 没有搜索内容的话就还是给results赋值原始数据的值和大小
             * 执行了搜索的话,根据搜索的规则过滤即可,最后把过滤后的数据的值和大小赋值给results
             * 
             */
            if(TextUtils.isEmpty(constraint)){
                results.values = original;
                results.count = original.size();
            }else {
                // 创建集合保存过滤后的数据
                List<String> mList = new ArrayList<String>();
                // 遍历原始数据集合,根据搜索的规则过滤数据
                for(String s: original){
                    // 这里就是过滤规则的具体实现【规则有很多,大家可以自己决定怎么实现】
                    if(s.trim().toLowerCase().contains(constraint.toString().trim().toLowerCase())){
                        // 规则匹配的话就往集合中添加该数据
                        mList.add(s);
                    }
                }
                results.values = mList;
                results.count = mList.size();
            }

            // 返回FilterResults对象
            return results;
        }

        /**
         * 该方法用来刷新用户界面,根据过滤后的数据重新展示列表
         */
        @Override
        protected void publishResults(CharSequence constraint,
                FilterResults results) {
            // 获取过滤后的数据
            list = (List<String>) results.values;
            // 如果接口对象不为空,那么调用接口中的方法获取过滤后的数据,具体的实现在new这个接口的时候重写的方法里执行
            if(listener != null){
                listener.getFilterData(list);
            }
            // 刷新数据源显示
            notifyDataSetChanged();
        }

    }

    /**
     * 控件缓存类
     * 
     * @author 邹奇
     *
     */
    class ViewHolder {
        TextView tv_ss;
    }

}

上面的代码我都给出了注释,大家可以好好的去理解。


细心的同学可能会发现,我在内部类MyFilter里面重写的
protected void publishResults(CharSequence constraint,FilterResults results){}方法里用到了接口里的抽象方法获取过滤后的数据。如下图所示:
这里写图片描述

这里用到了接口的回调,目的是为了获取过滤后的数据。为什么要写这个方法呢?是因为我刚开始搜索完成后,点击搜索后的数据,但是显示的是原来的数据,那么就是数据源没有变、而这个回调,就是为了解决这个问题的,拿到过滤后的数据源,对新的数据源进行操作即可很好的解决这个问题。

点我下载测试版apk


  • 最后接口类代码如下,很简单:
package com.example.mysearchview;

import java.util.List;
/**
 * 接口类,抽象方法用来获取过滤后的数据
 * @author 邹奇
 *
 */
public interface FilterListener {
    void getFilterData(List<String> list);// 获取过滤后的数据
}

接口中定义了一个抽象方法,用来获取数据用的。


  • 最后,大家通过这篇博客应该能加深对接口回调的理解和应用。希望大家以后遇到问题可以先自己想想解决的办法,然后再结合自己的想法尝试尝试,相信很多问题自己都可以很好的解决。比如上面的:搜索后的数据没有变,点击后还是显示原来的数据。这个问题的解决方法就是一个简单的接口回调解决了。

每天进步一点点!加油!

作者:csdnzouqi 发表于2016/12/14 17:45:00 原文链接
阅读:21 评论:0 查看评论

安卓自定义状态栏颜色以与APP作风保持一致

$
0
0

  我们知道IOS上的应用,状态栏的颜色总能与应用标题栏颜色保持一致,用户体验很不错,那安卓是否可以呢?若是在安卓4.4之前,答案是否定的,但在4.4之后,谷歌允许开发者自定义状态栏背景颜色啦,这是个不错的体验!若你手机上安装有最新版的qq,并且你的安卓SDK版本是4.4及以上,你可以看下它的效果:


实现此功能有两种方法:

1.在xml中设置主题或自定义style;

Theme.Holo.Light.NoActionBar.TranslucentDecor

Theme.Holo.NoActionBar.TranslucentDecor

<style name="AppTheme" parent="AppBaseTheme">
<!-- Status Bar -->
<item name="android:windowTranslucentStatus">true</item>
<!-- Navigation Bar -->
<item name="android:windowTranslucentNavigation">true</item>
</style>

鉴于市面上各种手机的SDK的各种版本,不建议采用这种方法;


2.在代码中控制;

可以首先创建一个BaseActivity,在onCreate方法中进行处理:

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {  
            setTranslucentStatus(true);  
            SystemBarTintManager tintManager = new SystemBarTintManager(this);  
            tintManager.setStatusBarTintEnabled(true);  
            tintManager.setStatusBarTintResource(R.color.top_bg_color);//通知栏所需颜色
        }  
		setContentView(R.layout.main_activity);
	}
	
	@TargetApi(19)   
    private void setTranslucentStatus(boolean on) {  
        Window win = getWindow();  
        WindowManager.LayoutParams winParams = win.getAttributes();  
        final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;  
        if (on) {  
            winParams.flags |= bits;  
        } else {  
            winParams.flags &= ~bits;  
        }  
        win.setAttributes(winParams);  
    }


需注意的是, tintManager.setStatusBarTintResource(R.color.top_bg_color);这一步的颜色值(即把你的状态栏颜色与你的标题栏颜色保持一致)要写在color.xml中去,如果用Color.praseColor则会报错。

SystemBarTintManager.java源码:

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout.LayoutParams;

import java.lang.reflect.Method;

/**
 * Class to manage status and navigation bar tint effects when using KitKat 
 * translucent system UI modes.
 *
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class SystemBarTintManager {

    static {
        // Android allows a system property to override the presence of the navigation bar.
        // Used by the emulator.
        // See https://github.com/android/platform_frameworks_base/blob/master/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java#L1076
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            try {
                Class c = Class.forName("android.os.SystemProperties");
                Method m = c.getDeclaredMethod("get", String.class);
                m.setAccessible(true);
                sNavBarOverride = (String) m.invoke(null, "qemu.hw.mainkeys");
            } catch (Throwable e) {
                sNavBarOverride = null;
            }
        }
    }


    /**
     * The default system bar tint color value.
     */
    public static final int DEFAULT_TINT_COLOR = 0x99000000;

    private static String sNavBarOverride;

    private final SystemBarConfig mConfig;
    private boolean mStatusBarAvailable;
    private boolean mNavBarAvailable;
    private boolean mStatusBarTintEnabled;
    private boolean mNavBarTintEnabled;
    private View mStatusBarTintView;
    private View mNavBarTintView;

    /**
     * Constructor. Call this in the host activity onCreate method after its
     * content view has been set. You should always create new instances when
     * the host activity is recreated.
     *
     * @param activity The host activity.
     */
    @TargetApi(19)
    public SystemBarTintManager(Activity activity) {

        Window win = activity.getWindow();
        ViewGroup decorViewGroup = (ViewGroup) win.getDecorView();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // check theme attrs
            int[] attrs = {android.R.attr.windowTranslucentStatus,
                    android.R.attr.windowTranslucentNavigation};
            TypedArray a = activity.obtainStyledAttributes(attrs);
            try {
                mStatusBarAvailable = a.getBoolean(0, false);
                mNavBarAvailable = a.getBoolean(1, false);
            } finally {
                a.recycle();
            }

            // check window flags
            WindowManager.LayoutParams winParams = win.getAttributes();
            int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
            if ((winParams.flags & bits) != 0) {
                mStatusBarAvailable = true;
            }
            bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
            if ((winParams.flags & bits) != 0) {
                mNavBarAvailable = true;
            }
        }

        mConfig = new SystemBarConfig(activity, mStatusBarAvailable, mNavBarAvailable);
        // device might not have virtual navigation keys
        if (!mConfig.hasNavigtionBar()) {
            mNavBarAvailable = false;
        }

        if (mStatusBarAvailable) {
            setupStatusBarView(activity, decorViewGroup);
        }
        if (mNavBarAvailable) {
            setupNavBarView(activity, decorViewGroup);
        }

    }

    /**
     * Enable tinting of the system status bar.
     *
     * If the platform is running Jelly Bean or earlier, or translucent system
     * UI modes have not been enabled in either the theme or via window flags,
     * then this method does nothing.
     *
     * @param enabled True to enable tinting, false to disable it (default).
     */
    public void setStatusBarTintEnabled(boolean enabled) {
        mStatusBarTintEnabled = enabled;
        if (mStatusBarAvailable) {
            mStatusBarTintView.setVisibility(enabled ? View.VISIBLE : View.GONE);
        }
    }

    /**
     * Enable tinting of the system navigation bar.
     *
     * If the platform does not have soft navigation keys, is running Jelly Bean
     * or earlier, or translucent system UI modes have not been enabled in either
     * the theme or via window flags, then this method does nothing.
     *
     * @param enabled True to enable tinting, false to disable it (default).
     */
    public void setNavigationBarTintEnabled(boolean enabled) {
        mNavBarTintEnabled = enabled;
        if (mNavBarAvailable) {
            mNavBarTintView.setVisibility(enabled ? View.VISIBLE : View.GONE);
        }
    }

    /**
     * Apply the specified color tint to all system UI bars.
     *
     * @param color The color of the background tint.
     */
    public void setTintColor(int color) {
        setStatusBarTintColor(color);
        setNavigationBarTintColor(color);
    }

    /**
     * Apply the specified drawable or color resource to all system UI bars.
     *
     * @param res The identifier of the resource.
     */
    public void setTintResource(int res) {
        setStatusBarTintResource(res);
        setNavigationBarTintResource(res);
    }

    /**
     * Apply the specified drawable to all system UI bars.
     *
     * @param drawable The drawable to use as the background, or null to remove it.
     */
    public void setTintDrawable(Drawable drawable) {
        setStatusBarTintDrawable(drawable);
        setNavigationBarTintDrawable(drawable);
    }

    /**
     * Apply the specified alpha to all system UI bars.
     *
     * @param alpha The alpha to use
     */
    public void setTintAlpha(float alpha) {
        setStatusBarAlpha(alpha);
        setNavigationBarAlpha(alpha);
    }

    /**
     * Apply the specified color tint to the system status bar.
     *
     * @param color The color of the background tint.
     */
    public void setStatusBarTintColor(int color) {
        if (mStatusBarAvailable) {
            mStatusBarTintView.setBackgroundColor(color);
        }
    }

    /**
     * Apply the specified drawable or color resource to the system status bar.
     *
     * @param res The identifier of the resource.
     */
    public void setStatusBarTintResource(int res) {
        if (mStatusBarAvailable) {
            mStatusBarTintView.setBackgroundResource(res);
        }
    }

    /**
     * Apply the specified drawable to the system status bar.
     *
     * @param drawable The drawable to use as the background, or null to remove it.
     */
    @SuppressWarnings("deprecation")
    public void setStatusBarTintDrawable(Drawable drawable) {
        if (mStatusBarAvailable) {
            mStatusBarTintView.setBackgroundDrawable(drawable);
        }
    }

    /**
     * Apply the specified alpha to the system status bar.
     *
     * @param alpha The alpha to use
     */
    @TargetApi(11)
    public void setStatusBarAlpha(float alpha) {
        if (mStatusBarAvailable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            mStatusBarTintView.setAlpha(alpha);
        }
    }

    /**
     * Apply the specified color tint to the system navigation bar.
     *
     * @param color The color of the background tint.
     */
    public void setNavigationBarTintColor(int color) {
        if (mNavBarAvailable) {
            mNavBarTintView.setBackgroundColor(color);
        }
    }

    /**
     * Apply the specified drawable or color resource to the system navigation bar.
     *
     * @param res The identifier of the resource.
     */
    public void setNavigationBarTintResource(int res) {
        if (mNavBarAvailable) {
            mNavBarTintView.setBackgroundResource(res);
        }
    }

    /**
     * Apply the specified drawable to the system navigation bar.
     *
     * @param drawable The drawable to use as the background, or null to remove it.
     */
    @SuppressWarnings("deprecation")
    public void setNavigationBarTintDrawable(Drawable drawable) {
        if (mNavBarAvailable) {
            mNavBarTintView.setBackgroundDrawable(drawable);
        }
    }

    /**
     * Apply the specified alpha to the system navigation bar.
     *
     * @param alpha The alpha to use
     */
    @TargetApi(11)
    public void setNavigationBarAlpha(float alpha) {
        if (mNavBarAvailable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            mNavBarTintView.setAlpha(alpha);
        }
    }

    /**
     * Get the system bar configuration.
     *
     * @return The system bar configuration for the current device configuration.
     */
    public SystemBarConfig getConfig() {
        return mConfig;
    }

    /**
     * Is tinting enabled for the system status bar?
     *
     * @return True if enabled, False otherwise.
     */
    public boolean isStatusBarTintEnabled() {
        return mStatusBarTintEnabled;
    }

    /**
     * Is tinting enabled for the system navigation bar?
     *
     * @return True if enabled, False otherwise.
     */
    public boolean isNavBarTintEnabled() {
        return mNavBarTintEnabled;
    }

    private void setupStatusBarView(Context context, ViewGroup decorViewGroup) {
        mStatusBarTintView = new View(context);
        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mConfig.getStatusBarHeight());
        params.gravity = Gravity.TOP;
        if (mNavBarAvailable && !mConfig.isNavigationAtBottom()) {
            params.rightMargin = mConfig.getNavigationBarWidth();
        }
        mStatusBarTintView.setLayoutParams(params);
        mStatusBarTintView.setBackgroundColor(DEFAULT_TINT_COLOR);
        mStatusBarTintView.setVisibility(View.GONE);
        decorViewGroup.addView(mStatusBarTintView);
    }

    private void setupNavBarView(Context context, ViewGroup decorViewGroup) {
        mNavBarTintView = new View(context);
        LayoutParams params;
        if (mConfig.isNavigationAtBottom()) {
            params = new LayoutParams(LayoutParams.MATCH_PARENT, mConfig.getNavigationBarHeight());
            params.gravity = Gravity.BOTTOM;
        } else {
            params = new LayoutParams(mConfig.getNavigationBarWidth(), LayoutParams.MATCH_PARENT);
            params.gravity = Gravity.RIGHT;
        }
        mNavBarTintView.setLayoutParams(params);
        mNavBarTintView.setBackgroundColor(DEFAULT_TINT_COLOR);
        mNavBarTintView.setVisibility(View.GONE);
        decorViewGroup.addView(mNavBarTintView);
    }

    /**
     * Class which describes system bar sizing and other characteristics for the current
     * device configuration.
     *
     */
    public static class SystemBarConfig {

        private static final String STATUS_BAR_HEIGHT_RES_NAME = "status_bar_height";
        private static final String NAV_BAR_HEIGHT_RES_NAME = "navigation_bar_height";
        private static final String NAV_BAR_HEIGHT_LANDSCAPE_RES_NAME = "navigation_bar_height_landscape";
        private static final String NAV_BAR_WIDTH_RES_NAME = "navigation_bar_width";
        private static final String SHOW_NAV_BAR_RES_NAME = "config_showNavigationBar";

        private final boolean mTranslucentStatusBar;
        private final boolean mTranslucentNavBar;
        private final int mStatusBarHeight;
        private final int mActionBarHeight;
        private final boolean mHasNavigationBar;
        private final int mNavigationBarHeight;
        private final int mNavigationBarWidth;
        private final boolean mInPortrait;
        private final float mSmallestWidthDp;

        private SystemBarConfig(Activity activity, boolean translucentStatusBar, boolean traslucentNavBar) {
            Resources res = activity.getResources();
            mInPortrait = (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
            mSmallestWidthDp = getSmallestWidthDp(activity);
            mStatusBarHeight = getInternalDimensionSize(res, STATUS_BAR_HEIGHT_RES_NAME);
            mActionBarHeight = getActionBarHeight(activity);
            mNavigationBarHeight = getNavigationBarHeight(activity);
            mNavigationBarWidth = getNavigationBarWidth(activity);
            mHasNavigationBar = (mNavigationBarHeight > 0);
            mTranslucentStatusBar = translucentStatusBar;
            mTranslucentNavBar = traslucentNavBar;
        }

        @TargetApi(14)
        private int getActionBarHeight(Context context) {
            int result = 0;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                TypedValue tv = new TypedValue();
                context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);
                result = TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics());
            }
            return result;
        }

        @TargetApi(14)
        private int getNavigationBarHeight(Context context) {
            Resources res = context.getResources();
            int result = 0;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                if (hasNavBar(context)) {
                    String key;
                    if (mInPortrait) {
                        key = NAV_BAR_HEIGHT_RES_NAME;
                    } else {
                        key = NAV_BAR_HEIGHT_LANDSCAPE_RES_NAME;
                    }
                    return getInternalDimensionSize(res, key);
                }
            }
            return result;
        }

        @TargetApi(14)
        private int getNavigationBarWidth(Context context) {
            Resources res = context.getResources();
            int result = 0;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                if (hasNavBar(context)) {
                    return getInternalDimensionSize(res, NAV_BAR_WIDTH_RES_NAME);
                }
            }
            return result;
        }

        @TargetApi(14)
        private boolean hasNavBar(Context context) {
            Resources res = context.getResources();
            int resourceId = res.getIdentifier(SHOW_NAV_BAR_RES_NAME, "bool", "android");
            if (resourceId != 0) {
                boolean hasNav = res.getBoolean(resourceId);
                // check override flag (see static block)
                if ("1".equals(sNavBarOverride)) {
                    hasNav = false;
                } else if ("0".equals(sNavBarOverride)) {
                    hasNav = true;
                }
                return hasNav;
            } else { // fallback
                return !ViewConfiguration.get(context).hasPermanentMenuKey();
            }
        }

        private int getInternalDimensionSize(Resources res, String key) {
            int result = 0;
            int resourceId = res.getIdentifier(key, "dimen", "android");
            if (resourceId > 0) {
                result = res.getDimensionPixelSize(resourceId);
            }
            return result;
        }

        @SuppressLint("NewApi")
        private float getSmallestWidthDp(Activity activity) {
            DisplayMetrics metrics = new DisplayMetrics();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                activity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
            } else {
                // TODO this is not correct, but we don't really care pre-kitkat
                activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
            }
            float widthDp = metrics.widthPixels / metrics.density;
            float heightDp = metrics.heightPixels / metrics.density;
            return Math.min(widthDp, heightDp);
        }

        /**
         * Should a navigation bar appear at the bottom of the screen in the current
         * device configuration? A navigation bar may appear on the right side of
         * the screen in certain configurations.
         *
         * @return True if navigation should appear at the bottom of the screen, False otherwise.
         */
        public boolean isNavigationAtBottom() {
            return (mSmallestWidthDp >= 600 || mInPortrait);
        }

        /**
         * Get the height of the system status bar.
         *
         * @return The height of the status bar (in pixels).
         */
        public int getStatusBarHeight() {
            return mStatusBarHeight;
        }

        /**
         * Get the height of the action bar.
         *
         * @return The height of the action bar (in pixels).
         */
        public int getActionBarHeight() {
            return mActionBarHeight;
        }

        /**
         * Does this device have a system navigation bar?
         *
         * @return True if this device uses soft key navigation, False otherwise.
         */
        public boolean hasNavigtionBar() {
            return mHasNavigationBar;
        }

        /**
         * Get the height of the system navigation bar.
         *
         * @return The height of the navigation bar (in pixels). If the device does not have
         * soft navigation keys, this will always return 0.
         */
        public int getNavigationBarHeight() {
            return mNavigationBarHeight;
        }

        /**
         * Get the width of the system navigation bar when it is placed vertically on the screen.
         *
         * @return The width of the navigation bar (in pixels). If the device does not have
         * soft navigation keys, this will always return 0.
         */
        public int getNavigationBarWidth() {
            return mNavigationBarWidth;
        }

        /**
         * Get the layout inset for any system UI that appears at the top of the screen.
         *
         * @param withActionBar True to include the height of the action bar, False otherwise.
         * @return The layout inset (in pixels).
         */
        public int getPixelInsetTop(boolean withActionBar) {
            return (mTranslucentStatusBar ? mStatusBarHeight : 0) + (withActionBar ? mActionBarHeight : 0);
        }

        /**
         * Get the layout inset for any system UI that appears at the bottom of the screen.
         *
         * @return The layout inset (in pixels).
         */
        public int getPixelInsetBottom() {
            if (mTranslucentNavBar && isNavigationAtBottom()) {
                return mNavigationBarHeight;
            } else {
                return 0;
            }
        }

        /**
         * Get the layout inset for any system UI that appears at the right of the screen.
         *
         * @return The layout inset (in pixels).
         */
        public int getPixelInsetRight() {
            if (mTranslucentNavBar && !isNavigationAtBottom()) {
                return mNavigationBarWidth;
            } else {
                return 0;
            }
        }

    }

}
引用自:https://github.com/jgilfelt/SystemBarTint

代码复制进你的项目即可,好了,这些工作完成之后我们来看下效果:


貌似已经达到效果了,但仔细观察,好像标题栏被提上去了,就是说APP界面全屏了,状态了盖在了APP上,恩,这并非我们想要的效果,那如何将界面从状态栏下部开始呢,只需要在Activity的布局文件最外层控件加上一个属性:

 android:fitsSystemWindows="true"就可以啦!看下效果:


OK,大功告成!


PS:在使用过程中发现了一些问题,使用以上方法对单个Activity有效,但是对继承了TabActivity的导航页怎么办呢?假如MainActivity继承了TabActivity,Tab1Activity、Tab2Activity、Tab3Activity是三个子项,那么设置状态栏的代码需写在MainActivity中,而 android:fitsSystemWindows="true"需写在三个子Activity的xml布局文件中,这样设置后仍然有问题,就是进入应用后首页也就是Tab1Activity没有问题,而Tab2Activity、Tab3Activity却没达到效果,它们的效果相当于未加android:fitsSystemWindows="true"时的效果,期初我怀疑是Activity不同的原因,因此我把Tab1Activity和Tab3Activity调了下位置,结果Tab3Activity成为首页后正常,而Tab1Activity又不正常了,百思不得姐,最后实在没办法,就在Tab2Activity、Tab3Activity的OnCreate方法中加了几句代码:

		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
	        ((LinearLayout)findViewById(R.id.ll)).setPadding(0, SysUtils.getStatusHeight(this), 0,0);
	    }
意思是,先求出状态栏高度,然后设置最外层控件的PaddingTop值为状态栏高度,结果正好达到效果,至于为什么只有首页Activity可以达到效果,而后面的子项无法达到效果,本人也在郁闷中,有知道的朋友可以分享下!

状态栏高度算法:

/**
		 * 状态栏高度算法
		 * @param activity
		 * @return
		 */
		public static int getStatusHeight(Activity activity){  
		    int statusHeight = 0;  
		    Rect localRect = new Rect();  
		    activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(localRect);  
		    statusHeight = localRect.top;  
		    if (0 == statusHeight){  
		        Class<?> localClass;  
		        try {  
		            localClass = Class.forName("com.android.internal.R$dimen");  
		            Object localObject = localClass.newInstance();  
		            int i5 = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString());  
		            statusHeight = activity.getResources().getDimensionPixelSize(i5);  
		        } catch (ClassNotFoundException e) {  
		            e.printStackTrace();  
		        } catch (IllegalAccessException e) {  
		            e.printStackTrace();  
		        } catch (InstantiationException e) {  
		            e.printStackTrace();  
		        } catch (NumberFormatException e) {  
		            e.printStackTrace();  
		        } catch (IllegalArgumentException e) {  
		            e.printStackTrace();  
		        } catch (SecurityException e) {  
		            e.printStackTrace();  
		        } catch (NoSuchFieldException e) {  
		            e.printStackTrace();  
		        }  
		    }  
		    return statusHeight;  
		}  

作者:qq_29443203 发表于2016/12/14 17:46:57 原文链接
阅读:25 评论:0 查看评论

如何在本地搭建一个Android应用crashing跟踪系统-ACRA

$
0
0

在开发一款移动app时的一个事实是会有很多约束,比如硬件(CPU、RAM、Battery 等等)。如果你的代码设计不是很好,你会遇到一个非常让人头疼的问题:“Crash”,研究表明:

*应用崩溃时绝大多数应用使用者抱怨的问题。

此外

  • 如果应用程序联系崩溃三次,大约一半的用户将会卸载这款应用。

崩溃跟踪系统,帮助开发者能够直接的葱用户的设备收集每一个崩溃原因,是不是发现这个功能很特殊。目前最受欢迎的崩溃跟踪系统是 CrashlyticsParse Crash Reporting,这两个系统都是完全免费的。开发者可以免费的集成他们在自己的应用中。不论什么时候app崩溃了,整个bug信息将会发送到后台,允许开发人员用最简单的方式去解决这些bug。通过这个方法,你可以在短时间内迭代一款不会影响正常使用的应用。

然而,提供崩溃信息收集的厂商收集这些崩溃信息同时也收集了用户信息,这可能让引起大公司担心用户隐私。

所以,这儿有没有崩溃信息跟踪系统可以让我们搭建在自己的服务器上?那么就不存在泄漏用户隐私的担忧了。当然有了,并且这个系统提供了非常简单的搭建方法。在这里我们来介绍下Application Crash Reporting on Android (ACRA),一个库允许Android应用自动地发送崩溃信息到自己的服务器。

下面将会介绍如何去搭建。

搭建一个服务器

服务器端是一个先决条件,让我们先从搭建服务器端开始。

由于ACRA设计的很好并且很受欢迎。它允许开发者开发自己的服务器系统,并且现在我们可以看到很多这样的系统。即便如此我觉得最好的是Acralyzer,这个也是由ACRA团队研发。Acralyzer工作在Apache CouchDB,所以 这里没有必要安装除了CouchDB以外的软件。

Acralyzer是一个功能相当齐全的后端崩溃跟踪系统。来自不同原因的相同堆栈轨迹将会被分组成一个单一的问题。如果你解决了所有问题,你可以非常便捷的关闭Acralyzer服务,并且这种关闭服务的操作时实时的,我发现系统唯一的缺点是它的ui让人感到不舒服,但是谁会在乎这个?它是为开发者开发的。

安装起来也很简单,下面将介绍如何在Ubuntu安装Acralyzer。

打开命令窗口,开始安装couchdb

*apt-get install couchdb

Test the installation with this command:

测试是否安装成功。

*curl http://127.0.0.1:5984

如果正确安装,会显示如下:

*{"couchdb":"Welcome","version":"1.2.0"}

编辑etc/couchdb/local.ini允许我们通过外部IP(默认的访问会通过127.0.0.1)去访问CouchDB。仅仅改变两行实现这个功能:

*;port = 5984 *;bind_address = 127.0.0.1

改变为

*port = 5984 *bind_address = 0.0.0.0

在同一个文件夹下,你需要添加username/password作为管理员账户。找到这一行(应该会在文件末尾)

*[admins]

下一行添加username/password 形式为username = password,比如:

*[nuuneoi = 12345]

请不要对在这里书写明文密码感到担心,一旦CouchDB重启后,你的密码将会自动地散列,并且将会是不可读的,

保存你刚刚编辑的文件同时通过命令行重启hashed:

*curl -X POST http://localhost:5984/_restart -H"Content-Type: application/json"

从现在起,你将可以通过浏览器访问CouchDB。这个web服务我们称之为Futon,一个CouchDB UI管理后台。在你的浏览器中打开这个地址。

*http://:5984/_utils

让我们开始吧,Futon。 

首先,通过你之前设置的管理员账号登陆这个系统。

现在我们开始安装一个acro-storage (Acralyzer's Storage Endpoing).在左边的菜单,点击Replicator,然后填写远程存储改为本地存储的表单。

*from Remote Database: http://get.acralyzer.com/distrib-acra-storage to Local Database: acra-myapp

点击Replicate然后等待,知道这个过程结束。

下一步安装Acralyzer通过同样的方法,但是参数是不同的。

*from Remote Database: http://get.acralyzer.com/distrib-acralyzer to Local Database: acralyzer

点击Replicate安装。

如果你操作正确,系统将会有两个数据库,acra-myapp 和 acralyzer。

我门就快大功告成了,下一步,我们需要为这个客户端创建一个用户,打开浏览器,然后打开这个网址:

*http://:5984/acralyzer/_design/acralyzer/index.html 

填写你想要的Username/Password,然后点击Create User,这些信息将会出现。 

复制这些信息,然后粘贴到你的文本编辑器,我们可能会用这个在客户端设置。

最后一件事是限制访问权限来保护在acra-myapp里面的数据,进入acra-myapp然后点击Securities,填写用户角色分配;

*["reader"] 

完工! 在这些结束后,你可以通过同一个网址访问这个控制台,去Admin选项卡,并选择Users。

*[http://:5984/acralyzer/_design/acralyzer/index.html

请注意acro-myapp只能够为一款应用服务。以防你想为另外一款应用创建一个后台,请通过同样的过程复制另外一个acro-storage,但是改变本地数据库名为acra-。请注意,有必要去通过acra- 去开启服务,或者它不能够在仪表盘中罗列为选择项供我们去选择。

如果在系统中有不止一款应用,在Acralyzer的仪表盘中将会有一个下拉列表,让我们去选择看哪一个的问题。你可以试一试。

在客户端设置ACRA。

在客户端中设置ACRA很简单,首先,在你的 build.gradle里添加ACRA的依赖配置信息。

*compile 'ch.acra:acra:4.6.1'

同步你的gradle文件,然后创建一个自定义Application类,但是不要忘记在AndroidManifest.xml中定义这个Application类。(我假设每一个Android开发者不会忘记这么做)。

在你创建的自定义的Application类中添加 @ReportCrashes注解。

import android.app.Application;
import org.acra.ACRA;
import org.acra.annotation.ReportsCrashes;
import org.acra.sender.HttpSender;

/**
 * Created by nuuneoi on 2/19/2015.
 */

@ReportsCrashes(
)
public class MainApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ACRA.init(this);
    }

}

现在我们复制服务器端生成的信息,并且像下面那样粘贴到@ReportsCrashes中。

    @ReportsCrashes(
    httpMethod = HttpSender.Method.PUT,
    reportType = HttpSender.Type.JSON,
    formUri = "http://YOUR_SERVER_IP:5984/acra-myapp/_design/acra-storage/_update/report",
    formUriBasicAuthLogin = "tester",
    formUriBasicAuthPassword = "12345"
)

最后一步,不要忘记添加在AndroidManifest.xml网络访问权限,否则ACRA可能无法发送这些日志信息到你的服务器上。

*

恭喜,现在所有的配置都已经完成,ACRA可以正常的工作,帮助你收集崩溃日志信息,从而你可以快速解决应用出现的问题。

测试

现在我们通过在Activity中强制一些崩溃来做一些测试,例子如下:

extView tvHello;

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

    tvHello.setText("Test Crash");
}

运行你的应用,然后改变崩溃的原因,再运行一次。查看你的仪表盘,你将会看到这些发送到后台的bug。

每一个bug来自不同用户不同时间,并且这些报告被分组了。 

仔细看看这些报告信息,你将会发现他们都是完整的崩溃信息。 

并且非常多的信息,足足有7页。

如果你修复这些bug后,你可以关闭这个问题,通过简单的点击在页面中高亮显示的"bug"图标, 

希望这篇文章对你们有用,特别是对于一些需要应用崩溃信息收集但是却担心隐私信息的大公司可以来使用这个系统。

事实上ACRA还有许多其他特性,比如:当一个月崩溃时显示Toast 或者 popup 来报告这些信息。你可以在ACRA网站上发现这些选项。

Acralytics也一样,这里有许多其他特性可以使用,比如,你可以设置一个服务器来发送邮件给我们。

作者:changsimeng 发表于2016/12/14 17:47:03 原文链接
阅读:18 评论:0 查看评论

RxJava学习笔记(二)操作符

$
0
0

RxJava 学习笔记(二)操作符

1 常见RxJava操作符介绍

Rxjava中的操作符提供了一种函数式编程的方式,这里列举一下个人感觉用的比较多的操作符。并列举一些可能用到的实例。本文适合于快速上手,熟悉RxJava常见操作符的使用

1.1 创建操作符

1)Create

通过调用观察者的方法从头创建一个Observable。这个没啥好说的,最基本的一个。但是2.0之后好像有点变动,以后再看。

2) From

将其它的对象或数据结构转换为Observable,会发射转入Iterable的每一项数据。个人理解就类似于for循环

3) Just

将对象或者对象集合转换为一个会发射这些对象的Observable

4) Timer

创建在一个指定的延迟之后发射单个数据的Observable,例如定时器

5) Interval

创建一个定时发射整数序列的Observable,计时器,这里注意如果需要停止他需要在filter中用条件停止,或者unsubscribe()

1.2 变换操作符

变换是RXjava中最有亮点的地方

1) map

映射,通过对序列的每一项都应用一个函数变换Observable发射的数据,实质是对序列中的每一项执行一个函数,函数的参数就是这个数据项。

简单的可以理解为一对一的变换,输入一个对象A,输出一个对象B。

例如,打印一个班所有同学任意一门课程成绩。

//打印每一名学生任意一门课程成绩
private void demo11_2_3() {
    Observable.from(getStudent())
            .map(new Func1<StudentBean, Cause>() {
                @Override
                public Cause call(StudentBean studentBean) {
                    return studentBean.getCauseList().get(generateRandom(0, studentBean.getCauseList().size()));
                }
            }).subscribe(new Action1<Cause>() {
        @Override
        public void call(Cause cause) {
            print("demo11_2_3", formatCause(cause));
        }
    });
}

可以看到,在一条调用链上,我们通过from每次传入的是StudentBean,最终输出的是Cause,就是变换。

变换原理分析

激动人心的时候来了,来看一下map的源码。

类似代理机制。可以看到内部调用了一个lift(operate)的方法,看下他的实现(仅核心代码)

// 这个是精简过后的代码,要看life的源码,可以去git仓库下载
public final <R> Observable<R> lift(final Operator<? extends R, ? super T> operator) {
    return new Observable<R>(new OnSubscribe<R>() {
        @Override
        public void call(Subscriber<? super R> o) {
             Subscriber<? super T> newSubscriber = hook.onLift(operator).call(o);
             newSubscriber.onStart();
             oldSubscriber.call(newSubscriber);
        }
    });
}

这一部分没有搞清楚的去好好研究一下Rxjava基础,至少要明白订阅关系是如何建立的。

  • 在oldObservable中创建了一个newObservable,他有一个newSubscriber;
  • 在newObservable中call方法里,operator通过call方法生成一个newSubscriber,并且oldSubscriber通过call方法和newSubscriber建立关系
  • 然后利用这个newSubscriber向Observable进行订阅

类似代理机制,拦截和处理事件序列之后的变换。我们还是举例来说明一下:

需求:Integer 转 String 这里不要说为什么不直接强制类型转换,举例子是为了辅助理解的,不要纠结。

做法一:利用map实现

 private void demo11_2_4() {
    Observable.create(new Observable.OnSubscribe<Integer>() {
        @Override
        public void call(Subscriber<? super Integer> subscriber) {
            subscriber.onNext(1);
        }
    }).map(new Func1<Integer, String>() {
        @Override
        public String call(Integer integer) {
            return integer + "";
        }
    }).subscribe(new Action1<String>() {
        @Override
        public void call(String s) {
            print("demo11_2_4", s);
        }
    });
}

做法二:利用lift实现

private void demo11_2_5() {
    Observable.create(new Observable.OnSubscribe<String>() {
        @Override
        public void call(Subscriber<? super String> subscriber) {
            subscriber.onNext("123");
        }
    }).lift(new Observable.Operator<Integer, String>() {
        @Override
        public Subscriber<? super String> call(final Subscriber<? super Integer> subscriber) {
            return new Subscriber<String>() {
                @Override
                public void onCompleted() {

                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onNext(String s) {
                    int value = Integer.parseInt(s) * 2;
                    subscriber.onNext(value);
                }
            };
        }
    }).subscribe(new Action1<Integer>() {
        @Override
        public void call(Integer integer) {
            print("demo11_2_5", integer + "");
        }
    });
}

做法三:代理

这里是把变换的代码用基本形式表示出来的

private void demo11_2_6() {
   final Observable.OnSubscribe oldOnSubScribe = new Observable.OnSubscribe<Integer>() {
        @Override
        public void call(Subscriber<? super Integer> subscriber) {
            subscriber.onNext(1);
        }
    };
    // 创建一个被观察者
    Observable oldObservable = Observable.create(oldOnSubScribe);
    // 创建一个观察者
    final Subscriber oldSubscriber = new Subscriber<String>() {
        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onNext(String o) {
            print("demo11_2_6", o);
        }
    };  
    //--------变换  用一个新的Observable处理之后代替发送订阅
    Observable newObservable = Observable.create(new Observable.OnSubscribe<String>() {
        @Override
        public void call(Subscriber<? super String> subscriber) {
            Subscriber newSubscribe = RxJavaPlugins.getInstance().getObservableExecutionHook()
                    .onLift(new OperatorMap<Integer, String>(new Func1<Integer, String>() {
                        @Override
                        public String call(Integer integer) {
                            return integer + "";
                        }
                    })).call(subscriber);
            newSubscribe.onStart();
            oldOnSubScribe.call(newSubscribe);
        }
    });
    //---------
    //订阅
    newObservable.subscribe(oldSubscriber);
}

第三种方法是源码的实现方法,只是替换掉了Func、Action

借用扔物线大神《给 Android 开发者的 RxJava 详解》的一张图来说明会直观一点。

2) FlatMap

扁平映射,将Observable发射的数据变换为Observables集合,然后将这
些Observable发射的数据平坦化的放进一个单独的Observable,可以认为是一个将嵌套的数据结构展开的过程。

简单的可以理解为一对多的变换,输入一个对象A,输出多个对象B,这是个人理解。实例,打印一个班每一名学生的每一门课程成绩。

//打印每一名学生的每一门课程成绩
private void demo11_2_2() {
    Observable.from(getStudent())
            .flatMap(new Func1<StudentBean, Observable<Cause>>() {
                @Override
                public Observable<Cause> call(StudentBean studentBean) {
                    print("demo11_2_2", studentBean.getName());
                    return Observable.from(studentBean.getCauseList());
                }
            }).subscribe(new Action1<Cause>() {
        @Override
        public void call(Cause cause) {
            print("demo11_2_2", formatCause(cause));
        }
    });
}

可以看到,我们通过from遍历了集合中的每一个学生,如果直接subscribe接收的会是StudentBean,但是我想要的数据又在StudentBean的cause集合里面,于是使用flatMap变换出一个Observable,在继续剩下的发射操作。

注意:使用Flatmap 发射的数据顺序会被打乱。要解决这个问题可以使用concatMap

3) GroupBy

分组,将原来的Observable分拆为Observable集合,将原始Observable发射的数据按Key分组,每一个Observable发射一组不同的数据。

4) Scan

扫描,对Observable发射的每一项数据应用一个函数,然后按顺序依次发射这些值,通常用来做一些通用公式的运算。

1.3 过滤操作符

1) Filter

过滤,过滤掉不符合要求的数据,只发射通过测试的。

2) Distinct

去重,过滤掉重复数据项。

3) Debounce

只有在空闲了一段时间后才发射数据,通俗的说,就是如果一段时间没有操作,就执行一次操作,可以用来做搜索框。

实例:搜索框

这里写图片描述

 // 一段时间没有变化则发送事件 0.5s 没有输入时则发送请求
private void demo11_3_3() {
    RxTextView.textChanges(mDemoEt)//监听EditText变化
            .debounce(500, TimeUnit.MILLISECONDS)//设置时间
            .observeOn(AndroidSchedulers.mainThread())//不设置在主线程消费则toast缺少looper
            .filter(new Func1<CharSequence, Boolean>() {//过滤掉空字符串
                @Override
                public Boolean call(CharSequence charSequence) {
                    return charSequence.toString().trim().length() > 0;
                }
            }).subscribe(new Action1<CharSequence>() {
        @Override
        public void call(CharSequence charSequence) {
            Log.d("demo11_3", charSequence.toString() + "thread:" + Thread.currentThread().getName());
            Toast.makeText(MainActivity.this, charSequence.toString(), Toast.LENGTH_SHORT).show();
        }
    }, new Action1<Throwable>() {//异常处理
        @Override
        public void call(Throwable throwable) {
            throwable.printStackTrace();
        }
    });
}

4) throttleFirst

取样,定期发射最新的数据,等于是数据抽样,可以用于防抖操作。

实例:防止按钮快速的多次点击 (例子中为了直观,设置的是5s响应一次点击事件

这里写图片描述

//需求:防止按钮连续快速的点击
private void demo11_3_4() {
    RxView.clicks(mDemoBt)//点击监听
            .throttleFirst(5000, TimeUnit.MILLISECONDS)//5s响应
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Action1<Void>() {
                @Override
                public void call(Void aVoid) {
                    Log.d("demo11_3_4", "click");
                    Toast.makeText(MainActivity.this, "click", Toast.LENGTH_SHORT).show();
                }
            });
}

还有辅助操作等很多操作,也可以自定义操作符,下一章将介绍如何自定义操作符。

附上操作符文档地址:https://mcxiaoke.gitbooks.io/rxdocs/content/Operators.html

代码已经上传至GitHub:https://github.com/ieewbbwe/RxJavaSamples

作者:moxiouhao 发表于2016/12/15 17:48:13 原文链接
阅读:46 评论:0 查看评论

深度学习Deep Learning(01)_CNN卷积神经网络

$
0
0

深度学习 Deep Learning

一、CNN卷积神经网络

1、概述

  • 典型的深度学习模型就是很深层的神经网络,包含多个隐含层,多隐层的神经网络很难直接使用BP算法进行直接训练,因为反向传播误差时往往会发散,很难收敛
  • CNN节省训练开销的方式是权共享weight sharing,让一组神经元使用相同的权值
  • 主要用于图像识别领域

2、卷积(Convolution)特征提取

  • 卷积核(Convolution Kernel),也叫过滤器filter,由对应的权值W和偏置b体现
  • 下图是3x3的卷积核在5x5的图像上做卷积的过程,就是矩阵做点乘之后的和
    ![enter description here][1]
    i个隐含单元的输入就是:$${W_{\rm{i}}}{x_{small}} + {b_i}$$,其中$${x_{small}}$$就时与过滤器filter过滤到的图片
  • 另外上图的步长stride1,就是每个filter每次移动的距离
  • 卷积特征提取的原理

    • 卷积特征提取利用了自然图像的统计平稳性,这一部分学习的特征也能用在另一部分上,所以对于这个图像上的所有位置,我们都能使用同样的学习特征。
    • 当有多个filter时,我们就可以学到多个特征,例如:轮廓、颜色等
  • 多个过滤器filter(卷积核)

    • 例子如下
      这里写图片描述
    • 一张图片有RGB三个颜色通道,则对应的filter过滤器也是三维的,图像经过每个filter做卷积运算后都会得到对应提取特征的图像,途中两个filter:W0和W1,输出的就是两个图像
    • 这里的步长stride2(一般就取2,3)
    • 在原图上添加zero-padding,它是超参数,主要用于控制输出的大小
    • 同样也是做卷积操作,以下图的一步卷积操作为例:
      与w0[:,:,0]卷积:0x(-1)+0x0+0x1+0x1+0x0+1x(-1)+1x0+1x(-1)+2x0=-2
      与w0[:,:,1]卷积:2x1+1x(-1)+1x1=2
      与w0[:,:,2]卷积:1x(-1)+1x(-1)=-2
      最终结果:-2+2+(-2)+1=-1 (1为偏置)
      这里写图片描述

3、池化(Pooling)

  • 也叫做下采样
  • Pooling过程
    • 把提取之后的特征看做一个矩阵,并在这个矩阵上划分出几个不重合的区域,
    • 然后在每个区域上计算该区域内特征的均值最大值,然后用这些均值或最大值参与后续的训练
      这里写图片描述
      -下图是使用最大Pooling的方法之后的结果
      这里写图片描述
  • Pooling的好处
    • 很明显就是减少参数
    • Pooling就有平移不变性((translation invariant)
      如图feature map12x12大小的图片,Pooling区域为6x6,所以池化后得到的feature map2x2,假设白色像素值为1,灰色像素值为0,若采用max pooling之后,左上角窗口值为1
      这里写图片描述
      将图像右移一个像素,左上角窗口值仍然为1
      这里写图片描述
      将图像缩放之后,左上角窗口值仍然为1
      这里写图片描述
  • Pooling的方法中average方法对背景保留更好,max对纹理提取更好
  • 深度学习可以进行多次卷积、池化操作

4、激活层

  • 在每次卷积操作之后一般都会经过一个非线性层,也是激活层
  • 现在一般选择是ReLu,层次越深,相对于其他的函数效果较好,还有Sigmod,tanh函数等
    这里写图片描述
  • sigmodtanh都存在饱和的问题,如上图所示,当x轴上的值较大时,对应的梯度几乎为0,若是利用BP反向传播算法, 可能造成梯度消失的情况,也就学不到东西了

5、全连接层 Fully connected layer

  • 将多次卷积和池化后的图像展开进行全连接,如下图所示。
    这里写图片描述
  • 接下来就可以通过BP反向传播进行训练了
  • 所以总结起来,结构可以是这样的
    这里写图片描述

6、CNN是如何工作的

  • 看到知乎上的一个回答还不错:https://www.zhihu.com/question/52668301
  • 每个过滤器可以被看成是特征标识符( feature identifiers)
  • 如下图一个曲线检测器对应的值
    这里写图片描述
  • 我们有一张图片,当过滤器移动到左上角时,进行卷积运算
    这里写图片描述
  • 当与我们的过滤器的形状很相似时,得到的值会很大
    这里写图片描述
  • 若是滑动到其他的部分,可以看出很不一样,对应的值就会很小,然后进行激活层的映射。
    这里写图片描述
  • 过滤器filter的值怎么求到,就是我们通过BP训练得到的。
作者:u013082989 发表于2016/12/15 17:49:30 原文链接
阅读:46 评论:0 查看评论

学习笔记:GoogLeNet Incepetion V2,V3

$
0
0

上次整理了googlenet V1,V2和V3在同一篇文章里进行描述的,所以我们也在这里一起学习。



tensorflow发布了所有的模型

https://github.com/tensorflow/models/blob/master/slim/README.md#pre-trained-models



论文列表:

[v1] Going Deeper with Convolutions, 6.67% test error 

http://arxiv.org/abs/1409.4842 
[v2] Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift, 4.8% test error 
http://arxiv.org/abs/1502.03167 
[v3] Rethinking the Inception Architecture for Computer Vision, 3.5% test error 
http://arxiv.org/abs/1512.00567 
[v4] Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning, 3.08% test error 
http://arxiv.org/abs/1602.07261


  • Inception v1的网络,将1x1,3x3,5x5的conv和3x3的pooling,stack在一起,一方面增加了网络的width,另一方面增加了网络对尺度的适应性;
  • v2的网络在v1的基础上,进行了改进,一方面了加入了BN层,减少了Internal Covariate Shift(内部neuron的数据分布发生变化),使每一层的输出都规范化到一个N(0, 1)的高斯,另外一方面学习VGG用2个3x3的conv替代inception模块中的5x5,既降低了参数数量,也加速计算;
  • v3一个最重要的改进是分解(Factorization),将7x7分解成两个一维的卷积(1x7,7x1),3x3也是一样(1x3,3x1),这样的好处,既可以加速计算(多余的计算能力可以用来加深网络),又可以将1个conv拆成2个conv,使得网络深度进一步增加,增加了网络的非线性,还有值得注意的地方是网络输入从224x224变为了299x299,更加精细设计了35x35/17x17/8x8的模块;
  • v4研究了Inception模块结合Residual Connection能不能有改进?发现ResNet的结构可以极大地加速训练,同时性能也有提升,得到一个Inception-ResNet v2网络,同时还设计了一个更深更优化的Inception v4模型,能达到与Inception-ResNet v2相媲美的性能。




http://blog.csdn.net/shuzfan/article/details/50738394

GoogLeNet V1出现的同期,性能与之接近的大概只有VGGNet了,并且二者在图像分类之外的很多领域都得到了成功的应用。但是相比之下,GoogLeNet的计算效率明显高于VGGNet,大约只有500万参数,只相当于Alexnet的1/12(GoogLeNet的caffemodel大约50M,VGGNet的caffemodel则要超过600M)。

GoogLeNet的表现很好,但是,如果想要通过简单地放大Inception结构来构建更大的网络,则会立即提高计算消耗。此外,在V1版本中,文章也没给出有关构建Inception结构注意事项的清晰描述。因此,在文章中作者首先给出了一些已经被证明有效的用于放大网络的通用准则和优化方法。这些准则和方法适用但不局限于Inception结构。


General Design Principles

下面的准则来源于大量的实验,因此包含一定的推测,但实际证明基本都是有效的。

1 . 避免表达瓶颈,特别是在网络靠前的地方。 信息流前向传播过程中显然不能经过高度压缩的层,即表达瓶颈。从input到output,feature map的宽和高基本都会逐渐变小,但是不能一下子就变得很小。比如你上来就来个kernel = 7, stride = 5 ,这样显然不合适。 
另外输出的维度channel,一般来说会逐渐增多(每层的num_output),否则网络会很难训练。(特征维度并不代表信息的多少,只是作为一种估计的手段)

2 . 高维特征更易处理。 高维特征更易区分,会加快训练。

3. 可以在低维嵌入上进行空间汇聚而无需担心丢失很多信息。 比如在进行3x3卷积之前,可以对输入先进行降维而不会产生严重的后果。假设信息可以被简单压缩,那么训练就会加快。

4 . 平衡网络的宽度与深度。


http://blog.csdn.net/hjimce/article/details/50866313
http://blog.csdn.net/shuzfan/article/details/50723877
http://blog.csdn.net/sunbaigui/article/details/50807398
http://yeephycho.github.io/2016/08/03/Normalizations-in-neural-networks/
Batch Normalization(http://arxiv.org/pdf/1502.03167v3.pdf)文章讲的就是BN-inception v1,它不是网络本身本质上的内容修改,而是为了将conv层的输出做normalization以使得下一层的更新能够更快,更准确。

1-Motivation

作者认为:网络训练过程中参数不断改变导致后续每一层输入的分布也发生变化,而学习的过程又要使每一层适应输入的分布,因此我们不得不降低学习率、小心地初始化。作者将分布发生变化称之为 internal covariate shift

大家应该都知道,我们一般在训练网络的时会将输入减去均值,还有些人甚至会对输入做白化等操作,目的是为了加快训练。为什么减均值、白化可以加快训练呢,这里做一个简单地说明:

首先,图像数据是高度相关的,假设其分布如下图a所示(简化为2维)。由于初始化的时候,我们的参数一般都是0均值的,因此开始的拟合y=Wx+b,基本过原点附近,如图b红色虚线。因此,网络需要经过多次学习才能逐步达到如紫色实线的拟合,即收敛的比较慢。如果我们对输入数据先作减均值操作,如图c,显然可以加快学习。更进一步的,我们对数据再进行去相关操作,使得数据更加容易区分,这样又会加快训练,如图d。 
这里写图片描述


白化的方式有好几种,常用的有PCA白化:即对数据进行PCA操作之后,在进行方差归一化。这样数据基本满足0均值、单位方差、弱相关性。作者首先考虑,对每一层数据都使用白化操作,但分析认为这是不可取的。因为白化需要计算协方差矩阵、求逆等操作,计算量很大,此外,反向传播时,白化操作不一定可导。于是,作者采用下面的Normalization方法。

2-Normalization via Mini-Batch Statistics

数据归一化方法很简单,就是要让数据具有0均值和单位方差,如下式: 
这里写图片描述 

为此,作者又为BN增加了2个参数,用来保持模型的表达能力。 
于是最后的输出为: 
这里写图片描述 
上述公式中用到了均值E和方差Var,需要注意的是理想情况下E和Var应该是针对整个数据集的,但显然这是不现实的。因此,作者做了简化,用一个Batch的均值和方差作为对整个数据集均值和方差的估计。 
整个BN的算法如下: 
这里写图片描述 



非对称卷积:
  • 1、先进行 n×1 卷积再进行 1×n 卷积,与直接进行 n×n 卷积的结果是等价的。原文如下:

    In theory, we could go even further and argue that one can replace any n × n convolution by a 1 × n convolution followed by a n × 1 convolution

  • 2、非对称卷积能够降低运算量,这个很好理解吧,原来是 n×n 次乘法,改了以后,变成了 2×n 次乘法了,n越大,运算量减少的越多,原文如下:

    the computational cost saving increases dramatically as n grows.

  • 3、虽然可以降低运算量,但这种方法不是哪儿都适用的,非对称卷积在图片大小介于12×12到20×20大小之间的时候,效果比较好,具体原因未知。。。原文如下:

    In practice, we have found that employing this factorization does not work well on early layers, but it gives very good results on medium grid-sizes (On m×m feature maps, where m ranges between 12 and 20).

  • Factorization







    网络为42层,速度为googlenet的2.5倍



    Szegedy利用了两个并行的结构完成grid size reduction,分别是conv和pool,就是上图的右半部分。左半部分是右半部分的内部结构。







     

    def InceptionV3(include_top=True, weights='imagenet',
                    input_tensor=None):
        '''Instantiate the Inception v3 architecture,
        optionally loading weights pre-trained
        on ImageNet. Note that when using TensorFlow,
        for best performance you should set
        `image_dim_ordering="tf"` in your Keras config
        at ~/.keras/keras.json.
    
        The model and the weights are compatible with both
        TensorFlow and Theano. The dimension ordering
        convention used by the model is the one
        specified in your Keras config file.
    
        Note that the default input image size for this model is 299x299.
    
        # Arguments
            include_top: whether to include the 3 fully-connected
                layers at the top of the network.
            weights: one of `None` (random initialization)
                or "imagenet" (pre-training on ImageNet).
            input_tensor: optional Keras tensor (i.e. output of `layers.Input()`)
                to use as image input for the model.
    
        # Returns
            A Keras model instance.
        '''
        if weights not in {'imagenet', None}:
            raise ValueError('The `weights` argument should be either '
                             '`None` (random initialization) or `imagenet` '
                             '(pre-training on ImageNet).')
        # Determine proper input shape
        if K.image_dim_ordering() == 'th':
            if include_top:
                input_shape = (3, 299, 299)
            else:
                input_shape = (3, None, None)
        else:
            if include_top:
                input_shape = (299, 299, 3)
            else:
                input_shape = (None, None, 3)
    
        if input_tensor is None:
            img_input = Input(shape=input_shape)
        else:
            if not K.is_keras_tensor(input_tensor):
                img_input = Input(tensor=input_tensor)
            else:
                img_input = input_tensor
    
        if K.image_dim_ordering() == 'th':
            channel_axis = 1
        else:
            channel_axis = 3
    
        x = conv2d_bn(img_input, 32, 3, 3, subsample=(2, 2), border_mode='valid')
        x = conv2d_bn(x, 32, 3, 3, border_mode='valid')
        x = conv2d_bn(x, 64, 3, 3)
        x = MaxPooling2D((3, 3), strides=(2, 2))(x)
    
        x = conv2d_bn(x, 80, 1, 1, border_mode='valid')
        x = conv2d_bn(x, 192, 3, 3, border_mode='valid')
        x = MaxPooling2D((3, 3), strides=(2, 2))(x)
    
        # mixed 0, 1, 2: 35 x 35 x 256
        for i in range(3):
            branch1x1 = conv2d_bn(x, 64, 1, 1)
    
            branch5x5 = conv2d_bn(x, 48, 1, 1)
            branch5x5 = conv2d_bn(branch5x5, 64, 5, 5)
    
            branch3x3dbl = conv2d_bn(x, 64, 1, 1)
            branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3)
            branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3)
    
            branch_pool = AveragePooling2D(
                (3, 3), strides=(1, 1), border_mode='same')(x)
            branch_pool = conv2d_bn(branch_pool, 32, 1, 1)
            x = merge([branch1x1, branch5x5, branch3x3dbl, branch_pool],
                      mode='concat', concat_axis=channel_axis,
                      name='mixed' + str(i))
    
        # mixed 3: 17 x 17 x 768
        branch3x3 = conv2d_bn(x, 384, 3, 3, subsample=(2, 2), border_mode='valid')
    
        branch3x3dbl = conv2d_bn(x, 64, 1, 1)
        branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3)
        branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3,
                                 subsample=(2, 2), border_mode='valid')
    
        branch_pool = MaxPooling2D((3, 3), strides=(2, 2))(x)
        x = merge([branch3x3, branch3x3dbl, branch_pool],
                  mode='concat', concat_axis=channel_axis,
                  name='mixed3')
    
        # mixed 4: 17 x 17 x 768
        branch1x1 = conv2d_bn(x, 192, 1, 1)
    
        branch7x7 = conv2d_bn(x, 128, 1, 1)
        branch7x7 = conv2d_bn(branch7x7, 128, 1, 7)
        branch7x7 = conv2d_bn(branch7x7, 192, 7, 1)
    
        branch7x7dbl = conv2d_bn(x, 128, 1, 1)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 7, 1)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 1, 7)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 7, 1)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7)
    
        branch_pool = AveragePooling2D((3, 3), strides=(1, 1), border_mode='same')(x)
        branch_pool = conv2d_bn(branch_pool, 192, 1, 1)
        x = merge([branch1x1, branch7x7, branch7x7dbl, branch_pool],
                  mode='concat', concat_axis=channel_axis,
                  name='mixed4')
    
        # mixed 5, 6: 17 x 17 x 768
        for i in range(2):
            branch1x1 = conv2d_bn(x, 192, 1, 1)
    
            branch7x7 = conv2d_bn(x, 160, 1, 1)
            branch7x7 = conv2d_bn(branch7x7, 160, 1, 7)
            branch7x7 = conv2d_bn(branch7x7, 192, 7, 1)
    
            branch7x7dbl = conv2d_bn(x, 160, 1, 1)
            branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 7, 1)
            branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 1, 7)
            branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 7, 1)
            branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7)
    
            branch_pool = AveragePooling2D(
                (3, 3), strides=(1, 1), border_mode='same')(x)
            branch_pool = conv2d_bn(branch_pool, 192, 1, 1)
            x = merge([branch1x1, branch7x7, branch7x7dbl, branch_pool],
                      mode='concat', concat_axis=channel_axis,
                      name='mixed' + str(5 + i))
    
        # mixed 7: 17 x 17 x 768
        branch1x1 = conv2d_bn(x, 192, 1, 1)
    
        branch7x7 = conv2d_bn(x, 192, 1, 1)
        branch7x7 = conv2d_bn(branch7x7, 192, 1, 7)
        branch7x7 = conv2d_bn(branch7x7, 192, 7, 1)
    
        branch7x7dbl = conv2d_bn(x, 160, 1, 1)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 7, 1)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 7, 1)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7)
    
        branch_pool = AveragePooling2D((3, 3), strides=(1, 1), border_mode='same')(x)
        branch_pool = conv2d_bn(branch_pool, 192, 1, 1)
        x = merge([branch1x1, branch7x7, branch7x7dbl, branch_pool],
                  mode='concat', concat_axis=channel_axis,
                  name='mixed7')
    
        # mixed 8: 8 x 8 x 1280
        branch3x3 = conv2d_bn(x, 192, 1, 1)
        branch3x3 = conv2d_bn(branch3x3, 320, 3, 3,
                              subsample=(2, 2), border_mode='valid')
    
        branch7x7x3 = conv2d_bn(x, 192, 1, 1)
        branch7x7x3 = conv2d_bn(branch7x7x3, 192, 1, 7)
        branch7x7x3 = conv2d_bn(branch7x7x3, 192, 7, 1)
        branch7x7x3 = conv2d_bn(branch7x7x3, 192, 3, 3,
                                subsample=(2, 2), border_mode='valid')
    
        branch_pool = AveragePooling2D((3, 3), strides=(2, 2))(x)
        x = merge([branch3x3, branch7x7x3, branch_pool],
                  mode='concat', concat_axis=channel_axis,
                  name='mixed8')
    
        # mixed 9: 8 x 8 x 2048
        for i in range(2):
            branch1x1 = conv2d_bn(x, 320, 1, 1)
    
            branch3x3 = conv2d_bn(x, 384, 1, 1)
            branch3x3_1 = conv2d_bn(branch3x3, 384, 1, 3)
            branch3x3_2 = conv2d_bn(branch3x3, 384, 3, 1)
            branch3x3 = merge([branch3x3_1, branch3x3_2],
                              mode='concat', concat_axis=channel_axis,
                              name='mixed9_' + str(i))
    
            branch3x3dbl = conv2d_bn(x, 448, 1, 1)
            branch3x3dbl = conv2d_bn(branch3x3dbl, 384, 3, 3)
            branch3x3dbl_1 = conv2d_bn(branch3x3dbl, 384, 1, 3)
            branch3x3dbl_2 = conv2d_bn(branch3x3dbl, 384, 3, 1)
            branch3x3dbl = merge([branch3x3dbl_1, branch3x3dbl_2],
                                 mode='concat', concat_axis=channel_axis)
    
            branch_pool = AveragePooling2D(
                (3, 3), strides=(1, 1), border_mode='same')(x)
            branch_pool = conv2d_bn(branch_pool, 192, 1, 1)
            x = merge([branch1x1, branch3x3, branch3x3dbl, branch_pool],
                      mode='concat', concat_axis=channel_axis,
                      name='mixed' + str(9 + i))
    
        if include_top:
            # Classification block
            x = AveragePooling2D((8, 8), strides=(8, 8), name='avg_pool')(x)
            x = Flatten(name='flatten')(x)
            x = Dense(1000, activation='softmax', name='predictions')(x)
    
        # Create model
        model = Model(img_input, x)
    
        # load weights
        if weights == 'imagenet':
            if K.image_dim_ordering() == 'th':
                if include_top:
                    weights_path = get_file('inception_v3_weights_th_dim_ordering_th_kernels.h5',
                                            TH_WEIGHTS_PATH,
                                            cache_subdir='models',
                                            md5_hash='b3baf3070cc4bf476d43a2ea61b0ca5f')
                else:
                    weights_path = get_file('inception_v3_weights_th_dim_ordering_th_kernels_notop.h5',
                                            TH_WEIGHTS_PATH_NO_TOP,
                                            cache_subdir='models',
                                            md5_hash='79aaa90ab4372b4593ba3df64e142f05')
                model.load_weights(weights_path)
                if K.backend() == 'tensorflow':
                    warnings.warn('You are using the TensorFlow backend, yet you '
                                  'are using the Theano '
                                  'image dimension ordering convention '
                                  '(`image_dim_ordering="th"`). '
                                  'For best performance, set '
                                  '`image_dim_ordering="tf"` in '
                                  'your Keras config '
                                  'at ~/.keras/keras.json.')
                    convert_all_kernels_in_model(model)
            else:
                if include_top:
                    weights_path = get_file('inception_v3_weights_tf_dim_ordering_tf_kernels.h5',
                                            TF_WEIGHTS_PATH,
                                            cache_subdir='models',
                                            md5_hash='fe114b3ff2ea4bf891e9353d1bbfb32f')
                else:
                    weights_path = get_file('inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5',
                                            TF_WEIGHTS_PATH_NO_TOP,
                                            cache_subdir='models',
                                            md5_hash='2f3609166de1d967d1a481094754f691')
                model.load_weights(weights_path)
                if K.backend() == 'theano':
                    convert_all_kernels_in_model(model)
        return model
    







  • 作者:lynnandwei 发表于2016/12/15 17:54:18 原文链接
    阅读:29 评论:0 查看评论

    移动端利用localresizeImg压缩图片ajax无刷新上传(asp版)

    $
    0
    0
    <!DOCTYPE HTML>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>移动端利用localresizeImg压缩图片ajax无刷新上传</title>
    <meta name="description" content="" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=0,minimum-scale=1.0,maximum-scale=1.0" />
    <script type='text/javascript' src='js/jquery-2.0.3.min.js'></script>
    <script type='text/javascript' src='js/LocalResizeIMG.js'></script>
    <script type='text/javascript' src='js/patch/mobileBUGFix.mini.js'></script>
    <style type="text/css">
    body{font-family:"微软雅黑"; font-size:12px}
    .uploadbtn{ display:block;height:40px; line-height:40px; color:#333; text-align:center; width:100%; background:#f2f2f2; text-decoration:none; }
    .imglist{min-height:200px;margin:10px;}
    .imglist img{width:100%;}
    .iptxt{width:50%;height:20px;color:#333;padding:0px 5px;border:1px solid #b6d3ff;-moz-border-radius:0.3em;-webkit-border-radius:0.3em;border-radius:0.3em;margin:1px 0;font-size:12px;}
    </style>
    </head>
    <body>
    
    <div style="width:100%;margin:10px auto; border:solid 1px #ddd; overflow:hidden;">
      <form name="myform" id="myform" action="save.asp" method="post">
        姓名:<input type="text" class="iptxt" name="realname" /><br />
        电话:<input type="text" class="iptxt" name="tel" /><br />
        身份证:<input type="button" onClick="uploadphoto.click()" value="上传正面" /> <input type="button" onClick="uploadphoto.click()" value="上传反面" /><br />
        推荐人姓名:<input type="text" class="iptxt" name="rec_realname" /><br />
        推荐人电话:<input type="text" class="iptxt" name="rec_tel" /><br />
        签名照:<input type="button" onClick="uploadphoto.click()" value="上传" />
        <input type="hidden" name="fileList" id="fileList" /> <input type="button" value="读取文件列表" onClick='alert($("#fileList").val());' />
      <input type="file" id="uploadphoto" name="uploadfile" value="请点击上传图片" style="display:none;" /> 
      <div class="imglist"></div> 
      <a href="javascript:void(0);" onclick="uploadphoto.click()" class="uploadbtn">点击上传文件</a>
        <input type="submit" value="提交" />
      </form>
    </div>
    <script type="text/javascript">
    $(document).ready(function(e) {
       $('#uploadphoto').localResizeIMG({
          width: 400,
          quality: 1,
          success: function (result) {  
    		  var submitData={
    				base64_string:result.clearBase64, 
    			}; 
    			//alert(result.clearBase64);
    		$.ajax({
    		   type: "POST",
    		   url: "upload.asp",
    		   data: submitData,
    		   dataType:"json",
    		   success: function(data){
    			 if (0 == data.status) {
    				alert(data.content);
    				return false;
    			 }else{
    				alert(data.content);
    				var attstr= '<img src="'+data.url+'" width="30%" height="30%" />'; 
    				$(".imglist").append(attstr);
    				
    				
    				var filelist=$("#fileList").val();
    				if(filelist!=''){
    					filelist+='|'+data.url;
    				}
    				else{
    					filelist=data.url;	
    				}
    				
    				$("#fileList").val(filelist);
    				
    				return false;
    			 }
    		   }, 
    			complete :function(XMLHttpRequest, textStatus){
    			},
    			error:function(XMLHttpRequest, textStatus, errorThrown){ //上传失败 
    			   alert(XMLHttpRequest.status);
    			   alert(XMLHttpRequest.readyState);
    			   alert(textStatus);
    			}
    		}); 
          }
      });
    }); 
    </script>
    </body>
    </html>
    
    

    upload.asp

    dim content,user_serial,path
    Dim xml:Set xml=Server.CreateObject("MSXML2.DOMDocument")
    Dim stm:Set stm=Server.CreateObject("ADODB.Stream")
    xml.resolveExternals=False
    
    content=Request.Form("base64_string")  'base64位加密的图片信息
    
    randomize
    ranNum=int(90000*rnd)+10000
    path="../UploadFile/"&year(now)&month(now)&day(now)&hour(now)&minute(now)&second(now)&ranNum&".jpeg"    '图片保存路径
    
    xmlStr="<?xml version=""1.0"" encoding=""gb2312""?><data>"&content&"</data>"
    
    xml.loadXML(xmlStr) '加载xml文件中的内容,使用xml解析出
    
    xml.documentElement.setAttribute "xmlns:dt","urn:schemas-microsoft-com:datatypes" 
    xml.documentElement.dataType = "bin.base64" 
    
    stm.Type=1 'adTypeBinary
    stm.Open
    stm.Write xml.documentElement.nodeTypedValue  
    stm.SaveToFile Server.MapPath(path)   '文件保存到指定路径
    'response.Write(path)
    if err.number=0 then
    	Response.Write("{""status"":1,""content"":""上传成功"",""url"":"""&path&"""}")
    else
    	Response.Write("{""status"":0,""content"":""上传失败""}")
    end if
    
    stm.Close
    Set xml=Nothing
    Set stm=Nothing

    欢迎交流  QQ: 1984756933
    
    作者:quweiie 发表于2016/12/15 17:56:12 原文链接
    阅读:47 评论:0 查看评论

    CriminalTucao

    $
    0
    0

    CriminalTucao这个小应用可以详细记录身边的各种陋习,这个小应用记录下来的陋习记录包含标题、具体时间以及照片,可以通过邮件、短信、QQ、微信等应用发送给想要吐槽的对象。

    一、应用界面

    1.空白页

    在没用吐槽记录的时候,增加空白页能够提供更佳的用户视觉体验。

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:gravity="center" >
    
            <ListView
                android:id="@android:id/list"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent" >
            </ListView>
    
            <TextView
                android:id="@android:id/empty"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:layout_gravity="center_horizontal"
                android:gravity="center_horizontal"
                android:text="@string/empty_view"
                android:textSize="24sp" />
        </LinearLayout>
    
    </FrameLayout>

    2.FragmentCrime布局

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginTop="16dp"
            android:orientation="horizontal" >
    
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="4dp"
                android:orientation="vertical" >
    
                <ImageView
                    android:id="@+id/crime_imageView"
                    android:layout_width="80dp"
                    android:layout_height="80dp"
                    android:background="@android:color/darker_gray"
                    android:cropToPadding="true"
                    android:scaleType="centerInside" />
    
                <ImageButton
                    android:id="@+id/crime_imageButton"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:src="@android:drawable/ic_menu_camera" />
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical" >
    
                <TextView
                    style="?android:listSeparatorTextViewStyle"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/crime_title_label" />
    
                <EditText
                    android:id="@+id/crime_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="16dp"
                    android:layout_marginRight="16dp"
                    android:hint="@string/crime_title_hint" />
            </LinearLayout>
        </LinearLayout>
    
        <TextView
            style="?android:listSeparatorTextViewStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/crime_detail_label" />
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp" >
    
            <Button
                android:id="@+id/btn_crime_date"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1" />
    
            <CheckBox
                android:id="@+id/crime_solved"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/crime_solved_label" />
        </LinearLayout>
    
        <Button
            android:id="@+id/crime_suspectButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:text="@string/crime_suspect_text" />
    
        <Button
            android:id="@+id/crime_reportButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:text="@string/crime_report_text" />
    
    </LinearLayout>

    3.吐槽记录布局

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" 
        android:background="@drawable/background_activated">
        <!-- 定制列表项 选择框对齐相对布局的右手边 布置两个textview相对于checkbox左对齐 -->
        <CheckBox
            android:id="@+id/crime_list_item_solvedCheckBox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:layout_alignParentRight="true"        
            android:enabled="false"
            android:focusable="false"
            android:padding="16dp"
            ></CheckBox>
        <TextView 
            android:id="@+id/crime_list_item_titleTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/crime_list_item_solvedCheckBox"
            android:textStyle="bold"
            android:paddingLeft="16dp"
            android:paddingRight="16dp"
            android:text="Crime Title"
            />
        <TextView 
            android:id="@+id/crime_list_item_dateTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/crime_list_item_titleTextView"
            android:layout_toLeftOf="@id/crime_list_item_solvedCheckBox"
            android:paddingLeft="16dp"
            android:paddingRight="16dp"
            android:paddingTop="4dp"
            android:text="Crime Date"
            />
    
    
    </RelativeLayout>

    4.拍照界面

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <LinearLayout 
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <SurfaceView 
                android:id="@+id/crime_camera_surfaceView"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                />
            <ImageButton 
                android:id="@+id/crime_camera_takePictureButton"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:src="@android:drawable/ic_menu_camera"/>
                <!--android:text="@string/take"-->
        </LinearLayout>
        <FrameLayout 
            android:id="@+id/crime_camera_progressContainer"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clickable="true">
            <ProgressBar 
                style="@android:style/Widget.ProgressBar.Large"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                />
        </FrameLayout>
    
    </FrameLayout>

    5.横屏布局

    屏幕切换后能够自适应调整布局。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="4dp"
            android:orientation="horizontal" >
    
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="4dp"
                android:orientation="vertical" >
    
                <ImageView
                    android:id="@+id/crime_imageView"
                    android:layout_width="80dp"
                    android:layout_height="80dp"
                    android:background="@android:color/darker_gray"
                    android:contentDescription="TODO"
                    android:cropToPadding="true"
                    android:scaleType="centerInside" />
    
                <ImageButton
                    android:id="@+id/crime_imageButton"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:src="@android:drawable/ic_menu_camera" />
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical" >
    
                <TextView
                    style="?android:listSeparatorTextViewStyle"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/crime_title_label" />
    
                <EditText
                    android:id="@+id/crime_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="16dp"
                    android:layout_marginRight="16dp"
                    android:hint="@string/crime_title_hint" />
            </LinearLayout>
        </LinearLayout>
    
        <TextView
            style="?android:listSeparatorTextViewStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/crime_detail_label" />
    
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:orientation="horizontal" >
    
            <Button
                android:id="@+id/btn_crime_date"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="4" />
    
            <CheckBox
                android:id="@+id/crime_solved"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="16dp"
                android:layout_weight="1"
                android:text="@string/crime_solved_label" />
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:orientation="horizontal" >
    
            <Button
                android:id="@+id/crime_suspectButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/crime_suspect_text" />
    
            <Button
                android:id="@+id/crime_reportButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/crime_report_text" />
        </LinearLayout>
    
    </LinearLayout>

    二、Crime.java

    DAO模型层

    package com.dw.criminalintent;
    
    import java.util.Date;
    import java.util.UUID;
    import org.json.JSONException;
    import org.json.JSONObject;
    
    //模型层
    public class Crime {
        private static final String JSON_ID = "id";
        private static final String JSON_TITLE = "title";
        private static final String JSON_SOLVED = "solved";
        private static final String JSON_DATE = "date";
        private static final String JSON_PHOTO = "photo";
        private static final String JSON_SUSPECT = "suspect";
    
        private UUID mId;
        private String mTitle;
        private Date mDate;
        private boolean mSolved;
        private Photo mPhoto;
        private String mSuspect;
    
        public Crime() {
            mId = UUID.randomUUID();// 生成唯一标示
            mDate = new Date();
        }
    
        public Crime(JSONObject json) throws JSONException {
            mId = UUID.fromString(json.getString(JSON_ID));
            if (json.has(JSON_TITLE)) {
                mTitle = json.getString(JSON_TITLE);
            }
            mSolved = json.getBoolean(JSON_SOLVED);
            mDate = new Date(json.getLong(JSON_DATE));
            if (json.has(JSON_PHOTO)) {
                mPhoto = new Photo(json.getJSONObject(JSON_PHOTO));
            }
            if(json.has(JSON_SUSPECT)){
                mSuspect=json.getString(JSON_SUSPECT);
            }
        }
    //实现toJson方法,以json格式保存crime对象
        public JSONObject toJSON() throws JSONException {
            JSONObject json = new JSONObject();
            json.put(JSON_ID, mId.toString());
            json.put(JSON_TITLE, mTitle);
            json.put(JSON_SOLVED, mSolved);
            json.put(JSON_DATE, mDate.getTime());
            if (mPhoto != null)
                json.put(JSON_PHOTO, mPhoto.toJSON());
            json.put(JSON_SUSPECT, mSuspect);
            return json;
        }
    
        public Date getDate() {
            return mDate;
        }
    
        public void setDate(Date date) {
            mDate = date;
        }
    
        public boolean isSolved() {
            return mSolved;
        }
    
        public void setSolved(boolean solved) {
            mSolved = solved;
        }
    
        public UUID getId() {
            return mId;
        }
    
        public void setId(UUID id) {
            mId = id;
        }
    
        public String getTitle() {
            return mTitle;
        }
    
        public void setTitle(String title) {
            mTitle = title;
        }
        public Photo getPhoto() {
            return mPhoto;
        }
    
        public void setPhoto(Photo photo) {
            mPhoto = photo;
        }
    
        public String getSuspect() {
            return mSuspect;
        }
    
        public void setSuspect(String suspect) {
            mSuspect = suspect;
        }
    
        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return mTitle;
        }
    
    }
    

    三.CrimeLab.java

    创建单例:一个私有构造方法和get方法,若实例已经存在,get方法直接返回它,若不存在,get方法会调用构造方法来创建它,在该类中进行数据持久保存。

    package com.dw.criminalintent;
    
    import java.util.ArrayList;
    import java.util.UUID;
    
    import android.content.Context;
    import android.util.Log;
    /**
     * 
     * @author dw
     *
     */
    public class CrimeLab {
        private static final String TAG="CrimeLab";
        private static final String FILENAME="crimes.json";
        private static CrimeLab sCrimeLab;//带s前缀表示静态变量
        private Context mAppContext;
        private ArrayList<Crime> mCrimes;
        private CriminalIntentJSONSerializer mSerializer;
    
    //使用context参数,单例可以完成activity的启动、获取项目资源、查找应用的私用存储空间等作用
        public CrimeLab(Context appContext) {
            mAppContext=appContext;
            //mCrimes=new ArrayList<Crime>();
            try {
                mCrimes=mSerializer.loadCrimes();
            } catch (Exception e) {
                //加载失败则新建一个数组列表
                mCrimes=new ArrayList<Crime>();
                Log.e(TAG, "error loading crimes:",e);
            }
            mSerializer=new CriminalIntentJSONSerializer(mAppContext, FILENAME);
            /*for (int i = 0; i < 50; i++) {
                Crime c=new Crime();
                c.setTitle("Crime #"+i);
                c.setSolved(i%2==0);
                mCrimes.add(c);
            }*/
        }
        public static CrimeLab get(Context c){
            if(sCrimeLab==null){
                //调用getApplicationContext方法将不确定是否存在的context替换成application context,它是针对应用的全局性context
                sCrimeLab=new CrimeLab(c.getApplicationContext());
            }
            return sCrimeLab;
        }
        //返回数组列表,新建的arraylist将内含用户自建的crime,既可以存入也可从中间调取
        public ArrayList<Crime> getCrime(){
            return mCrimes;
        }
        //返回带有指定id的crime对象
        public Crime getCrime(UUID id){
            for (Crime c : mCrimes) {
                if(c.getId().equals(id)){
                    return c;
                }
            }
            return null;
        }
        public void addCrime(Crime c){
            mCrimes.add(c);
        }
        public void deleteCrime(Crime c){
            mCrimes.remove(c);
        }
        public boolean saveCrimes(){
            try {
                mSerializer.saveCrimes(mCrimes);
                Log.d(TAG, "crimes saved to file");
                return true;
            } catch (Exception e) {
                // TODO: handle exception
                Log.e(TAG, "error saving crimes",e);
                return false;
            }
        }
    }
    

    四、CrimeListFragment.java

    ListFragment默认实现方法生成一个全屏listview布局,通过listvie将列表展示给用户,覆盖oncreateview方法可以添加更多高级功能

    package com.dw.criminalintent;
    
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import android.animation.AnimatorSet;
    import android.annotation.SuppressLint;
    import android.annotation.TargetApi;
    import android.content.Intent;
    import android.os.Build;
    import android.os.Bundle;
    import android.support.v4.app.ListFragment;
    import android.util.Log;
    import android.view.ContextMenu;
    import android.view.ContextMenu.ContextMenuInfo;
    import android.view.ActionMode;
    import android.view.LayoutInflater;
    import android.view.Menu;
    import android.view.MenuInflater;
    import android.view.MenuItem;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.animation.AlphaAnimation;
    import android.view.animation.Animation;
    import android.view.animation.AnimationSet;
    import android.view.animation.LayoutAnimationController;
    import android.view.animation.TranslateAnimation;
    import android.webkit.WebView.FindListener;
    import android.widget.AbsListView.MultiChoiceModeListener;
    import android.widget.AdapterView.AdapterContextMenuInfo;
    import android.widget.ArrayAdapter;
    import android.widget.CheckBox;
    import android.widget.ListView;
    import android.widget.TextView;
    
    /**
     * 
     * @author dw
     * 
     */
    public class CrimeListFragment extends ListFragment {
        private static final int REQUEST_CRIME = 1;
        private ArrayList<Crime> mCrimes;
        private boolean mSubtitleVisible;
        private final static String TAG = "CrimeListFragment";
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            // TODO Auto-generated method stub
            super.onCreate(savedInstanceState);
    
            setHasOptionsMenu(true);// 右上角选项菜单
            // 将显示在操作栏的文字替换为传入的字符串资源中设定的文字,设置托管Crimelistfragment的activity标题
            // getactivity方法不仅可以返回托管activity还允许fragment处理更多的activity相关的事务
            getActivity().setTitle(R.string.crime_title_label);
            // 先获取单例,在获取其中的crime
            mCrimes = CrimeLab.get(getActivity()).getCrime();
            /*
             * ArrayAdapter<Crime> adapter=new ArrayAdapter<Crime>(getActivity(),
             * android.R.layout.simple_list_item_1, mCrimes);
             */
            CrimeAdapter adapter = new CrimeAdapter(mCrimes);
    
            setListAdapter(adapter);
            // 可以为CrimeListFragment管理内置Listview的adapter
    
            setRetainInstance(true);
            mSubtitleVisible = false;
        }
    
        // 无论是单机硬按键还是软键盘,或者是手指的触摸,都会触发该方法
        @Override
        public void onListItemClick(ListView l, View v, int position, long id) {
            // 使adapter返回被点击的列表项所对应的crime对象
            Crime c = ((CrimeAdapter) getListAdapter()).getItem(position);
            Log.d(TAG, c.getTitle() + "was clicked");
            //从crimeListFragment中启动Crime明细页,这里用的是显式intent
            //intent构造方法所需的context对象使用的是getActivity方法传入它的托管activity来满足
            Intent i = new Intent(getActivity(), CrimePagerActivity.class);
            //将crimeId的值附加到extra上,可以告知crimefragment应该显示的crime
            i.putExtra(CrimeFragment.EXTRA_CRIME_ID, c.getId());
            startActivityForResult(i, REQUEST_CRIME);
        }
    
        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
            if (requestCode == REQUEST_CRIME) {
                // handle result
            }
        }
    
        // crimelistactivity恢复运行后操作系统会向它发出调用onresume的指定,接到指令后,
        //它的fragmentmanager会调用当前被activity托管的fragment的onresume方法,要保证fragment视图得到刷新,在这里刷新是最安全的选择
        @Override
        public void onResume() {
            // TODO Auto-generated method stub
            super.onResume();
            ((CrimeAdapter) getListAdapter()).notifyDataSetInvalidated();// 刷新列表
        }
    
        @Override
        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
            // TODO Auto-generated method stub
            super.onCreateOptionsMenu(menu, inflater);
            //将菜单文件的资源id传入并将文件中定义的菜单项目填充到menu实例中
            inflater.inflate(R.menu.fragment_crime_list, menu);
    
            MenuItem showSubtitle = menu.findItem(R.id.menu_item_show_subtitle);
            if (mSubtitleVisible && showSubtitle != null) {
                showSubtitle.setTitle(R.string.hide_subtitle);
            }
        }
    //点击选项菜单中的菜单项时,fragment会收到该方法的回调请求
        @TargetApi(11)
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // TODO Auto-generated method stub
            switch (item.getItemId()) {
            case R.id.menu_item_new_crime:
                Crime crime = new Crime();
                CrimeLab.get(getActivity()).addCrime(crime);
                Intent i = new Intent(getActivity(), CrimePagerActivity.class);
                i.putExtra(CrimeFragment.EXTRA_CRIME_ID, crime.getId());
                startActivityForResult(i, 0);
                return true;
                //实现菜单项标题与子标题的联动显示
            case R.id.menu_item_show_subtitle:
                if (getActivity().getActionBar().getSubtitle() == null) {
                    getActivity().getActionBar().setSubtitle(R.string.subtitle);
                    mSubtitleVisible = true;
                    item.setTitle(R.string.hide_subtitle);
                } else {
                    getActivity().getActionBar().setSubtitle(null);
                    mSubtitleVisible = false;
                    item.setTitle(R.string.show_subtitle);
                }
                return true;
                //若菜单项id不存在,默认的超类版本方法会被调用
            default:
                return super.onOptionsItemSelected(item);// 若ID项不存在,会调用默认的超类版本方法
            }
        }
    
        @Override
        public void onCreateContextMenu(ContextMenu menu, View v,
                ContextMenuInfo menuInfo) {
            // 用菜单资源文件中定义的菜单项填充菜单实例
            getActivity().getMenuInflater().inflate(R.menu.crime_list_item_context,
                    menu);
        }
    
        // 监听上下文菜单选择事件
        @Override
        public boolean onContextItemSelected(MenuItem item) {
            // TODO Auto-generated method stub
            AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
                    .getMenuInfo();
            int positon = info.position;
            CrimeAdapter adapter = (CrimeAdapter) getListAdapter();
            Crime crime = adapter.getItem(positon);
    
            switch (item.getItemId()) {
            case R.id.menu_item_delete_crime:
                CrimeLab.get(getActivity()).deleteCrime(crime);
                adapter.notifyDataSetChanged();
                return true;
            }
            return super.onContextItemSelected(item);
        }
    
        @TargetApi(11)
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup parent,
                Bundle savedInstanceState) {
            // TODO Auto-generated method stub
            //View v = super.onCreateView(inflater, parent, savedInstanceState);
            View v=inflater.inflate(R.layout.empty_crime, null);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                //根据mSubtitleVisible的值确定是否要设置子标题
                if (mSubtitleVisible) {
                    getActivity().getActionBar().setSubtitle(R.string.subtitle);
                }
            }
            ListView listView = (ListView) v.findViewById(android.R.id.list);
            //显示空视图
            if(mCrimes==null){
                listView.setEmptyView(v.findViewById(android.R.id.empty));
            }
    
            //给listview添加动画效果
            AnimationSet set =new AnimationSet(true);
            Animation animation=new AlphaAnimation(0.0f, 1.0f);
            animation.setDuration(100);
            set.addAnimation(animation);
            animation=new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF,
                    0.0f,Animation.RELATIVE_TO_SELF , -1.0f, Animation.RELATIVE_TO_SELF, 0.0f);
            LayoutAnimationController controller=new LayoutAnimationController(set, 0.5f);
            listView.setLayoutAnimation(controller);
    
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
                registerForContextMenu(listView);// 登记菜单列表项,长按删除crime
            } else {
                // 多选delete操作
                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
                listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
                    @Override
                    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                        // not used
                        return false;
                    }
    
                    @Override
                    public void onDestroyActionMode(ActionMode arg0) {
    
                    }
                    @Override
                    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                        MenuInflater inflater = mode.getMenuInflater();
                        inflater.inflate(R.menu.crime_list_item_context, menu);
                        return true;
                    }
                    @Override
                    public boolean onActionItemClicked(ActionMode mode,
                            MenuItem item) {
                        switch (item.getItemId()) {
                        case R.id.menu_item_delete_crime:
                            CrimeAdapter adapter = (CrimeAdapter) getListAdapter();
                            CrimeLab crimeLab = CrimeLab.get(getActivity());
                            for (int i = adapter.getCount() - 1; i >= 0; i--) {
                                if (getListView().isItemChecked(i)) {
                                    crimeLab.deleteCrime(adapter.getItem(i));
                                }
                            }
                            mode.finish();
                            adapter.notifyDataSetChanged();
                            return true;
                        default:
                            return false;
                        }
                    }
    
                    @Override
                    public void onItemCheckedStateChanged(ActionMode mode,
                            int position, long id, boolean checked) {
                        // not used
                    }
                });
            }
            return v;
        }
    
        // 添加定制的内部类
        private class CrimeAdapter extends ArrayAdapter<Crime> {
            // 调用超类的构造方法来绑定crime对象的数组列表
            public CrimeAdapter(ArrayList<Crime> crimes) {
                // 参数0表示不使用预定义布局
                super(getActivity(), 0, crimes);
                // TODO Auto-generated constructor stub
            }
            // 当上下滚动列表时,listview调用getView方法,按需获取要显示的视图;创建并返回定制列表项,填充对应的crime数据
            //首先检查传入的视图对象是否是复用对象,若不是则从定制布局里产生一个新的视图对象。
            @SuppressLint("SimpleDateFormat")
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                if (convertView == null) {
                    convertView = getActivity().getLayoutInflater().inflate(
                            R.layout.list_item_crime, null);
                }
                //不管是新对象还是复用对象,都调用该方法获取当前position的crime对象
                Crime c = getItem(position);
                TextView titleTextView = (TextView) convertView
                        .findViewById(R.id.crime_list_item_titleTextView);
                titleTextView.setText(c.getTitle());
                TextView dateTextView = (TextView) convertView
                        .findViewById(R.id.crime_list_item_dateTextView);
                // 格式化时间
                SimpleDateFormat simpleFormatDisplay = new SimpleDateFormat(
                        "yyyy年MM月dd日 E HH:mm:ss");
                dateTextView.setText(simpleFormatDisplay.format(c.getDate()));
    
                //出现在列表项布局内的任何可聚焦组件(checkbox或button)都应设置为非聚焦状态focusable=false
                CheckBox solvedCheckBox = (CheckBox) convertView
                        .findViewById(R.id.crime_list_item_solvedCheckBox);
                solvedCheckBox.setChecked(c.isSolved());
                return convertView;
            }
        }
    }

    五、CrimePagerActivity.java

    明细视图相关的类,用以托管crimeFragment。

    package com.dw.criminalintent;
    
    import java.util.ArrayList;
    import java.util.UUID;
    
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.support.v4.app.FragmentActivity;
    import android.support.v4.app.FragmentManager;
    import android.support.v4.app.FragmentStatePagerAdapter;
    import android.support.v4.view.ViewPager;
    import android.support.v4.view.ViewPager.OnPageChangeListener;
    /**
     * 
     * @author dw
     *
     */
    public class CrimePagerActivity extends FragmentActivity {
        //viewpager默认加载当前屏幕上的列表项,以及左右相邻页面的数据,从而实现页面滑动的快速切换;默认只显示pageradapter中的第一个列表项。
        private ViewPager mViewPager;
        private ArrayList<Crime> mCrimes;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            // TODO Auto-generated method stub
            super.onCreate(savedInstanceState);
            //以代码的方式创建内容视图
            mViewPager=new ViewPager(this);
            mViewPager.setId(R.id.viewPager);
            setContentView(mViewPager);
            //从crimelab中获取数据集
            mCrimes=CrimeLab.get(this).getCrime();
            //获取activity 的fm实例
            FragmentManager fm=getSupportFragmentManager();
            //设置adpter为FragmentStatePagerAdapter的一个匿名实例,负责管理与viewpager的对话协同工作
            mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) {
                @Override
                public int getCount() {
                    // TODO Auto-generated method stub
                    return mCrimes.size();
                }
                //调用该方法获取crime数组指定位置的crime时,会返回一个已配置的用于显示指定位置crime信息的crimeFragment
                @Override
                public Fragment getItem(int position) {
                    // TODO Auto-generated method stub
                    Crime crime=mCrimes.get(position);
                    return CrimeFragment.newInstance(crime.getId());
                }
            });
            //监听viewpager当前显示页面的状态变化,页面状态变化时可将Crime实例的标题设置给该activity的标题;
            mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
                //当前选择的页面
                @Override
                public void onPageSelected(int pos) {
                    // TODO Auto-generated method stub
                    Crime crime=mCrimes.get(pos);
                    if(crime.getTitle()!=null){
                        setTitle(crime.getTitle());
                    }
                }
                //通知页面将会滑向哪里
                @Override
                public void onPageScrolled(int pos, float posOffset, int posOffsetPixels) {
                }
                //告知动迁页面所处的状态,滑动、页面滑动入位到完全静止及页面切换完成后的闲置状态
                @Override
                public void onPageScrollStateChanged(int state) {
                }
            });
            //当从CrimePageActivity创建crimefragment 时应调用crimefragment中的newInstance方法,并传入获取的UUID
            UUID crimeId=(UUID) getIntent().getSerializableExtra(CrimeFragment.EXTRA_CRIME_ID);
            //循环检查crime的id,找到所选crime在数组中的位置,解决viewpager默认显示第一项的问题
            for(int i=0;i<mCrimes.size();i++){
                if(mCrimes.get(i).getId().equals(crimeId)){
                    mViewPager.setCurrentItem(i);
                    break;
                }
            }
        }
    
    }
    

    七、SingleFragmentActivity.java

    通过代码的方式将fragment添加到activity中,activity中的fm负责调用fragment的生命周期方法,添加fragment供fm管理时,onAttach,onCreate,onCreateView方法会被调用。

    package com.dw.criminalintent;
    
    import android.support.v4.app.FragmentManager;
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.support.v4.app.FragmentActivity;
    /**
     * 
     * @author dw 
     */
    public abstract class SingleFragmentActivity extends FragmentActivity {
        protected abstract Fragment createFragment();//抽象方法
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            // TODO Auto-generated method stub
            super.onCreate(savedInstanceState);
            //设置从activity_fragment布局里生成activity视图,
            setContentView(R.layout.activity_fragment);
            //获取一个fragment交由fm管理,fm管理着fragment事务的回退栈
            FragmentManager fm=getSupportFragmentManager();
            //然后在容器中寻找fm里的fragment,若不存在则创建一个新的fragment并将其添加到容器中
            Fragment fragment=fm.findFragmentById(R.id.fragmentContainer);
            if(fragment==null){
                fragment=createFragment();
                //fragment事务被用来添加、删除、附加分离或替换fragment队列中的fragment,这是使用fragment在运行时组装和重新组装界面的核心方式
                fm.beginTransaction().add(R.id.fragmentContainer, fragment)//第二个参数表示新创建的fragment
                                     .commit();
            }
        }
    }
    

    八、CrimeCameraActivity.java

    package com.dw.criminalintent;
    
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.view.Window;
    import android.view.WindowManager;
    
    public class CrimeCameraActivity extends SingleFragmentActivity {
    
        @Override
        protected Fragment createFragment() {
            // TODO Auto-generated method stub
            return new CrimeCameraFragment();
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            // TODO Auto-generated method stub
            //隐藏操作栏和状态栏
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            //全屏
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
            super.onCreate(savedInstanceState);
        }
    
    }
    

    九、CriminalIntentJSONSerializer.java

    创建和解析JSON数据。

    package com.dw.criminalintent;
    
    import java.io.BufferedReader;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.Writer;
    import java.util.ArrayList;
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONTokener;
    import android.content.Context;
    
    /**
     *
     * @author dw
     *
     */
    public class CriminalIntentJSONSerializer {
        private Context mContext;
        private String mFilename;
    
        public CriminalIntentJSONSerializer(Context context, String filename) {
            mContext = context;
            mFilename = filename;
        }
    //先调用openfileoutput方法获取呕吐葡萄stream对象,然后创建一个outputstreamwriter对象,最后调用它的写入方法写入数据
        public ArrayList<Crime> loadCrimes() throws IOException, JSONException {
            ArrayList<Crime> crimes = new ArrayList<Crime>();
            BufferedReader reader = null;
            try {
                InputStream in = mContext.openFileInput(mFilename);
                reader=new BufferedReader(new InputStreamReader(in));
                StringBuilder jsonString = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    jsonString.append(line);
                }
                JSONArray array = (JSONArray) new JSONTokener(jsonString.toString())
                        .nextValue();
                for (int i = 0; i < array.length(); i++) {
                    crimes.add(new Crime(array.getJSONObject(i)));
                }
            } catch (FileNotFoundException e) {
            } finally {
                if (reader != null) {
                    reader.close();
                }
            }
            return crimes;
        }
    
        public void saveCrimes(ArrayList<Crime> crimes) throws IOException {
            JSONArray array = new JSONArray();
            for (Crime c : crimes) {
                try {
                    array.put(c.toJSON());
                } catch (JSONException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
    
                Writer writer = null;
                try {
                    OutputStream out = mContext.openFileOutput(mFilename,
                            Context.MODE_PRIVATE);
                    writer = new OutputStreamWriter(out);
                    writer.write(array.toString());
                } catch (Exception e) {
                    // TODO: handle exception
                } finally {
                    if (writer != null) {
                        writer.close();
                    }
                }
            }
        }
    
    }
    

    十、DatePickerFragment.java

    该fragment也是由crimepageractivity托管。

    package com.dw.criminalintent;
    
    import java.util.Calendar;
    import java.util.Date;
    import java.util.GregorianCalendar;
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.app.Dialog;
    import android.content.DialogInterface;
    import android.content.Intent;
    import android.os.Bundle;
    import android.support.v4.app.DialogFragment;
    import android.view.View;
    import android.widget.DatePicker;
    import android.widget.DatePicker.OnDateChangedListener;
    /**
     *
     * @author dw  
     *
     */
    public class DatePickerFragment extends DialogFragment {
        public static final String EXTRA_DATE = "com.dw.criminalintent.date";
        private Date mDate;
    
        public static DatePickerFragment newInstance(Date date) {
            Bundle args = new Bundle();
            args.putSerializable(EXTRA_DATE, date);
            DatePickerFragment fragment = new DatePickerFragment();
            fragment.setArguments(args);
            return fragment;
        }
    //创建dialogFragment,在屏幕上显示dialogFragment时,托管activity的fm会调用此方法
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            mDate = (Date) getArguments().getSerializable(EXTRA_DATE);
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(mDate);
            int year = calendar.get(Calendar.YEAR);
            int month = calendar.get(Calendar.MONTH);
            int day = calendar.get(Calendar.DAY_OF_MONTH);
            // 以流接口的方式创建alertDialog实例
            View v = getActivity().getLayoutInflater().inflate(
                    R.layout.dialog_date, null);
    
            DatePicker datePicker = (DatePicker) v
                    .findViewById(R.id.dialog_date_datePicker);
            datePicker.init(year, month, day, new OnDateChangedListener() {
                //
                @Override
                public void onDateChanged(DatePicker view, int year, int month,
                        int day) {
                    // TODO Auto-generated method stub
                    mDate = new GregorianCalendar(year, month, day).getTime();
                    getArguments().putSerializable(EXTRA_DATE, mDate);
                }
            });
            //以流接口的方式创建一个alertdialog实例
            return new AlertDialog.Builder(getActivity())
                    .setView(v)
                    .setTitle(R.string.date_picker_title)
                    .setPositiveButton(android.R.string.ok,
                            new DialogInterface.OnClickListener() {
                        //响应确定按钮事件,调用sendResult方法传入结果代码,以更新日期数据
                                @Override
                                public void onClick(DialogInterface dialog,
                                        int which) {
                                    // TODO Auto-generated method stub
                                    sendResult(Activity.RESULT_OK);
                                }
                            }).create();
        }
    //创建一个intent将日期数据附加到intent上,最后调用onActivityResult
        private void sendResult(int resultCode) {
            // TODO Auto-generated method stub
            if (getTargetFragment() == null)
                return;
            Intent i = new Intent();
            i.putExtra(EXTRA_DATE, mDate);
            getTargetFragment().onActivityResult(getTargetRequestCode(),
                    resultCode, i);
        }
    }
    

    十一、ImageFragment.java

    package com.dw.criminalintent;
    
    import android.graphics.drawable.BitmapDrawable;
    import android.os.Bundle;
    import android.support.v4.app.DialogFragment;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    
    public class ImageFragment extends DialogFragment {
        public static final String EXTRA_IMAGE_PATH="com.dw.criminalintent.image_path";
        private ImageView mImageView;
    
    
        //获取文件路径并获取缩小版的图片设置给imageview,最后只要图片不再需要,就主动释放内存
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup parent,
                Bundle savedInstanceState) {
            mImageView=new ImageView(getActivity());
            String path=(String) getArguments().getSerializable(EXTRA_IMAGE_PATH);
            BitmapDrawable image=PictureUtils.getScaleBitmapDrawable(getActivity(), path);
            mImageView.setImageDrawable(image);
            return mImageView;
        }
    
        @Override
        public void onDestroyView() {
            // TODO Auto-generated method stub
            super.onDestroyView();
            PictureUtils.cleanImageView(mImageView);
        }
    
        public static ImageFragment newInstance(String imagePath){
            Bundle args=new Bundle();
            args.putSerializable(EXTRA_IMAGE_PATH, imagePath);
    
            ImageFragment fragment=new ImageFragment();
            fragment.setArguments(args);
            fragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
            return fragment;
        }
    
    }
    

    十二、Photo.java

    package com.dw.criminalintent;
    
    import org.json.JSONException;
    import org.json.JSONObject;
    
    public class Photo {
        private static final String JSON_FILENAME="filename";
        private String mFilename;
        //创建photo对象
        public Photo(String filename) {
            mFilename = filename;
        }
        //json序列化方法,保存及加载photo数据
        public Photo(JSONObject json) throws JSONException{
            mFilename=json.getString(JSON_FILENAME);
        }
        public JSONObject toJSON() throws JSONException{
            JSONObject json=new JSONObject();
            json.put(JSON_FILENAME, mFilename);
            return json;
        }
        public String getFilename(){
            return mFilename;
        }
    }
    

    十三、PictureUtils.java

    package com.dw.criminalintent;
    
    import android.app.Activity;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.drawable.BitmapDrawable;
    import android.view.Display;
    import android.widget.ImageView;
    
    public class PictureUtils {
    
        @SuppressWarnings("deprecation")
        public static BitmapDrawable getScaleBitmapDrawable(Activity a,String path){
            Display diaplay=a.getWindowManager().getDefaultDisplay();
            float destWidth=diaplay.getWidth();
            float destHeight=diaplay.getHeight();
    
            //读取硬盘上的图片dimensions
            BitmapFactory.Options options=new BitmapFactory.Options();
            options.inJustDecodeBounds=true;
            BitmapFactory.decodeFile(path, options);
    
            float srcWidth=options.outWidth;
            float srcHeight=options.outHeight;
    
            int inSampleSize=1;
            if(srcHeight>destHeight||srcWidth>destWidth){
                if(srcWidth>srcHeight){
                    inSampleSize=Math.round(srcHeight/destHeight);
                }else{
                    inSampleSize=Math.round(srcWidth/destWidth);
                }
            }
            options=new BitmapFactory.Options();
            options.inSampleSize=inSampleSize;
    
            Bitmap bitmap=BitmapFactory.decodeFile(path, options);
            return new BitmapDrawable(a.getResources(),bitmap);
    
        }
    
        public static void cleanImageView(ImageView imageView){
            if(!(imageView.getDrawable() instanceof BitmapDrawable))
                return;
            //清除该视图的图片以节省存储空间
            BitmapDrawable b=(BitmapDrawable) imageView.getDrawable();
            b.getBitmap().recycle();//释放bitmap占用的原始内存空间
            imageView.setImageDrawable(null);
        }
    
    }
    

    十四、CrimePagerActivity.java

    明细视图相关的类,用以托管crimeFragment。

    package com.dw.criminalintent;
    
    import java.util.ArrayList;
    import java.util.UUID;
    
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.support.v4.app.FragmentActivity;
    import android.support.v4.app.FragmentManager;
    import android.support.v4.app.FragmentStatePagerAdapter;
    import android.support.v4.view.ViewPager;
    import android.support.v4.view.ViewPager.OnPageChangeListener;
    /**
     * 
     * @author dw
     *
     */
    public class CrimePagerActivity extends FragmentActivity {
        //viewpager默认加载当前屏幕上的列表项,以及左右相邻页面的数据,从而实现页面滑动的快速切换;默认只显示pageradapter中的第一个列表项。
        private ViewPager mViewPager;
        private ArrayList<Crime> mCrimes;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            // TODO Auto-generated method stub
            super.onCreate(savedInstanceState);
            //以代码的方式创建内容视图
            mViewPager=new ViewPager(this);
            mViewPager.setId(R.id.viewPager);
            setContentView(mViewPager);
            //从crimelab中获取数据集
            mCrimes=CrimeLab.get(this).getCrime();
            //获取activity 的fm实例
            FragmentManager fm=getSupportFragmentManager();
            //设置adpter为FragmentStatePagerAdapter的一个匿名实例,负责管理与viewpager的对话协同工作
            mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) {
                @Override
                public int getCount() {
                    // TODO Auto-generated method stub
                    return mCrimes.size();
                }
                //调用该方法获取crime数组指定位置的crime时,会返回一个已配置的用于显示指定位置crime信息的crimeFragment
                @Override
                public Fragment getItem(int position) {
                    // TODO Auto-generated method stub
                    Crime crime=mCrimes.get(position);
                    return CrimeFragment.newInstance(crime.getId());
                }
            });
            //监听viewpager当前显示页面的状态变化,页面状态变化时可将Crime实例的标题设置给该activity的标题;
            mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
                //当前选择的页面
                @Override
                public void onPageSelected(int pos) {
                    // TODO Auto-generated method stub
                    Crime crime=mCrimes.get(pos);
                    if(crime.getTitle()!=null){
                        setTitle(crime.getTitle());
                    }
                }
                //通知页面将会滑向哪里
                @Override
                public void onPageScrolled(int pos, float posOffset, int posOffsetPixels) {
                }
                //告知动迁页面所处的状态,滑动、页面滑动入位到完全静止及页面切换完成后的闲置状态
                @Override
                public void onPageScrollStateChanged(int state) {
                }
            });
            //当从CrimePageActivity创建crimefragment 时应调用crimefragment中的newInstance方法,并传入获取的UUID
            UUID crimeId=(UUID) getIntent().getSerializableExtra(CrimeFragment.EXTRA_CRIME_ID);
            //循环检查crime的id,找到所选crime在数组中的位置,解决viewpager默认显示第一项的问题
            for(int i=0;i<mCrimes.size();i++){
                if(mCrimes.get(i).getId().equals(crimeId)){
                    mViewPager.setCurrentItem(i);
                    break;
                }
            }
        }
    }
    

    整个Demo写下来,几乎覆盖到了Android开发的所有知识点,收获还是蛮大的,更多细节的东西后续慢慢再完善。

    作者:Arthur_02_13 发表于2016/12/15 18:02:38 原文链接
    阅读:51 评论:0 查看评论

    微机技术题库复习题

    $
    0
    0
    这几天写题写的头晕,下下周就期末,只对微机技术发愁。
    1【单选题】
    无论是微处理器、微型计算机还是微型计算机系统,都是采用(A )连接各部件而构成的一个整体。
    
          A总线结构框架
          B控制总线
          C输入输出接口
          D内外存储器
    
    2【单选题】
    8086微处理器有20根地址线,所以物理地址编址范围为(A )。
    
          A0000H-FFFFH
          B10000H-20000H
          C00000H-FFFFFH
          D12345H-34567H
    
    3【单选题】
    8086物理地址的计算方法是( A)。
    
          A段基址左移4位加上16位段内偏移量。
          B段基址右移4位加上16位段内偏移量。
          C段基址加上16位段内偏移量。
          DIP的值
    
    4【单选题】
    8086CPU地址信息在( D)时钟周期出现。
    
          AT4
          BT3
          C T2
          DT1
    
    5【单选题】
    8086系统复位时代码段寄存器置(D )。
    
          A0000H
          B00FFH
          CFF00H
          DFFFFH
    
    6【单选题】
    用BP作基址寻址时,若无指定段替换,则默认在(B )内寻址。
    
          A代码段
          B堆栈段
          C数据段
          D附加段
    
    7【单选题】
    段定义语句的定位类型为PARA,其段基址为(A )。
    
          AXXXX XXXX XXXX XXXX 0000B
          BXXXX XXXX XXXX XXXX XXXXB
          CXXXX XXXX XXXX XXXX XXX0B
          DXXXX XXXX XXXX 0000 0000B
    
    8【单选题】
    数据定义语句DD命令定义数据长度为(B )字节。
    
          A1
          B2
          C3
          D4
    
    9【单选题】
    程序段内跳转需处理( B)。
    
          ACS
          BIP
          CCS和IP
          DDS
    
    10【单选题】
    DOS系统功能调用采用中断(C )。
    
          A15H
          B18H
          C21H
          D30H
    
    11【单选题】
    CS=1234H,IP=0000H,则指令物理地址为(C )。
    (CS×10+IP)
          A1234H
          B4321H
          C12340H
          D01234H
    
    12【单选题】
    ASCII码表中的十进制数由( C)代表。
    
          A30H-39H
          B40H-49H
          C 50H-59H
          D60H-69H
    
    13【单选题】
    微型计算机的发展特点是(D)。
    
          A体积越来越小 
          B容量越来越大
          C 精度越来越高
          D以上都对
    
    14【单选题】
    标准的ASCII码由( C)位二进制代码组成。
    
          A 5
          B6
          C7
          D8
    
    15【单选题】
    堆栈的工作方式是(B )。
    
          A先进先出    
          B后进先出 
          C随机读写   
          D只能读出不能写入
    
    16【单选题】
    指令的指针寄存器是(B )。
    
    
          ABX
          BIP
          CBP
          D SP
    
    17【单选题】
    下面的说法中,(B )是正确的。
    
          A指令周期等于总线周期
          B 指令周期大于等于总线周期
          C指令周期是总线周期的两倍
          D指令周期与总线周期之间的关系不确定     
    
    
    
    18【单选题】
    在8086系统中,在以下地址中可作为段起始地址的为:( A)
    
          A20100H
          B 20102H
          C20104H
          D20108H     
    
    
    
    19【单选题】
    8086执行一个总线周期最多可传送( A)字节。
    
          A1个
          B 2个
          C 3个
          D4个
    
    20【单选题】
    在8086中,一个最基本的总线周期由4个时钟周期(T状态)组成,在T1状态,CPU在总线上发出(B )信息。
    
    
          A数据
          B 地址
          C状态
          D其他
    
    21【单选题】
    对于8086微处理器,可屏蔽中断请求输入信号加在(B )引脚。
    
          AALE
          BINTR
          CNMI
          DCLK
    
    22【单选题】
    下面的指令不合法的是(D )。
    
    
          AINT 21H
          BADC AX,[SI]
          C IN AX,03H
          D PUSH AL 
    
    
    
    23【单选题】
    设SP=50H,执行段间返回指令RET 后,寄存器SP的内容是( B)。
    
    
          A 44H 
          B54H
          C5AH
          D5CH
    
    24【单选题】
    在指令MOV  ES:[SI],AX 中,目的操作数为(C )寻址方式。
    
    
    
          A 寄存器
          B直接
          C基址变址
          D 寄存器间接
    
    25【单选题】
    在子程序的最后一定要有一条( B)指令。
    
    
    
          A HLT
          BRET
          CIRET
          D POP
    
    26【单选题】
    下列指令中,有语法错误的是(D )。
    
    
          AMOV [SI],[DI]
          B IN AL,DX  
          C JMP WORDPTR[BX+8]
          D  PUSH AX
    
    27【单选题】
    若已知(SP)=2000H,(AX)=0020H,则执行指令,PUSH AX 后,(SP)和((SS):(SP))的值分别为(D )。
    
    
          A2002H,00H
          B 2000H,20H
          C 1FFFH,00H
          D1FFEH,20H
    
    
    
    28【单选题】
    汇编语言源程序结束伪指令是(A )。
    
          A END
          B RET
          C IRET
          D  ENDM
    
    29【单选题】
    某CPU有地址线20根,它可连接内存的最大存储容量是( C)。
    
      
    
    
          A 64K
          B640K
          C1M
          D4M
    
    30【单选题】
    没有外部存贮器的计算机,其监控程序可以存放在(B )。
    
    
          A RAM
          BROM
          C CPU
          D RAM和ROM
    
    31【单选题】
    用16M×1的DRAM芯片组成128MB×8存储容量,要使用( B)片。
    
          A128
          B  64
          C  32
          D 16
    
    32【单选题】
    27128的存储容量为16K×8,其片内地址线和数据线数分别为( C)。
    
      
    
          A  8,8
          B 8,14
          C14,8
          D 8,16
    
    33【单选题】
    下列几种半导体存储器中,哪一种需要刷新操作( B)?
    
    
          ASRAM
          BDRAM
          CEPROM
          DROM
    
    34【单选题】
    某SRAM芯片,其存储容量为512K×8位,该芯片的地址线和数据线数目为(D )。
    
          A8,8
          B16,8
          C18,8
          D 19,8
    
    35【单选题】
    中断与DMA(  D )。
    
        
          A程序中断可以完全代替DMA
          B程序中断就是DMA
          CDMA可以完全代替程序中断 
          D  二者各有使用范围和特点,不能互相代替
    
    
    
    36【单选题】
    8086系统中的中断向量表的作用是(D )。
    
          A存放中断服务程序
          B存放系统引导程序
    
           
          C存放中断响应标志              
          D存放中断服务程序的入口地址
    
    
    37【单选题】
    8259A内中断类型号的数据长度为(B )。
    
          A4位     
          B8位
          C16位
          D24位
    
    38【多选题】
    8086总线接口部件由( AB)组成。
    
          A段寄存器和指令指针
          B指令队列
          C地址形成逻辑
          D总线控制逻辑
    
    39【多选题】
    8086执行部件由( AB)组成。
    
          A通用寄存器和标志寄存器
          B段寄存器和标志寄存器
          C算术逻辑单元
          DEU控制系统
    
    40【多选题】
    程序开始和结束的命令有( ABD)。
    
          A NAME
          BTITLE
          CEND
          DAND
    
    41【多选题】
    变量和标号类型的改变可应用运算符( B  )、( C  )和( D  )。
    
          A PTR
          B JMP
          CTHIS
          D LABEL
    
    42【多选题】
    在8086CPU的标志寄存器中,属于状态标志位的是(ABC )。
    
      
    
          A CF
          B ZF
          C SF
          D DF
    
    43【多选题】
    存储器是计算机系统的记忆设备,它可以用来(AB )。
    
          A存放数据
          B 存放程序
          C存放指令
          D存放堆栈
    
    44【多选题】
    CPU与外设间数据传送的控制方式有( AD)。
    
        A)中断方式     B)程序控制方式     C)DMA方式     D)以上三种都是
    CPU与外设之间数据传送都是通过内存实现的。
    外围设备和内存之间的常用数据传送控制方式有四种
    (1)程序直接控制方式:就是由用户进程直接控制内存或CPU和外围设备之间的信息传送。这种方式控制者都是用户进程。
    (2)中断控制方式:被用来控制外围设备和内存与CPU之间的数据传送。这种方式要求CPU与设备(或控制器)之间有相应的中断请求线,而且在设备控制器的控制状态寄存器的相应的中断允许位。
    (3)DMA方式:又称直接存取方式。其基本思想是在外围设备和内存之间开辟直接的数据交换通道。
    (4)通道方式:与DMA方式相类似,也是一种以内存为中心,实现设备和内存直接交换数据控制方式。
          A中断方式
          B查询方式
          C无条件传送方式
          D DMA方式
    
    
    45【填空题】
    8086微处理器可分为_____运算器___________和_________控制器_______两个部分。
    
    
    46【填空题】
    通用寄存器中的数据寄存器包括___累加器_____、_基址寄存器_______、___计数器_____和___数据寄存器_____。
    
    47【填空题】
    8086系统总线包括____数据总线____、__地址总线______和___控制总线_____三种。
    
    48【填空题】
    8086微处理器包括_____运算器___、___控制器_____和____内部存储器____三部分。
    一个典型的8位微处理器的内部结构,一般由算数逻辑单元、寄存器组和指令处理单元组成
    运算器(ALU)和控制器(CU)两大部件
    主要包括:控制单元、存储单元和中断单元三大部分组成
    运算器ALU,控制器CU,内部存储器RA三部分组成!
    
    49【填空题】
    段寄存器包括___代码段寄存器_CS____、_堆栈段寄存器SS_______、_数据段寄存器DS______和__附加段寄存器ES______。
    
        
    
    50【填空题】
    8086段间转移寻址不仅要改变_______IP___寄存器的值,还要改变___CS_______寄存器的值。
    段间间接转移寻址:不仅要求改变IP中的指令偏移地址,还要改变CS中的段基值。
     
    51【填空题】
    8086的汇编码指令可分为__数据传送指令、算数操作指令、_逻辑运算和移位运算指令、___串操作_______、___控制转移指令_______和__处理器控制指令________6大类。
    
     
    52【填空题】
    汇编语言程序的语句有两类:__指令语句________和___指示性语句_______。
    
    
    53【填空题】
    汇编语言中段语句利用命令________和________定义。
    
    54【填空题】
    变量和标号一样都具有__________、____________和__________等三种属性。
    
    55【填空题】
    子程序段间返回指令,把SP所指的堆栈顶部的两个字的内容先弹回______,后弹回______。
    
    56【填空题】
    标识符包括__________、__________、__________和__________等。
    
    57【填空题】
    系统总线由___________、___________、___________三类传输线组成。
    
    58【填空题】
    微型计算机由_______________、_______________、_______________和系统总线组成。
    
    
    59【填空题】
    计算机的硬件结构通常由五大部分组成。即运算器,____________,__________,输入设备和输出设备组成。
    
    
    60【填空题】
    微处理器由_______________、_______________和少量寄存器组成。
    
    61【填空题】
    8086的内存单元3017H:010BH的物理地址为_______________。
    
    62【填空题】
     8086中,RESET的作用是:_______________。
    
    63【填空题】
    8086上电复位后,其内部(CS)=___________,(IP)=________。
    
    64【填空题】
    在用8086CPU组成的计算机系统中,当访问偶地址字节时,CPU和存储器通过____________数据线交换信息;访问奇地址字节时通过_____________数据线交换信息。
    
    65【填空题】
    8086 CPU对存储器的最大寻址空间为_______________;在独立编址时对接口的最大寻址空间是_______________。
    
    66【填空题】
    在8086系统中,堆栈是按___________方式工作的存储区域,操作地址由________和_______提供。
    
    67【填空题】
    8086/8088的基本总线周期由_______个时钟周期组成,若CPU主频为10MHz,则一个时钟周期的时间为_______________。
    
    
    68【填空题】
    在8086CPU的时序中,为满足慢速外围芯片的需要,CPU采样_____________信号,若未准备好,插入_____________时钟周期。
    
    
    69【填空题】
    条件转移指令转移的范围是______________。
    
    70【填空题】
    设当前的(SP)=0100H,执行PUSH AX指令后,(SP)=_______________H。
    
    71【填空题】
    若当前(SP)=6000H,CPU执行一条IRET指令后,(SP)=_______________H;而当CPU执行一条段内返回指令RET 6后,(SP)=_______________H。
    
    72【填空题】
    已知(BX)=2000H,(DI)=3000H,(SS)=4000H,(DS)=6000H,(SS)=5000H,66000H单元的内容为28H,则指令MOV AL,[BX+DI+1000H]的执行结果是_______________。
    
    73【填空题】
    若(AL)=95H,执行SAR  AL,1 后(AL)=________。
    
    74【填空题】
    MOV  AX,[BX][DI] 指令中源操作数的寻址方式为_______________。
    
    75【填空题】
    若(CS)=1000H,(DS)=2000H,(SS)=3000H ,(ES)=4000H,(SI)=1000H,(BP)=2000H,则指令MOV  AX,[BP]的功能是将____________单元的内容传送给AL,将__________单元的内容传送给AH(填写物理地址)。
    
    76【填空题】
    指令MOVDX,OFFSET BUFFER 的源操作数的寻址方式是:_______________。
    
    77【填空题】
    指令MOVAX,[DI-4]中源操作数的寻址方式是______________。
    
    78【填空题】
    累加器专用传送指令IN间接访问I/O端口,端口号地址范围为_______________。
    	
    
    79【填空题】
    标号和变量都是存贮单元的符号地址,但其内容不同,标号是_______________的符号地址,而变量是_______________的符号地址。
    
    80【填空题】
    在微机系统中用高位地址线产生存储器片选(CS)的方法有__________、_________、__________。
    
    
    81【填空题】
    掉电后信息丢失的存储器是_______________,掉电后信息不丢失的存储器是_______________。
    
    82【填空题】
    CPU和外设之间的数据传送方式有__________、__________、____________和___________。
    
    83【填空题】
    I/O接口的编址方式可分为_______________、_______________两种方式。
    
    84【填空题】
    主机与外设之间的数据传送控制方式通常有三种,它们是程序控制方式,DMA方式及_______________,其中________方式的数据传输率最高。
    
    85【填空题】
    中断类型码若为58H,它在中断矢量表中的矢量地址为_______________H,从该地址开始连续四个单元存放的是_______________。
    
    86【填空题】
    8086的外部中断分为________和__________两大类。
    
    87【填空题】
    A/D转换器的功能是将_______________转换成数字量信息,便于计算机运算、处理。
    
      
    88【判断题】
    8086通用寄存器可以随便当做一般数据寄存器来用。
    
    89【判断题】
    变址寄存器可以按字节进行操作。
    
    90【判断题】
    程序员不能对指令指针IP进行存取操作。
    
    91【判断题】
    8086微处理器取指令和执行指令可以重叠进行。
    
    92【判断题】
    8086的系统总线采用了分时复用技术。
    
    93【判断题】
    数据总线是单向总线。
    
    94【判断题】
    IP中存放的是物理地址。
    
    95【判断题】
    立即寻址直接给出操作数的地址。
    
    96【判断题】
    8086指令给出的地址是物理地址。
    
    97【判断题】
    8086可根据有效地址准确定位操作数地址。
    
    98【判断题】
    堆栈是先进后出。
    
    99【判断题】
    TYPE 变量名=1表示数据类型是字数据。
    
    100【判断题】
    ASSUME语句指明了程序中所用到的段寄存器与段名的对应关系,同时这些段寄存器被赋值。
    
    101【判断题】
    指示性语句SEGMENT与END成对出现。
    
    102【判断题】
    问号可单独作为标识符。
    
    103【判断题】
    在8086CPU中,对时钟周期、指令周期和总线周期的长短排序,应该是总线周期≥指令周期≥时钟周期( )。
    
    104【判断题】
    8086系统总线形成时,须要用ALE信号锁定地址信号(  )。
    
    105【判断题】
    在8086系统中,20位地址是在执行部件中形成的。( )
    
    
    106【判断题】
    8086 CPU的最小方式是为实现多处理器系统而设计的。 ( )
    
    107【判断题】
    两数相加,结果有偶数个“1”,则PF=1。( )
    
    108【判断题】
    当8086CPU复位后,(CS)=0000H,(IP)=0FFFFH,所以复位后系统从物理地址0FFFF0H开始执行程序。 ( )
    
    109【判断题】
    同一个物理地址可以由不同的段地址和偏移量组合得到。( )
    
    110【判断题】
    只读存储器ROM只有一种类型。( )
    
    111【判断题】
    动态RAM的一个重要问题是必须对它所存的信息定期进行刷新。( )
    
    112【判断题】
    8086的中断有优先级。( )
    
    113【判断题】
    除法出错可触发中断。( )
    
    
    114【判断题】
    所有中断处理(服务)过程的最后一条可执行指令必须是RET。
    
    
    115【简答题】
    微处理器、微型计算机和微型计算机系统的关系。
    
       
    116【简答题】
    8086微处理器程序执行一般步骤。
    
    117【简答题】
    8086中4个段寄存器的表示和功用。
    
    118【简答题】
    指令周期、总线周期和时钟周期。
    
    119【简答题】
    8086的寻址方式。
    	
    
    120【简答题】
    解释汇编语言程序中常量、标识符和表达式的含义。
    
    121【简答题】
    变量三种属性取出操作时所用到的分析运算符。
    
    122【简答题】
    微型计算机由那些基本功能部件组成?
    
    123【简答题】
    简述微处理器的基本功能。
    
    
    124【简答题】
    计算机系统中数据传送的控制方式。
    
    125【简答题】
    若在4002H段中有8个字节的数据为34H,45H,56H,67H,78H,89H,9AH,0ABH,假定它们在存储器中的物理地址为400A5H-400ACH,试求各存储单元的有效地址;若从存储器中读出这些数据,试问最少要几个总线周期?
    
    
    126【简答题】
    简述8086内部EU和BIU两大功能单元各自的功能和这样组织的意义。
    
    127【简答题】
    什么是寻址方式,写出五种与数据有关的寻址方式?
    
    
    128【简答题】
    子程序调用的操作过程包含哪几个步骤?
    
    
    129【简答题】
    如BUFFER为数据段中0032单元的符号地址其中存放的内容为2345H,试问以下两条指令有什么区别?指令执行完成后AX寄存器的内容是什么?
    (1)MOV AX,BUFFER      (2)LEA AX,BUFFER
    
    
    130【简答题】
    简述8086汇编语言中,指令语句和伪指令语句的区别和作用。
    
    131【简答题】
    什么是变量,变量有哪些属性,如何使用这些属性?
    
    132【简答题】
    简述DOS系统功能调用的使用方法?
     
    133【简答题】
    什么是RAM?什么是ROM、EPROM?各有什么用途?
    
    
    134【简答题】
    设有一个具有14位地址和8位字长的存储器,问:
        (1)该存储器能存储多少字节的信息
        (2)如果存储器由1K*1位SRAM芯片组成,需要多少芯片?
        (3)最少需要多少位地址作芯片选择
    
    
    135【简答题】
    常用的存储器片选控制方法有哪几种?它们各有什么优缺点?
    
    
    136【简答题】
    设某系统的CPU有16根地址线A15-A0、8根数据线D7-D0。现需扩展6KB的ROM,地址范围为:0000H-17FFH,采用2716芯片。
    (1)请写出存储器器件2716的数据线和地址线的条数;
    (2)计算ROM的芯片数量;
    
    137【简答题】
    如果利用中断方式传送数据,数据是如何传输的?中断机构起什么作用?
    
    
    138【简答题】
    8086地址总线和数据总线分时复用是什么意思?
    
    
    139【简答题】
    存储器的容量通常用N×M表示,其中N和M各表示什么?
    
    140【简答题】
    定时/计数器的工作原理是什么?
    
    141【简答题】
    段寄存器CS=1200H,指令指针寄存器IP=FF00H,此时,指令的物理地址为多少?指向这一物理地址的CS值和IP值是唯一的吗?
    
    142【简答题】
    I/O接口电路中应具有的电路单元。
    

    作者:Lina_ACM 发表于2016/12/15 18:06:48 原文链接
    阅读:74 评论:0 查看评论

    Retrofit和RxJava的结合使用

    $
    0
    0

    转载请标明出处:
    http://blog.csdn.net/hai_qing_xu_kong/article/details/53674161
    本文出自:【顾林海的博客】

    前言

    Retrofit,一个时尚的代名词,好像不知道Retrofit就不算Android开发工程师了,因此我也来时尚一把,写这篇文章旨在使广大开发者能根据这篇浅薄的文章来了解Retrofit,并将它用到我们的项目中去,当然Retrofit和RxJava结合起来用是非常酸爽的。文章开头会先去介绍Retrofit,并单独使用Retrofit,后面会将Retrofit和RxJava结合起来使用,最后会封装一个Retrofit和RxJava结合的请求框架。

    Retrofit介绍

    Retrofit出自Square公司,是一个类型安全的REST安卓客户端请求库。这个库为网络认证、API请求以及用OkHttp发送网络请求提供了强大的框架 ,当然OkHttp也是出自这家公司。


    在漫长的时间里,Retrofit经历了从1.x到2.1(最新版请参看这里“https://github.com/square/retrofit”),相比retrofit1.x来说,retrofit2.x更新了几个不错的功能点。



    实例一(单独使用Retrofit )

    该实例只会讲解单纯使用 Retrofit的用法,源码位于底下github地址,实例一位于demo1包下。

    使用 Retrofit 前我们需要做以下两件事:

    • 在app/build.gradle 中引入 Retrofit。
    • 在AndroidManifest中添加网络请求权限。

    在gradle中引入 Retrofit:

    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    

    在AndroidManifest中添加网络请求权限:

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

    下面正式我们的Retrofit使用之旅。



    步骤一:创建我们的请求API(Service)

    这里的请求API其实就是我们的向服务端请求的接口地址。Retrofit2.x在定义Service时,已经不区分同步和异步之分了。可以直接看下面的代码,建议大家边看文章边动手撸下代码,代码中的接口地址替换成你们公司或是自己的服务器的地址。在Retrofit中使用注解的方式来区分请求类型.比如@GET(“”)表示一个GET请求,括号中的内容为请求的地址.

    public interface APIService {
    
        @GET("getBrandBanner.php")
        Call<ResponseBody> getBanner(@Query("uid") String _uid, @Query("token") String _token);
    
        @GET("getHomePager.php")
        Call<ResponseBody> getHomePager();
    
        @FormUrlEncoded
        @POST("editUserInfo.php")
        Call<ResponseBody> postIP(@Field("name") String name, @Field("age") int age);
    }
    



    以上定义了两个请求方式,分别是Get和Post请求,其中Get请求分为无参和有参请求。至此接口地址已经创建完毕。

    步骤二:创建Retrofit实例

    在请求接口的API定义完毕后,就需要使用Retrofit Builder类,来指定Service的baseUrl(也就是域名)。

    在Activity中编写代码:

    private static final String API_URL = "http://n1.glh.la/apps/";
    
    
    Retrofit retrofit = new Retrofit.Builder().baseUrl(API_URL).build();
    APIService apiService = retrofit.create(APIService.class);
    Call<ResponseBody> responseBodyCall = apiService.getBanner("10915", "585234059ab68");
    



    创建完Retrofit实例后,通过该实例创建APIService,接着通过APIService中定义的方法来获取Call对象。前置工作已经准备完毕,剩下的就是进行请求。

    步骤三:请求(异步与同步)

    一、同步请求

    同步请求使用execute方法,但不能在UI线程中执行,否则会阻塞UI线程,引起NetwordOnMainThreadException异常。因此,这里使用handler+thread的方式来进行请求,在子线程中请求数据,并通过handler来刷新界面。

    handler:

    private static class MyHandler extends Handler {
    
        WeakReference<DemoActivity1> weakReference;
    
        public MyHandler(DemoActivity1 activity) {
            weakReference = new WeakReference<>(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
            DemoActivity1 activity = weakReference.get();
            if (activity != null) {
                String json = (String) msg.obj;
                activity.setData(json);
            }
            super.handleMessage(msg);
        }
    }


    Thead:

    private static class MyThread extends Thread {
        private MyHandler myHandler;
        Call<ResponseBody> bodyResponse;
    
        public MyThread(Call<ResponseBody> responseBodyCall, MyHandler handler) {
            bodyResponse = responseBodyCall;
            myHandler = handler;
        }
    
        @Override
        public void run() {
            super.run();
            try {
                Response<ResponseBody> body = bodyResponse.execute();
                Message message = new Message();
                message.obj = body.body().string();
                myHandler.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    使用:

    /**
     * 同步
     *
     * @param responseBodyCall
     */
    private void synSendRequest(Call<ResponseBody> responseBodyCall) {
        MyHandler myHandler = new MyHandler(this);
        MyThread myThread = new MyThread(responseBodyCall, myHandler);
        myThread.start();
    }
    
    private void setData(String json) {
        tv_show.setText(json);
    }


    同步请求的整体流程大致就这样了。



    二、异步请求

    相比同步请求方式,异步比较简单,因此建议使用异步请求方式,异步请求方式使用enqueue方法,并通过回调Callback 泛型接口的两个方法:

    /**
     * 异步
     *
     * @param responseBodyCall
     */
    private void asySendRequest(Call<ResponseBody> responseBodyCall) {
    
        responseBodyCall.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                try {
                    tv_show.setText(response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                tv_show.setText("error");
            }
        });
    
    }


    其它一些注意事项

    service 的模式变成Call的形式的原因是为了让正在进行的事务可以被取消。要做到这点,你只需调用call.cancel()。


    实例二(Retrofit 与 Gson)

    当然如果你想把json字符串解析成DAO(实体类对象),在Retrofit2.x中,Converter不再包含其中,因此需要我们把Gson Converter依赖添加进来,此实例源码在源文件中的demo2包下。

    添加Gson Converter:

    compile 'com.squareup.retrofit2:converter-gson:2.1.0'



    demo1下的程序进行修改如下:


    修改一:接口请求

    public interface APIService {
    
        @GET("getBrandBanner.php")
        Call<HttpResult> getBanner(@Query("uid") String _uid, @Query("token") String _token);
    
    }

    在定义接口请求时,我们传入了一个HttpResult类,它是我们从服务器返回的json串解析后的实体类。

    修改二:创建Retrofit实例

    Retrofit retrofit = new Retrofit.Builder().baseUrl(API_URL).addConverterFactory(GsonConverterFactory.create()).build();
    APIService apiService = retrofit.create(APIService.class);
    Call<HttpResult> responseBodyCall = apiService.getBanner("10915", "58524bb42c9ca");



    我们在通过Retrofit Builder类来构造Retrofit实例时插入了一个Converter(Gson Converter)。


    修改三:请求


    同步请求

    /**
     * 同步
     *
     * @param responseBodyCall
     */
    private void synSendRequest(Call<HttpResult> responseBodyCall) {
        MyHandler myHandler = new MyHandler(this);
        MyThread myThread = new MyThread(responseBodyCall, myHandler);
        myThread.start();
    }
    
    private void setData(String data) {
        tv_show.setText(data);
    }
    
    private static class MyHandler extends Handler {
    
        WeakReference<DemoActivity2> weakReference;
    
        public MyHandler(DemoActivity2 activity) {
            weakReference = new WeakReference<>(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
            DemoActivity2 activity = weakReference.get();
            if (activity != null) {
                HttpResult hotBean = (HttpResult) msg.obj;
                if (hotBean != null) {
                    activity.setData(hotBean.data.pc.number);
                }
            }
            super.handleMessage(msg);
        }
    }
    
    private static class MyThread extends Thread {
        private MyHandler myHandler;
        Call<HttpResult> bodyResponse;
    
        public MyThread(Call<HttpResult> responseBodyCall, MyHandler handler) {
            bodyResponse = responseBodyCall;
            myHandler = handler;
        }
    
        @Override
        public void run() {
            super.run();
            try {
                Response<HttpResult> body = bodyResponse.execute();
                Message message = new Message();
                message.obj = body.body();
                myHandler.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    异步请求

    /**
     * 异步
     *
     * @param responseBodyCall
     */
    private void asySendRequest(Call<HttpResult> responseBodyCall) {
    
        responseBodyCall.enqueue(new Callback<HttpResult>() {
            @Override
            public void onResponse(Call<HttpResult> call, Response<HttpResult> response) {
                HotBean hotBean = response.body().data;
                tv_show.setText(hotBean.pc.number);
            }
    
            @Override
            public void onFailure(Call<HttpResult> call, Throwable t) {
                tv_show.setText("error");
            }
        });
    
    }
    


    实例三(Retrofit与RxJava )

    经历了上面的两个例子,大家对Retrofit的使用已经有了充分的认识了吧,如果不满足于此,可以继续看下去,因为高潮马上来了,接下来会讲解Retrofit与RxJava的结合使用。


    RxJava介绍

    想来想去对RxJava介绍的文章,网上多的是,当然我认为这篇文章《给Android开发者的RxJava讲解》(“http://gank.io/post/560e15be2dca930e00da1083“)还是很不错,所以啊,我就不介绍了,哈哈哈。。。。。,强烈建议大家将RxJava讲解这篇文章看看,当然,不看也没问题,除非你只是拿来就用,否则作为一个有”节操”的程序员,还是老老实实的研究下。


    正题

    Retrofit2.x中有个机制,叫做CallAdapter,而Retrofit团队已经提供了RxJava的CallAdapter,这时就需要在app/build.gradle中引入以下依赖:

    compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
    compile 'io.reactivex:rxjava:1.1.0'
    compile 'io.reactivex:rxandroid:1.1.0'

    在之前例子中,为了使用同步请求方式,需要自己创建线程,过程比较繁琐,所以只能使用异步方式,基于此,使用RxJava来解决异步的问题,代码实例在demo3包下。

    步骤一:请求接口

    在创建请求接口时,我们就可以将Service作为Observable返回:

    public interface APIService {
    
        @GET("getBrandBanner.php")
        Observable<HttpResult> getBanner(@Query("uid") String _uid, @Query("token") String _token);
    
    }


    步骤二:创建Retrofit实例

    //step1
    Retrofit retrofit = new Retrofit.Builder().baseUrl(API_URL).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();
    APIService apiService = retrofit.create(APIService.class);
    Observable<HttpResult> httpResultObservable = apiService.getBanner("10915", "58524bb42c9ca");
    



    使用CallAdapter这种机制,可以在 Retrofit Builder 链中调用addCallAdapterFactory方法。

    步骤三:请求

    httpResultObservable.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<HttpResult>() {
        @Override
        public void onCompleted() {
            tv_show.append("请求结束");
        }
    
        @Override
        public void onError(Throwable e) {
            tv_show.append("请求错误");
        }
    
        @Override
        public void onNext(HttpResult httpResult) {
            tv_show.append(httpResult.data.pc.number);
        }
    
        @Override
        public void onStart() {
            tv_show.append("请求开始");
        }
    });
    



    上面指定subscribe发生在IO线程中,而指定的Subscriber的回调发生在Android的UI线程中。

    呼~~~~,终于把上面的例子写完了,天已经黑了,看看还有什么要讲的,好吧,每次这样请求无意间代码量就增多了,而我们理想中的请求方式应该是这样的,在Activity中是这样的请求的:

    private void getNews() {
        MainHttpRequest.getInstance().getBanner(new ListenerSubscriber<ArrayList<NewsBean>>(getNewsListener, DemoActivity4.this));
    }



    接着通过回调获取我们想要的数据:

    private OnFunctionListener getNewsListener = new OnFunctionListener<ArrayList<NewsBean>>() {
        @Override
        public void success(ArrayList<NewsBean> o) {
            tv_show.setText(o.get(0).lname);
        }
    
        @Override
        public void fail(String message) {
    
        }
    
    };



    并且服务端返回的信息,我们应该是抽取其中有用的信息,而code 、message、success、client等等信息不是我们应该关心的,就拿下面的json串来说:

    {
        "code":"200",
        "message":"数据返回成功",
        "success":true,
        "data":[
            {
                "id":"1",
                "lname":"香水合集 | 该换上适合秋天的味道啦~"
            },
            {
                "id":"3",
                "lname":"IOPE水滢多效气垫腮红"
            },
            {
                "id":"0",
                "lname":"单品小记∣毛孔收收收?痘痘消消消?"
            },
            {
                "id":"4",
                "lname":"雅诗兰黛肌透修护精萃蜜"
            }
        ]
    }
    



    我们应该是关心data节点下的json串,因此,不管是为了省代码量还是获取数据方便,我们都有必要对这些进行一定量的封装。


    实例四:封装

    还是拿上面的json串来说事,在这json串中我们业务层应该是只关心data节点下的数据,因此定义一个HttpResult类:

    public class HttpResult<T> {
    
        private int code;
        private String message;
        private boolean success;
    
        private T data;
    
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public boolean isSuccess() {
            return success;
        }
    
        public void setSuccess(boolean success) {
            this.success = success;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    
    



    接着自定义一个Func1的类,用于变换时获取data部分的数据:

    /**
     * 用来统一处理Http的resultCode,并将HttpResult的Data部分剥离出来返回给subscriber
     *
     * @param <T> Subscriber真正需要的数据类型,也就是Data部分的数据类型
     */
    public class HttpResultFunc<T> implements Func1<HttpResult<T>, T> {
    
        @Override
        public T call(HttpResult<T> httpResult) {
            if (httpResult.isSuccess()) {
                Log.e("TAG", "response error");
            }
            return httpResult.getData();
        }
    }



    自定义一个Converter类用于Gson解析:

    public class ResponseConvertFactory extends Converter.Factory {
    
        private final Gson gson;
    
        public static ResponseConvertFactory create() {
            return create(new Gson());
        }
    
        public static ResponseConvertFactory create(Gson gson) {
            return new ResponseConvertFactory(gson);
        }
    
    
        private ResponseConvertFactory(Gson gson) {
            if (gson == null) {
                throw new NullPointerException("gson == null");
            }
            this.gson = gson;
        }
    
        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                                Retrofit retrofit) {
            return new GsonResponseBodyConverter<>(gson, type);
        }
    
    }


    class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
        private final Gson gson;
        private final Type type;
    
        GsonResponseBodyConverter(Gson gson, Type type) {
            this.gson = gson;
            this.type = type;
        }
    
        @Override
        public T convert(ResponseBody value) throws IOException {
            String response = value.string();
            HttpResult httpResult = gson.fromJson(response, HttpResult.class);
            if (!httpResult.isSuccess()) {
                Log.e("TAG", "request error");
            }
            return gson.fromJson(response, type);
        }
    }



    这样定义后,我们可以在底层根据返回数据的一些参数来判别一些错误类型,方便处理。

    创建一个单例的Http类:

    public class Http {
    
        public static final String BASE_URL = "http://n1.glh.la/apps_T1/";
    
        private static final int DEFAULT_TIMEOUT = 5;
    
        private Retrofit retrofit;
    
    
        //构造方法私有
        private Http() {
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
            retrofit = new Retrofit.Builder()
                    .client(builder.build())
                    .addConverterFactory(ResponseConvertFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .baseUrl(BASE_URL)
                    .build();
    
        }
    
        private static class SingletonHolder {
            private static final Http INSTANCE = new Http();
        }
    
        public Retrofit getRetrofit() {
            return retrofit;
        }
    
        public static Http getInstance() {
            return SingletonHolder.INSTANCE;
        }
    
    
        public <T> void getData(Observable<T> o, Subscriber<T> s) {
            o.subscribeOn(Schedulers.io())
                    .unsubscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(s);
        }
    
    }



    最后自定义一个Subscriber类,用于请求监听,比如在这里显示一个加载框或者是对一些错误码的处理:

    public class ListenerSubscriber<T> extends Subscriber<T> {
    
        private OnFunctionListener mSubscriberOnNextListener;
        private Context context;
    
    
        public ListenerSubscriber(OnFunctionListener mSubscriberOnNextListener, Context context) {
            this.mSubscriberOnNextListener = mSubscriberOnNextListener;
            this.context = context;
        }
    
    
        @Override
        public void onStart() {
        }
    
        @Override
        public void onCompleted() {
        }
    
        @Override
        public void onError(Throwable e) {
            if (mSubscriberOnNextListener != null) {
                mSubscriberOnNextListener.fail("error");
            }
        }
    
        /**
         * 将onNext方法中的返回结果交给Activity或Fragment自己处理
         *
         * @param t 创建Subscriber时的泛型类型
         */
        @Override
        public void onNext(T t) {
            if (mSubscriberOnNextListener != null) {
                mSubscriberOnNextListener.success(t);
            }
        }
    
    }


    public interface OnFunctionListener<T> {
        void success(T t);
        void fail(String message);
    }



    整体封装完毕后,再在业务层将上面请求的方式再次进行封装一遍:

    /**
     * 首页相关请求
     * Created by glh on 2016-12-15.
     */
    public interface HomeCarouselService {
        @GET("getHomeCarousel.php")
        Observable<HttpResult<ArrayList<NewsBean>>> getNews();
    }
    /**
     * 首页相关的网络请求
     * Created by glh on 2016-12-14.
     */
    public class MainHttpRequest {
    
        private HomeCarouselService mHomeCarouselService;
        private Http mHttpMethods;
    
        private MainHttpRequest() {
            mHttpMethods = Http.getInstance();
            mHomeCarouselService = mHttpMethods.getRetrofit().create(HomeCarouselService.class);
        }
    
        private static class SingletonHolder {
            private static final MainHttpRequest INSTANCE = new MainHttpRequest();
        }
    
        //获取单例
        public static MainHttpRequest getInstance() {
            return SingletonHolder.INSTANCE;
        }
    
        /**
         * 获取广告
         *
         * @param subscriber 由调用者传过来的观察者对象
         */
        public void getBanner(Subscriber<ArrayList<NewsBean>> subscriber) {
            Observable observable = mHomeCarouselService.getNews()
                    .map(new HttpResultFunc<ArrayList<NewsBean>>());
            mHttpMethods.getData(observable, subscriber);
        }
    
    }
    


    创建实体类:

    public class NewsBean {
        public String id;
        public String lname;
        public String tid;
        public String imgurl;
        public String desc;
        public String ptype;
        public String url;
    }
    

    完整源码在源文件的demo4中。



    项目下载地址


    以下是完整的github项目地址,欢迎多多star和fork。
    github项目源码地址:点击【项目源码】

    作者:GULINHAI12 发表于2016/12/15 18:43:01 原文链接
    阅读:80 评论:0 查看评论
    Viewing all 35570 articles
    Browse latest View live


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