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

iOS Runtime Method IMP指针详解

$
0
0

Method IMP 概念介绍 


OC是消息转发机制,代码在编译的时候会生产Runtime中间代码,运行的时候执行Runtime代码,我们也可以动态的添加Runtime代码。
这篇之前讲过了如何创建类和Runtime中的属性,今天主要说一下关于Runtime的方法。

首先还要说一下Runtime类的结构体:

struct objc_class {

    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;


#if !__OBJC2__

    Class _Nullable super_class                              OBJC2_UNAVAILABLE;

    const char * _Nonnull name                               OBJC2_UNAVAILABLE;

    long version                                             OBJC2_UNAVAILABLE;

    long info                                                OBJC2_UNAVAILABLE;

    long instance_size                                       OBJC2_UNAVAILABLE;

    struct objc_ivar_list *_Nullable ivars                  OBJC2_UNAVAILABLE;

    struct objc_method_list *_Nullable *_Nullable methodLists                    OBJC2_UNAVAILABLE;

    struct objc_cache *_Nonnull cache                       OBJC2_UNAVAILABLE;

    struct objc_protocol_list *_Nullable protocols          OBJC2_UNAVAILABLE;

#endif


} OBJC2_UNAVAILABLE;



然后我们可以看到:


    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;




继续分析下 objc_method_list:

struct objc_method_list {

    struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;


    int method_count                                         OBJC2_UNAVAILABLE;

#ifdef __LP64__

    int space                                                OBJC2_UNAVAILABLE;

#endif

    /* variable length structure */

    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;

}          


我们可以从上面的结构体上面看出 objc_method_list是 objc_method 结构体。


那么继续找 objc_method结构体 看看是否有IMP

struct objc_method {

    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;

    char * _Nullable method_types                            OBJC2_UNAVAILABLE;

    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;

}      



分析:

看了根据上面的关系网,我们知道在OC的类中,所有的方法在Runtime的时候都会变成objc_method,IMP是一个函数指针,指向实现这个函数的方法。然后这些方法都被放到objc_method_list这个结构体里。


Runtime 关于Method IMP 的API介绍


/* Working with Methods */


/** 

 * Returns the name of a method.

 * 

 * @param m The method to inspect.

 * 

 * @return A pointer of type SEL.

 * 

 * @note To get the method name as a C string, call \c sel_getName(method_getName(method)).

 */

OBJC_EXPORT SEL_Nonnull

method_getName(Method _Nonnull m) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns the implementation of a method.

 * 

 * @param m The method to inspect.

 * 

 * @return A function pointer of type IMP.

 */

OBJC_EXPORT IMP_Nonnull

method_getImplementation(Method _Nonnull m) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns a string describing a method's parameter and return types.

 * 

 * @param m The method to inspect.

 * 

 * @return A C string. The string may be \c NULL.

 */

OBJC_EXPORT const char * _Nullable

method_getTypeEncoding(Method _Nonnull m) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns the number of arguments accepted by a method.

 * 

 * @param m A pointer to a \c Method data structure. Pass the method in question.

 * 

 * @return An integer containing the number of arguments accepted by the given method.

 */

OBJC_EXPORT unsignedint

method_getNumberOfArguments(Method _Nonnull m)

    OBJC_AVAILABLE(10.0,2.0,9.0,1.0,2.0);


/** 

 * Returns a string describing a method's return type.

 * 

 * @param m The method to inspect.

 * 

 * @return A C string describing the return type. You must free the string with \c free().

 */

OBJC_EXPORT char * _Nonnull

method_copyReturnType(Method _Nonnull m) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns a string describing a single parameter type of a method.

 * 

 * @param m The method to inspect.

 * @param index The index of the parameter to inspect.

 * 

 * @return A C string describing the type of the parameter at index \e index, or \c NULL

 *  if method has no parameter index \e index. You must free the string with \c free().

 */

OBJC_EXPORT char * _Nullable

method_copyArgumentType(Method _Nonnull m, unsigned int index) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns by reference a string describing a method's return type.

 * 

 * @param m The method you want to inquire about. 

 * @param dst The reference string to store the description.

 * @param dst_len The maximum number of characters that can be stored in \e dst.

 *

 * @note The method's return type string is copied to \e dst.

 *  \e dst is filled as if \c strncpy(dst, parameter_type, dst_len) were called.

 */

OBJC_EXPORT void

method_getReturnType(Method _Nonnull m, char * _Nonnull dst, size_t dst_len) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns by reference a string describing a single parameter type of a method.

 * 

 * @param m The method you want to inquire about. 

 * @param index The index of the parameter you want to inquire about.

 * @param dst The reference string to store the description.

 * @param dst_len The maximum number of characters that can be stored in \e dst.

 * 

 * @note The parameter type string is copied to \e dst. \e dst is filled as if \c strncpy(dst, parameter_type, dst_len) 

 *  were called. If the method contains no parameter with that index, \e dst is filled as

 *  if \c strncpy(dst, "", dst_len) were called.

 */

OBJC_EXPORT void

method_getArgumentType(Method _Nonnull m, unsigned int index, 

                       char * _Nullable dst, size_t dst_len) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


OBJC_EXPORT struct objc_method_description *_Nonnull

method_getDescription(Method _Nonnull m) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Sets the implementation of a method.

 * 

 * @param m The method for which to set an implementation.

 * @param imp The implemention to set to this method.

 * 

 * @return The previous implementation of the method.

 */

OBJC_EXPORT IMP_Nonnull

method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Exchanges the implementations of two methods.

 * 

 * @param m1 Method to exchange with second method.

 * @param m2 Method to exchange with first method.

 * 

 * @note This is an atomic version of the following:

 *  \code 

 *  IMP imp1 = method_getImplementation(m1);

 *  IMP imp2 = method_getImplementation(m2);

 *  method_setImplementation(m1, imp2);

 *  method_setImplementation(m2, imp1);

 *  \endcode

 */

OBJC_EXPORT void

method_exchangeImplementations(Method _Null_unspecified /* _Nonnull */ m1, Method _Null_unspecified /* _Nonnull */ m2) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);



Runtime 关于Method IMP 的应用示例


Demo1:
     获取方法名字和获取方法的IMP 。 从objc_method结构体可知,method里有SEL 方法名 和IMP 实现方法的函数指针组成。所以我们可以从方法里拿到他的属性,然后玩一玩,下面就是针对这两个方法的小应用。

通过下面示例我们可以明白:Method SEL IMP 外在联系

method_getName(Method _Nonnull m) 

method_getImplementation(Method _Nonnull m) 


- (void)viewDidLoad {
    [super viewDidLoad];
//首先我们早类里面找到该方法
    Method ori_Method = class_getInstanceMethod([self class], @selector(myMethod:));
#if 0
    //获取方法名 SEL
    SEL oriMethodName = method_getName(ori_Method);
    //根据方法名获取函数指针
    IMP myMethodImp  =  [self methodForSelector:oriMethodName];
#else
    //直接根据方法名获取函数指针  等同于IF 0
    IMP myMethodImp  =  method_getImplementation(ori_Method);
#endif
   
 //在该类中添加方法。
#if 0
    class_addMethod([self class], @selector(testMethod:), myMethodImp, method_getTypeEncoding(ori_Method));
#else
    class_addMethod([self class], @selector(testMethod:), myMethodImp, "V@:@");
#endif
    //在执行方法。
    [self performSelector:@selector(testMethod:) withObject:@"7777"];
}

-(void)myMethod:(NSString *)myValue{
    NSLog(@"myMethod:%@",myValue);
}

打印结果:

2017-07-20 10:52:26.581146+0800 zyTest[8351:416437] myMethod:7777



Demo2:


中间关于获得方法参数,参数个数,参数返回类型就不赘述了,示例中有的会用到。主要介绍下面几个方法:


上面是GET IMP,那肯定可以设置方法的IMP

method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) 

方法交换

method_exchangeImplementations(Method _Null_unspecified /* _Nonnull */ m1, Method _Null_unspecified /* _Nonnull */ m2) 


//方法交换
-(void)demo2{
    Method method1 = class_getInstanceMethod([self class], @selector(exchangeMethod1:));
    Method method2 = class_getInstanceMethod([self class], @selector(exchangeMethod2:));
    Method method3 = class_getInstanceMethod([self class], @selector(exchangeMethod3:));
    Method method4 = class_getInstanceMethod([self class], @selector(exchangeMethod4:));
    //方法替换   替换SEL 的IMP实现
    class_replaceMethod([self class], @selector(exchangeMethod1:), method_getImplementation(method3), method_getTypeEncoding(method3));
    //和class_replaceMethod 类似,替换method 的结构题IMP指针
    method_setImplementation(method4, method_getImplementation(method2));
    //方法交换
    method_exchangeImplementations(method1, method2);
    //猜一猜打印的什么
    [self performSelector:method_getName(method1) withObject:@"Runtime Method Demo1" afterDelay:0.0];
    [self exchangeMethod2:@"Runtime Method Demo2"];
    [self exchangeMethod3:@"Runtime Method Demo3"];
    [self exchangeMethod4:@"Runtime Method Demo4"];
}

-(void)exchangeMethod1:(id)str{
    NSLog(@"exchangeMethod1:%@",str);
}

-(void)exchangeMethod2:(id)num{
    NSLog(@"exchangeMethod2:%@",num);
}

-(void)exchangeMethod3:(id)f{
    NSLog(@"exchangeMethod3:%@",f);
}

-(void)exchangeMethod4:(id)w{
    NSLog(@"exchangeMethod4:%@",w);
}


最后的打印结果是你想的吗?

2017-07-20 13:23:14.804342+0800 zyTest[16764:910790] exchangeMethod3:Runtime Method Demo2

2017-07-20 13:23:14.804586+0800 zyTest[16764:910790] exchangeMethod3:Runtime Method Demo3

2017-07-20 13:23:14.804738+0800 zyTest[16764:910790] exchangeMethod2:Runtime Method Demo4

2017-07-20 13:23:14.817341+0800 zyTest[16764:910790] exchangeMethod2:Runtime Method Demo1



Runtime  IMP 块操作


参考地址:http://www.cocoachina.com/ios/20141111/10186.html

块操作

我们都知道block给我们带到极大的方便,苹果也不断提供一些使用block的新的API。同时,苹果在runtime中也提供了一些函数来支持针对block的操作,这些函数包括:

1
2
3
4
5
6
7
8
// 创建一个指针函数的指针,该函数调用时会调用特定的block
IMP imp_implementationWithBlock ( id block );
 
// 返回与IMP(使用imp_implementationWithBlock创建的)相关的block
id imp_getBlock ( IMP anImp );
 
// 解除block与IMP(使用imp_implementationWithBlock创建的)的关联关系,并释放block的拷贝
BOOL imp_removeBlock ( IMP anImp );

● imp_implementationWithBlock函数:参数block的签名必须是method_return_type ^(id self, method_args …)形式的。该方法能让我们使用block作为IMP。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface MyRuntimeBlock : NSObject
 
@end
 
@implementation MyRuntimeBlock
 
@end
 
// 测试代码
IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) {
    NSLog(@"%@", str);
});
 
class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp, "v@:@");
 
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector(testBlock:) withObject:@"hello world!"];

输出结果是

1
2014-11-09 14:03:19.779 [1172:395446] hello world!


作者:ZY_FlyWay 发表于2017/7/20 13:31:03 原文链接
阅读:191 评论:0 查看评论

给ROM包内置ROOT权限,刷机以后立马拥有ROOT权限

$
0
0
基本上的MI2机油都刷了第三方的RECOVERY(如秋大的,C大的)实现了双系统共存,一个系统是MIUI,另一个系统刷上如锤子,COLOR OS,乐蛙等。

       由于官方的ROM都是不带ROOT权限的,而你不想要论坛大神发布的精简优化版再加ROOT的ROM,只是想给第三方的ROM添加ROOT权限,但是论坛上的办法都是刷进去系统,再用一键ROOT,ROOT精灵等工具进行ROM,没有给ROM包内置ROOT权限的教程,所以小生今天特开一帖,简单的分享一下给ROM内置ROOT权限的方法,内置ROOT权限的ROM包直接刷入就是ROOT,免去了再用工具ROOT的麻烦、、、

闲言碎语不多讲,进入正题

需要的东西:第三方ROM官方包(这里以最新的锤子ROM为例)、
ROOT文件(最后会提供附件下载

打开ROM压缩包,进入META-INF/com/google/android,里面有两个文件,我们需要改动的是updater-script


把updater-script文件拉到桌面,用WORD打开,添加一串代码(图中红色字体部分),或者直接使用附件中已添加好的


添加完成后,替换ROM包内的文件(直接拉进去替换即可)

然后回到压缩包根目录,进入system文件夹,把ROOT权限文件夹(下图)中的Superuser软件放到app文件夹中,su文件在bin文件夹和xbin文件夹下各放一个


至此,ROM包内置ROOT权限完成,刷入系统即带ROOT权限,省去再用工具ROOT的麻烦

下附件载:由于COLOR OS ROOT需要旧版的su,所以提供两个附件,请按照自己需要下载!
作者:chaoyu168 发表于2017/7/20 14:41:44 原文链接
阅读:218 评论:0 查看评论

Android 架构设计 本科《毕业论文》

$
0
0

  不知不觉学生时代已经结束,由于在 Android 上面花了不少功夫,所以这次毕业论文也是对我 Android 学习的一次总结,基本上都是自己写的。有关 Android 架构设计的一些想法,当然也有一些主流 Android 框架的源码解析(比如 retrofit,EventBus 等)可以下来看一看。

文档下载地址:https://github.com/ganyao114/Android_Architecture

目录:

第一章绪论..............................................................................................1

1.1 研究背景.....................................................................................................................1

1.2 Android 架构的现状和发展.......................................................................................1

1.2.1 Android 架构的初期状态.....................................................................................1

1.2.2 Android 4.0 之后,Fragment的引入.................................................................1

1.2.3 MVP &MVVM.....................................................................................................2

1.2.4 事件驱动编程.......................................................................................................2

1.2.5 RxJava 带来的革命..............................................................................................2

第二章架构需求分析.............................................................................3

2.1 需求目标.....................................................................................................................3

2.1.1 目标产品...............................................................................................................3

2.1.2 目标用户(开发者)................................................................................................3

2.2 开发需求.....................................................................................................................3

2.2.1 开发期的功能需求...............................................................................................3

2.2.2 开发期的质量需求...............................................................................................4

2.3 用户级需求.................................................................................................................4

2.3.1 用户级功能需求...................................................................................................4

2.3.2 运行期质量需求...................................................................................................5

2.4 需求总结.....................................................................................................................5

第三章架构整体设计.............................................................................6

3.1 架构逻辑设计.............................................................................................................6

3.1.1 架构图...................................................................................................................6

3.1.2 架构结构划分.......................................................................................................6

3.1.3 架构特点...............................................................................................................7

3.1.4 架构核心 Common..............................................................................................7

3.2 项目开发期间架构设计.............................................................................................9

3.2.1 工程结构...............................................................................................................9

3.2.2 使用本架构构建 App..........................................................................................9

3.2.3 编译期间依赖关系.............................................................................................11

3.2.4 模块隔离开发.....................................................................................................11

3.3 运行期间架构...........................................................................................................12

3.3.1 运行期间的模块加载升级.................................................................................12

3.3.2 运行期组件依赖.................................................................................................12

3.3.3 页面导航和业务服务导航.................................................................................12

第四章分层架构....................................................................................13

4.1 分层概述...................................................................................................................13

4.2 MVC 分层架构.........................................................................................................13

4.2.1 MVC 简介...........................................................................................................13

4.2.2 Model 模型层.....................................................................................................14

4.2.3 Controller 控制器...............................................................................................15

4.2.4 View 层...............................................................................................................15

4.2.5 MVC 所存在的问题...........................................................................................15

4.3 MVVM 分层架构.....................................................................................................17

4.3.1 MVVM 简介.......................................................................................................17

4.3.2 MVVM 各层.......................................................................................................17

4.3.3 DataBinding 原理简介.......................................................................................18

4.3.4 MVVM 缺点.......................................................................................................19

4.4 MVP 分层架构..........................................................................................................19

4.4.1 MVP 简介...........................................................................................................19

4.4.2 Model 层设计.....................................................................................................20

4.4.3 View 层设计.......................................................................................................20

4.4.3 Presenter 层设计.................................................................................................21

4.4.4 MVP 的统筹 协议层设计.................................................................................21

4.4.5 MVP 的优缺点...................................................................................................22

4.4.6 MVP 与 MVC的不同。..................................................................................22

第五章组件化........................................................................................24

5.1 组件化介绍...............................................................................................................24

5.2 本架构中组件的实现...............................................................................................24

5.3 Component 的依赖....................................................................................................25

5.4 Component 的生命周期............................................................................................25

5.5 MVPComponent........................................................................................................26

5.6 API Component..........................................................................................................27

5.7AppComponent...........................................................................................................28

5.7.1 AppComponent 含义..........................................................................................28

5.7.2 各个基础组件.....................................................................................................29

5.8 组件管理器 ComponentManager............................................................................31

第六章 DI 依赖注入.............................................................................33

6.1 依赖注入定义...........................................................................................................33

6.2 依赖注入意义...........................................................................................................33

6.3 Android 中的依赖注入 Dagger2.............................................................................34

6.3.1 简介.....................................................................................................................34

6.3.2 Dagger2 中的重要概念......................................................................................34

6.4 本架构中的依赖注入...............................................................................................35

6.4.1 MVP 三层依赖组装...........................................................................................35

6.4.2 App 全局组件依赖.............................................................................................36

6.4.3 业务组件依赖拼装.............................................................................................37

第七章 AOP 面向切面编程.................................................................40

7.1 AOP 的定义...............................................................................................................40

7.2 在 Android 上使用 Aspectj实现 AOP................................................................40

7.3 动态织入实现原理...................................................................................................41

7.4 AOP 在本架构中的使用...........................................................................................42

7.4.1 动态权限检测.....................................................................................................42

7.4.2 登陆检测.............................................................................................................43

7.4.3 性能检测 & Log................................................................................................43

7.4.4 异步与主线程.....................................................................................................44

第八章 Bundle 模块化容器化.............................................................46

8.1 定义...........................................................................................................................46

8.2 意义...........................................................................................................................46

8.3 Atlas &Small..............................................................................................................46

8.3.1 简介.....................................................................................................................46

8.3.2 原理.....................................................................................................................47

8.4 本架构中的模块化...................................................................................................48

第九章 Router 页面路由.....................................................................50

9.1 背景...........................................................................................................................50

9.2 Intent 导航遇到的问题.............................................................................................50

9.3 意义...........................................................................................................................50

9.4 页面路由的使用.......................................................................................................51

9.5 页面路由的原理及实现...........................................................................................52

9.5.1 页面发现.............................................................................................................52

9.5.2 页面分发.............................................................................................................53

第十章事件驱动....................................................................................54

10.1 事件驱动定义.........................................................................................................54

10.2 事件驱动意义.........................................................................................................54

10.3 事件驱动框架原理.................................................................................................54

10.4 事件框架的实现 EventPoster................................................................................56

10.4.1 EventPoster 优势简述......................................................................................56

10.4.2 模块 Handler 以及分发..................................................................................57

10.4.3 性能优化...........................................................................................................57

10.4.4 防止 Leak.........................................................................................................58

第十一章框架原理及实践...................................................................59

11.1 自己动手实现 JSON ORM 框架..........................................................................59

11.2 图像异步加载框架原理及实现.............................................................................63

11.2.1Cache..................................................................................................................63

11.2.2 线程池...............................................................................................................64

11.2.3 图片压缩...........................................................................................................64

11.2.4 其他优化...........................................................................................................64

11.3 Http 请求框架 Retrofit...........................................................................................65

11.3.1 原理...................................................................................................................65

11.3.2 源码分析...........................................................................................................65

第十二章案例实践...............................................................................68

12.1 案例简述-数读........................................................................................................68

12.2 需求简述-数读........................................................................................................68

12.3 重构之前的程序结构-数读....................................................................................68

12.4 重构后的程序结构-数读........................................................................................69

12.4.1 架构裁剪-数读.................................................................................................69

12.4.2 UML 类图-数读................................................................................................70

12.4.3 主要功能需求-数读.........................................................................................70

12.4.4 重构带来的改变...............................................................................................73

结 论....................................................................................................75

致谢........................................................................................................76

参考文献............................................................................................77

作者:ganyao939543405 发表于2017/7/20 14:56:54 原文链接
阅读:147 评论:0 查看评论

OpenGL实现相机视频NV21格式转RGB格式

$
0
0

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

CSDN视频网址:http://edu.csdn.net/lecturer/144

最近公司项目做人脸识别追踪,从相机出来的视频格式是YUV420或者说是NV21格式,在面板上显示出来的使用的是RGB格式,所以要将其转化成该格式,下面先了解一下YUV420格式,网上很多这方面的介绍,理解起来很麻烦,本篇博客通过使用简介的语言希望能够帮助读者更好的理解:

视频数据也是一张张图片组成的,每张图片的大小是由图片的(width * height)*3/2字节组成。图片分两部分:Y通道的长度是width * height。UV平面字节长度是:(width / 2) x (height / 2) x 2 = width x height / 2 。每两个连续的字节是2 x 2 = 4个原始像素的V,U(按照NV21规范的顺序)色度字节。换句话说,UV平面尺寸为(宽/ 2)×(高/ 2)像素,并且在每个维度中被下采样因子2, 此外,U,V色度字节是交错的。

下面给读者展示一副关于YUV-NV12, NV21存储的图片:


接下来介绍如何将其转化成RGB

如问题所述,如果在Android代码中完成,此转换将需要太多时间才能生效。 幸运的是,它可以在GPU上运行的GL着色器中完成。 这将允许它运行非常快。

一般的想法是将我们的图像的纹理作为纹理传递给着色器,并以RGB转换的方式渲染它们。 为此,我们必须首先将图像中的通道复制到可传递给纹理的缓冲区中:

byte[] image;
ByteBuffer yBuffer, uvBuffer;

...

yBuffer.put(image, 0, width*height);
yBuffer.position(0);

uvBuffer.put(image, width*height, width*height/2);
uvBuffer.position(0);
然后,我们将这些缓冲区传递给实际的GL纹理:

/*
 * Prepare the Y channel texture
 */

//Set texture slot 0 as active and bind our texture object to it
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0);
yTexture.bind();

//Y texture is (width*height) in size and each pixel is one byte; 
//by setting GL_LUMINANCE, OpenGL puts this byte into R,G and B 
//components of the texture
Gdx.gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_LUMINANCE, 
    width, height, 0, GL20.GL_LUMINANCE, GL20.GL_UNSIGNED_BYTE, yBuffer);

//Use linear interpolation when magnifying/minifying the texture to 
//areas larger/smaller than the texture size
Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, 
    GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_LINEAR);
Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, 
    GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_LINEAR);
Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, 
    GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_EDGE);
Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, 
    GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_EDGE);

/*
 * Prepare the UV channel texture
 */

//Set texture slot 1 as active and bind our texture object to it
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE1);
uvTexture.bind();

//UV texture is (width/2*height/2) in size (downsampled by 2 in 
//both dimensions, each pixel corresponds to 4 pixels of the Y channel) 
//and each pixel is two bytes. By setting GL_LUMINANCE_ALPHA, OpenGL 
//puts first byte (V) into R,G and B components and of the texture
//and the second byte (U) into the A component of the texture. That's 
//why we find U and V at A and R respectively in the fragment shader code.
//Note that we could have also found V at G or B as well. 
Gdx.gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_LUMINANCE_ALPHA, 
    width/2, height/2, 0, GL20.GL_LUMINANCE_ALPHA, GL20.GL_UNSIGNED_BYTE, 
    uvBuffer);

//Use linear interpolation when magnifying/minifying the texture to 
//areas larger/smaller than the texture size
Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, 
    GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_LINEAR);
Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, 
    GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_LINEAR);
Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, 
    GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_EDGE);
Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, 
    GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_EDGE);

接下来,我们渲染之前准备的网格(覆盖整个屏幕), 着色器将负责渲染网格上的绑定纹理:

shader.begin();

//Set the uniform y_texture object to the texture at slot 0
shader.setUniformi("y_texture", 0);

//Set the uniform uv_texture object to the texture at slot 1
shader.setUniformi("uv_texture", 1);

mesh.render(shader, GL20.GL_TRIANGLES);
shader.end();
最后,着色器接管将纹理渲染到网格的任务, 实现实际转换的片段着色器如下所示:
String fragmentShader = 
    "#ifdef GL_ES\n" +
    "precision highp float;\n" +
    "#endif\n" +

    "varying vec2 v_texCoord;\n" +
    "uniform sampler2D y_texture;\n" +
    "uniform sampler2D uv_texture;\n" +

    "void main (void){\n" +
    "   float r, g, b, y, u, v;\n" +

    //We had put the Y values of each pixel to the R,G,B components by 
    //GL_LUMINANCE, that's why we're pulling it from the R component,
    //we could also use G or B
    "   y = texture2D(y_texture, v_texCoord).r;\n" + 

    //We had put the U and V values of each pixel to the A and R,G,B 
    //components of the texture respectively using GL_LUMINANCE_ALPHA. 
    //Since U,V bytes are interspread in the texture, this is probably 
    //the fastest way to use them in the shader
    "   u = texture2D(uv_texture, v_texCoord).a - 0.5;\n" +
    "   v = texture2D(uv_texture, v_texCoord).r - 0.5;\n" +

    //The numbers are just YUV to RGB conversion constants
    "   r = y + 1.13983*v;\n" +
    "   g = y - 0.39465*u - 0.58060*v;\n" +
    "   b = y + 2.03211*u;\n" +

    //We finally set the RGB color of our pixel
    "   gl_FragColor = vec4(r, g, b, 1.0);\n" +
    "}\n"; 
请注意,我们使用相同的坐标变量v_texCoord访问Y和UV纹理,这是由于v_texCoord在-1.0和1.0之间,从纹理的一端到另一端,而不是实际的纹理像素坐标, 这是着色器最好的功能之一。

最后为了方便读者学习,给出完整的代码:

由于libgdx是跨平台的,因此我们需要一个可以在处理设备摄像头和渲染的不同平台中进行不同扩展的对象。 例如,如果您可以让硬件为您提供RGB图像,则可能需要绕过YUV-RGB着色器转换。 因此,我们需要一个将由每个不同平台实现的设备摄像头控制器接口:

public interface PlatformDependentCameraController {

    void init();

    void renderBackground();

    void destroy();
} 
该界面的Android版本如下(实时摄像机图像假定为1280x720像素):

public class AndroidDependentCameraController implements PlatformDependentCameraController, Camera.PreviewCallback {

    private static byte[] image; //The image buffer that will hold the camera image when preview callback arrives

    private Camera camera; //The camera object

    //The Y and UV buffers that will pass our image channel data to the textures
    private ByteBuffer yBuffer;
    private ByteBuffer uvBuffer;

    ShaderProgram shader; //Our shader
    Texture yTexture; //Our Y texture
    Texture uvTexture; //Our UV texture
    Mesh mesh; //Our mesh that we will draw the texture on

    public AndroidDependentCameraController(){

        //Our YUV image is 12 bits per pixel
        image = new byte[1280*720/8*12];
    }

    @Override
    public void init(){

        /*
         * Initialize the OpenGL/libgdx stuff
         */

        //Do not enforce power of two texture sizes
        Texture.setEnforcePotImages(false);

        //Allocate textures
        yTexture = new Texture(1280,720,Format.Intensity); //A 8-bit per pixel format
        uvTexture = new Texture(1280/2,720/2,Format.LuminanceAlpha); //A 16-bit per pixel format

        //Allocate buffers on the native memory space, not inside the JVM heap
        yBuffer = ByteBuffer.allocateDirect(1280*720);
        uvBuffer = ByteBuffer.allocateDirect(1280*720/2); //We have (width/2*height/2) pixels, each pixel is 2 bytes
        yBuffer.order(ByteOrder.nativeOrder());
        uvBuffer.order(ByteOrder.nativeOrder());

        //Our vertex shader code; nothing special
        String vertexShader = 
                "attribute vec4 a_position;                         \n" + 
                "attribute vec2 a_texCoord;                         \n" + 
                "varying vec2 v_texCoord;                           \n" + 

                "void main(){                                       \n" + 
                "   gl_Position = a_position;                       \n" + 
                "   v_texCoord = a_texCoord;                        \n" +
                "}                                                  \n";

        //Our fragment shader code; takes Y,U,V values for each pixel and calculates R,G,B colors,
        //Effectively making YUV to RGB conversion
        String fragmentShader = 
                "#ifdef GL_ES                                       \n" +
                "precision highp float;                             \n" +
                "#endif                                             \n" +

                "varying vec2 v_texCoord;                           \n" +
                "uniform sampler2D y_texture;                       \n" +
                "uniform sampler2D uv_texture;                      \n" +

                "void main (void){                                  \n" +
                "   float r, g, b, y, u, v;                         \n" +

                //We had put the Y values of each pixel to the R,G,B components by GL_LUMINANCE, 
                //that's why we're pulling it from the R component, we could also use G or B
                "   y = texture2D(y_texture, v_texCoord).r;         \n" + 

                //We had put the U and V values of each pixel to the A and R,G,B components of the
                //texture respectively using GL_LUMINANCE_ALPHA. Since U,V bytes are interspread 
                //in the texture, this is probably the fastest way to use them in the shader
                "   u = texture2D(uv_texture, v_texCoord).a - 0.5;  \n" +                                   
                "   v = texture2D(uv_texture, v_texCoord).r - 0.5;  \n" +


                //The numbers are just YUV to RGB conversion constants
                "   r = y + 1.13983*v;                              \n" +
                "   g = y - 0.39465*u - 0.58060*v;                  \n" +
                "   b = y + 2.03211*u;                              \n" +

                //We finally set the RGB color of our pixel
                "   gl_FragColor = vec4(r, g, b, 1.0);              \n" +
                "}                                                  \n"; 

        //Create and compile our shader
        shader = new ShaderProgram(vertexShader, fragmentShader);

        //Create our mesh that we will draw on, it has 4 vertices corresponding to the 4 corners of the screen
        mesh = new Mesh(true, 4, 6, 
                new VertexAttribute(Usage.Position, 2, "a_position"), 
                new VertexAttribute(Usage.TextureCoordinates, 2, "a_texCoord"));

        //The vertices include the screen coordinates (between -1.0 and 1.0) and texture coordinates (between 0.0 and 1.0)
        float[] vertices = {
                -1.0f,  1.0f,   // Position 0
                0.0f,   0.0f,   // TexCoord 0
                -1.0f,  -1.0f,  // Position 1
                0.0f,   1.0f,   // TexCoord 1
                1.0f,   -1.0f,  // Position 2
                1.0f,   1.0f,   // TexCoord 2
                1.0f,   1.0f,   // Position 3
                1.0f,   0.0f    // TexCoord 3
        };

        //The indices come in trios of vertex indices that describe the triangles of our mesh
        short[] indices = {0, 1, 2, 0, 2, 3};

        //Set vertices and indices to our mesh
        mesh.setVertices(vertices);
        mesh.setIndices(indices);

        /*
         * Initialize the Android camera
         */
        camera = Camera.open(0);

        //We set the buffer ourselves that will be used to hold the preview image
        camera.setPreviewCallbackWithBuffer(this); 

        //Set the camera parameters
        Camera.Parameters params = camera.getParameters();
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        params.setPreviewSize(1280,720); 
        camera.setParameters(params);

        //Start the preview
        camera.startPreview();

        //Set the first buffer, the preview doesn't start unless we set the buffers
        camera.addCallbackBuffer(image);
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {

        //Send the buffer reference to the next preview so that a new buffer is not allocated and we use the same space
        camera.addCallbackBuffer(image);
    }

    @Override
    public void renderBackground() {

        /*
         * Because of Java's limitations, we can't reference the middle of an array and 
         * we must copy the channels in our byte array into buffers before setting them to textures
         */

        //Copy the Y channel of the image into its buffer, the first (width*height) bytes are the Y channel
        yBuffer.put(image, 0, 1280*720);
        yBuffer.position(0);

        //Copy the UV channels of the image into their buffer, the following (width*height/2) bytes are the UV channel; the U and V bytes are interspread
        uvBuffer.put(image, 1280*720, 1280*720/2);
        uvBuffer.position(0);

        /*
         * Prepare the Y channel texture
         */

        //Set texture slot 0 as active and bind our texture object to it
        Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0);
        yTexture.bind();

        //Y texture is (width*height) in size and each pixel is one byte; by setting GL_LUMINANCE, OpenGL puts this byte into R,G and B components of the texture
        Gdx.gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_LUMINANCE, 1280, 720, 0, GL20.GL_LUMINANCE, GL20.GL_UNSIGNED_BYTE, yBuffer);

        //Use linear interpolation when magnifying/minifying the texture to areas larger/smaller than the texture size
        Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_LINEAR);
        Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_LINEAR);
        Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_EDGE);
        Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_EDGE);


        /*
         * Prepare the UV channel texture
         */

        //Set texture slot 1 as active and bind our texture object to it
        Gdx.gl.glActiveTexture(GL20.GL_TEXTURE1);
        uvTexture.bind();

        //UV texture is (width/2*height/2) in size (downsampled by 2 in both dimensions, each pixel corresponds to 4 pixels of the Y channel) 
        //and each pixel is two bytes. By setting GL_LUMINANCE_ALPHA, OpenGL puts first byte (V) into R,G and B components and of the texture
        //and the second byte (U) into the A component of the texture. That's why we find U and V at A and R respectively in the fragment shader code.
        //Note that we could have also found V at G or B as well. 
        Gdx.gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_LUMINANCE_ALPHA, 1280/2, 720/2, 0, GL20.GL_LUMINANCE_ALPHA, GL20.GL_UNSIGNED_BYTE, uvBuffer);

        //Use linear interpolation when magnifying/minifying the texture to areas larger/smaller than the texture size
        Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_LINEAR);
        Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_LINEAR);
        Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_EDGE);
        Gdx.gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_EDGE);

        /*
         * Draw the textures onto a mesh using our shader
         */

        shader.begin();

        //Set the uniform y_texture object to the texture at slot 0
        shader.setUniformi("y_texture", 0);

        //Set the uniform uv_texture object to the texture at slot 1
        shader.setUniformi("uv_texture", 1);

        //Render our mesh using the shader, which in turn will use our textures to render their content on the mesh
        mesh.render(shader, GL20.GL_TRIANGLES);
        shader.end();
    }

    @Override
    public void destroy() {
        camera.stopPreview();
        camera.setPreviewCallbackWithBuffer(null);
        camera.release();
    }
}
主应用程序部分只是确保在开始时调用一次init(),renderBackground()每帧渲染循环,并且destroy()最后调用一次:
public class YourApplication implements ApplicationListener {

    private final PlatformDependentCameraController deviceCameraControl;

    public YourApplication(PlatformDependentCameraController cameraControl) {
        this.deviceCameraControl = cameraControl;
    }

    @Override
    public void create() {              
        deviceCameraControl.init();
    }

    @Override
    public void render() {      
        Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);

        //Render the background that is the live camera image
        deviceCameraControl.renderBackground();

        /*
         * Render anything here (sprites/models etc.) that you want to go on top of the camera image
         */
    }

    @Override
    public void dispose() {
        deviceCameraControl.destroy();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}
唯一的其他Android特定部分是以下非常短的主要Android代码,您只需创建一个新的Android特定设备相机处理程序并将其传递到主要的libgdx对象:

public class MainActivity extends AndroidApplication {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
        cfg.useGL20 = true; //This line is obsolete in the newest libgdx version
        cfg.a = 8;
        cfg.b = 8;
        cfg.g = 8;
        cfg.r = 8;

        PlatformDependentCameraController cameraControl = new AndroidDependentCameraController();
        initialize(new YourApplication(cameraControl), cfg);

        graphics.getView().setKeepScreenOn(true);
    }
}
运行速度是非常快的,测试结果如下:

三星Galaxy Note II LTE - (GT-N7105):拥有ARM Mali-400 MP4 GPU。
渲染一帧需要大约5-6毫秒,每隔几秒偶尔会跳到15 ms左右
实际渲染线(mesh.render(着色器,GL20.GL_TRIANGLES);)一致地需要0-1 ms
两个纹理的创建和绑定总共需要1-3毫秒
ByteBuffer副本通常需要1-3毫秒,但偶尔跳到大约7ms,这可能是由于图像缓冲区在JVM堆中移动。


三星Galaxy Note 10.1 2014 - (SM-P600):具有ARM Mali-T628 GPU。
渲染一帧需要大约2-4毫秒,罕见的跳到大约6-10毫秒
实际渲染线(mesh.render(着色器,GL20.GL_TRIANGLES);)一致地需要0-1 ms
两个纹理的创建和绑定总共需要1-3毫秒,但每两秒钟跳到大约6-9毫秒
ByteBuffer副本通常总共需要0-2 ms,但很少跳到大约6ms

另外将Shader的顶点和片段着色器展现如下:

attribute vec4 position;
attribute vec2 inputTextureCoordinate;
varying vec2 v_texCoord;
void main()
{
   gl_Position = position;
   v_texCoord = inputTextureCoordinate;
}


precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D yTexture; 
uniform sampler2D uvTexture;
const mat3 yuv2rgb = mat3(
                        1, 0, 1.2802,
                        1, -0.214821, -0.380589,
                        1, 2.127982, 0
                        );

void main() {    
    vec3 yuv = vec3(
                1.1643 * (texture2D(yTexture, v_texCoord).r - 0.0627),
                texture2D(uvTexture, v_texCoord).a - 0.5,
                texture2D(uvTexture, v_texCoord).r - 0.5
                );
    vec3 rgb = yuv * yuv2rgb;
    gl_FragColor = vec4(rgb, 1.0);
}



作者:jxw167 发表于2017/7/20 15:17:28 原文链接
阅读:777 评论:0 查看评论

Retrofit 原理简析

$
0
0

简介

  Retrofit 所做的事情简单来说就是将你的 Http 业务 API --> Http 请求实现,类似于 Spring MVC 中的 Controller,它的主要任务是解析你的业务接口,从接口上获取你的 Http 接口协议,然后组装 Http 请求,进行异步 Request。
  Retrofit 整合了多个组件,包括 JSON/XML 的 ORM 映射,用于解析返回值;Http请求驱动用语发送 Http 请求,Retrofit 默认使用的是 Okhttp;异步处理框架,包括Observable。

原理

  如何生成一个接口的实现类,除了手动实现该接口之外,还有另外一种选择,那就是动态代理;Retrofit 的核心正是如此。
  如同 SpringMVC 的 Controller 一样,在方法描述上(方法描述包括 Modifiers,参数列表,返回值类型,异常列表),再加上参数列表上的注解,方法体上的注解,通过一个方法的描述就可以基本确定一个 Http 接口的所有协议了。

1. 请 求 目 标 地 址 和 请 求 方 法 :

  通 过 解 析 方 法 体 上 的 注 解 。 类 似 这 种@POST("/mobile/login") 代表请 POST 方法,地址“Domain/mobile/login”。

2.请求参数:

  通过解析参数列表和参数列表上的注解,如(@Query("loginname")StringloginName, @Query("pass")String pass) 代表其有两个参数 loginName,pass。

3. 请求返回值:

  通 过 解 析 方 法 的 返 回 值 类 型 。 例 如Observable<baseresponse> Observable 是异步可观测数据 RxJava 的包裹类,真正的业务 Model 类型在范型里面。

源码分析

  先来看创建 API 代理的入口方法 Retrofit.create(Class class):
public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

1.Utils.validateServiceInterface(service):

  检查传进来的 Class 对象是否合法,主要检查两个方面一是 Class 是否是接口,二是接口一定不能继承其他接口。

2.eagerlyValidateMethods(service):

  validateEagerly 这个参数控制了是否是在 create 是提前解析业务接口并且进行缓存,否则的话将需要在业务方法调用时解析。

3.Platform:

  如字面所描述平台类,Retrofit 专门为操作系统运行环境抽象了一层,事实上,Retrofit 可以运行在 Android,IOS,Java8 三个平台上。获取比较诡异的事为什么 Java代码可以在 IOS 平台运行,事实上是借助了一个叫 robovm 的第三方 JVM,Class.forName("org.robovm.apple.foundation.NSOperationQueue"); 这段代码就是获取 robovm 的线程池。Platform 主要提供了异步所需要的线程池,异步同步控制,而这个是一个抽象的接口,在 Retrofit 初始化时是可以指定其实现的。CallAdapter 就是上述接口。

4.invokeDefaultMethod:

  这个对应了 Java8 中 的 default 方 法 , 暂 时 没 有 实 现 , 会 抛 出UnSupportOperationException,也就是说你不可以调用接口中的 default 方法。

5.loadServiceMethod(method):

  加载 API 方法的实现,即加载动态代理,前面说过了,如果在 create 时已经解析过接口了的话,代理对象直接在缓存中找到。如果没有则需要在这个时候解析并且存储到缓存中,下次调用同样的接口就不需要重复解析了,毕竟业务解析大量用到了反射这种耗时操作。

6.ServiceMethod.Build():

  解析业务接口方法并且生成对应的动态代理。
  解析返回值类型: Utils.getRawType 从返回值的范型中扣出真正的业务类型,应为返回值一般不会直接写业务 Model,而是对异步任务的包裹,对 Callback 的封装。
  createResponseConverter 和 parseMethodAnnotation 开始创建 Http 请求,这时候需要解析方法上的 Annotation,这个 Annotation 包含了 URL,和请求方法。获取完了这些信息之后,这个方法就开始根据这些参数拼装完整的 URL。
  parseParameter 解析方法参数 ,获取 请 求 参数 。 这个方法主要就是调用了parseParameterAnnotation 解析参数列表上的注解,注解中包含了参数类型,参数名,Header,Path 等信息。参数类型也可以是 Form,Part 等复杂类型。
  createCallAdapter 上面说过了,Retrofit 只是一个业务解析器,组装器,诸如返回值的 JSON/XML 解析,Http 请求底层等等都是交给其他框架处理的,Adapter 就是抽象接口,createCallAdapter 就是根据配置进行工具组装,组装完毕,请求到解析一整套流程就可以顺利执行了。

作者:ganyao939543405 发表于2017/7/20 15:20:37 原文链接
阅读:146 评论:0 查看评论

React Native入门(四)之使用Flexbox布局

$
0
0

前言

flex,收缩,弹性的意思。

弹性(Flex)宽高

关于RN中宽高的设置,我们在上一篇设置Image加载网络图片的时候提到过,首先widthheight是两个属性,用来指定一个组件的宽高,使用的时候可以这样:
<Image source={pic} style={{width: 200, height: 200}} />
也可以在外边定义,之后再引用,这个就不多说了!
React Native中的尺寸都是无单位的,表示的是与设备像素密度无关的逻辑像素点

那么什么是弹性(Flex)宽高?
官网的说法,在组件样式中使用flex可以使其在可利用的空间中动态地扩张或收缩一般而言我们会使用flex:1来指定某个组件扩张以撑满所有剩余的空间如果有多个并列的子组件使用了flex:1,则这些子组件会平分父容器中剩余的空间如果这些并列的子组件的flex值不一样,则谁的值更大,谁占据剩余空间的比例就更大(即占据剩余空间的比等于并列组件间flex值的比)
看完上面的一段描述,我想大部分人都会想到Android里边控件的一个属性!!!

没错,就是weight,比重,权重!!!
我们使用flex的时候,可以对应weight属性,就非常好理解了!

Flexbox布局

我们在React Native中使用flexbox规则来指定某个组件的子元素的布局。Flexbox可以在不同屏幕尺寸上提供一致的布局结构。

一般来说,使用flexDirectionalignItemsjustifyContent三个样式属性就已经能满足大多数布局需求。

其实弹性盒子模型(Flexible Box)是CSS3.0之后加入的新特性,RN中的Flexbox的工作原理和web上的CSS基本一致,当然也存在少许差异。首先是默认值不同:flexDirection的默认值是column而不是row,而flex只能指定一个数字值

说到这里,我想提一下Android最新发布的约束布局ConstraintLayout,这个布局呢,和弹性盒子模型(Flexible Box)是非常相似的。现在AS在创建新项目的时候,默认的布局已经是ConstraintLayout了!
我们发现一个好的设计出现的时候,不管是哪个平台都会有与之对应的内容出现,所以结合起来学习的话,对于我们加深对这块的了解还是很有帮助的!

Flex Direction

在组件的style中指定flexDirection可以决定布局的主轴方向。对应的值有两个:row沿着水平轴方向排列;column沿着竖直轴方向排列,默认值是竖直轴(column)方向
这个可以理解Android中线性布局LinearLayout的orientation属性,对应的同样有水平方向horizontal和垂直方向vertical
举个例子:

<View style={{flex: 1, flexDirection: 'row'}}>
  <View style={{flex: 1, backgroundColor: 'powderblue'}} />
  <View style={{flex: 1, backgroundColor: 'skyblue'}} />
  <View style={{flex: 1, backgroundColor: 'steelblue'}} />
</View>

运行一下,我们可以看到3种颜色占据同面积且水平排列!
这里写图片描述
如果,修改为flexDirection: 'column',就会发现它们垂直排列!

Justify Content

在组件的style中指定justifyContent可以决定其子元素沿着主轴的排列方式。对应的取值有以下几种,这里我们改动一下上边的代码:

<View style={{
  flex: 1, 
  flexDirection: 'row',
  justifyContent: 'flex-start'   
}}>
  <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
  <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
  <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
</View>

注:下边截图都是以flexDirection: 'row'方向

  • flex-start
    弹性盒子元素将向行起始位置对齐。该行的第一个子元素的主起始位置的边界将与该行的主起始位置的边界对齐,同时所有后续的伸缩盒项目与其前一个项目对齐。
    这里写图片描述
  • center
    弹性盒子元素将向行中间位置对齐。该行的子元素将相互对齐并在行中居中对齐,同时第一个元素与行的主起始位置的边距等同与最后一个元素与行的主结束位置的边距(如果剩余空间是负数,则保持两端相等长度的溢出)。
    这里写图片描述
  • flex-end
    弹性盒子元素将向行结束位置对齐。该行的第一个子元素的主结束位置的边界将与该行的主结束位置的边界对齐,同时所有后续的伸缩盒项目与其前一个项目对齐。
    这里写图片描述
  • space-around
    弹性盒子元素会平均地分布在行里,两端保留子元素与子元素之间间距大小的一半。如果最左边的剩余空间是负数,或该行只有一个伸缩盒项目,则该值等效于'center'。在其它情况下,伸缩盒项目则平均分布,并确保两两之间的空白空间相等,同时第一个元素前的空间以及最后一个元素后的空间为其他空白空间的一半。
    这里写图片描述
  • space-between
    弹性盒子元素会平均地分布在行里。如果最左边的剩余空间是负数,或该行只有一个子元素,则该值等效于'flex-start'。在其它情况下,第一个元素的边界与行的主起始位置的边界对齐,同时最后一个元素的边界与行的主结束位置的边距对齐,而剩余的伸缩盒项目则平均分布,并确保两两之间的空白空间相等。
    这里写图片描述

Align Items

在组件的style中指定alignItems可以决定其子元素沿着次轴(与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式。
这里我们在上边代码中设置justifyContent: 'center',然后加入这个属性,看一看效果!
对应的这些可选取值有:

  • flex-start
    子元素的次轴起始位置的边界紧靠住该行的次轴起始边界
    这里写图片描述
  • center
    子元素在该行的次轴上居中放置。
    (如果该行的尺寸小于子元素的尺寸,则会向两个方向溢出相同的长度)。
    这里写图片描述
  • flex-end
    子元素次轴起始位置的边界紧靠住该行的次轴结束边界
    这里写图片描述
  • stretch
    延伸,伸展的意思,子元素在次轴方向上面延伸
    这个有点特殊,要使stretch选项生效的话,子元素在次轴方向上不能有固定的尺寸。我们代码中主轴方向flexDirection: 'row',所以垂直方向上不能有固定的尺寸,所以要把height: 50都去掉!
    这里写图片描述

结语

本篇文章了解了使用Flexbox布局的相关内容,在下一篇博客,我们要了解了一下常见的输入框和按钮的基本使用,并配合flexbox来搭建一个简单的登录界面!
好了,先这样,下篇见!

作者:aiynmimi 发表于2017/7/20 18:24:09 原文链接
阅读:18 评论:0 查看评论

Flutter学习之旅----环境搭建与Hello World

$
0
0

Flutter官网镇楼

Flutter来自Google,是移动端APP开发SDK,使用Dart语言编写一套代码即可同时在Android和iOS平台运行,性能无限接近原生,支持android 4.1以上 和 iOS8以上。想进一步了解Flutter,进入传送门(英文版)。目前虽然是alpha版,截止到2017年7月20日,其在Github上的star达5.6k+,而且相当活跃,不论是使用者还是开发工程师,都在不停的交互改善,1.6k+话题被打开,4.5k+话题被关闭,说明谷歌工程师还是很重视的。在学习过程中,遇到一些常见的问题,可以到这里来寻找帮助。
看到这些花花绿绿的标签,谷歌工程师也是用心良苦啊!同时也让我们更有信心,Flutter会越来越完善。

这里写图片描述

这里先说一下我使用的开发环境,MacBook Air,1.6 GHz Intel Core i5,8 GB 1600 MHz DDR3。下面正式进入主题。

1.下载安装Flutter SDK

直接在mac的Terminal输入命令

git clone -b alpha https://github.com/flutter/flutter.git
export PATH=`pwd`/flutter/bin:$PATH

flutter doctor  

第一条命令:下载Flutter
第二条命令:设置环境变量
第三条命令:安装相关依赖,可重复执行
在执行命令过程中可能会失败,可能是网络原因,没关系,多试几次。
最后,Flutter SDK下载后的路径: /Users/用户名/flutter,要注意的是flutter文件夹下面有多个同名的flutter文件夹,真正的SDK路径只到顶层flutter文件夹。
对Mac系统不熟悉的同学可能找不到打开文件夹的地方,看下面GIF图就知道了。

打开文件管理
这里写图片描述

进入sdk目录
这里写图片描述

获取文件完整路径
这里写图片描述

2.安装idea编译器

下载地址
选择Community版本,下载后直接安装,很简单。

3.安装Flutter和Dart插件

打开idea,按照下图,在仓库里面搜索flutter,然后点击Install,安装的时候自动安装了Dart。

这里写图片描述

左侧菜单栏有Dart和Flutter说明这两个安装好并可以用了,右边红色方框设置Flutter SDK(这点很重要)。
这里写图片描述

4.运行

新建工程,工程名不能含大写字母,这与Android Stuidio不同。Creating Flutter Project这个界面可能会卡一会,如果不想一直等待就重新来。

这里写图片描述

如果在创建的过程中出现如下错误,那么Close Project,然后重新打开即可。

这里写图片描述

新建的工程已经默认帮我们实现了一个界面,可以直接运行,运行结果:

这里写图片描述

如果我们只想简单的实现”Hello World”,用下面的代码替换掉main.dart里面的代码即可。

import 'package:flutter/material.dart';
void main(){
  runApp(new Center(child: new Text('Hello Flutter!')));
}

解释一下:方法runAPP()的参数Center是控件树的根,控件树包含Center和Text,框架强制根控件覆盖全屏幕,因此“Hello Flutter!”在屏幕中间。

运行结果
这里写图片描述

至此,Flutter开发环境搭好了,Hello World Demo也能跑起来了。在Mac系统下这些操作还算顺利,我曾经在Wondows系统下进行同样的操作,执行Flutter Doctor的时候一直进行不下去,可能是谷歌先推出Mac版的原因吧。这是这个系列的第一篇,后面会介绍如何快速开发出一个APP。

虽然代码比较简单,这里还是给出源码,源码下载

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

Android源码解析四大组件系列(五)---广播的注册过程

$
0
0

广播这个篇幅打算用四篇文章来写,分别为广播注册、广播处理、广播的发送,广播深入细节理解,如果都写到一篇文章会比较长,所以拆分成四篇来写。

第一篇
Android源码解析—广播的注册过程
第二篇
Android源码解析—广播的处理过程
第三篇
Android源码解析—广播的发送过程
第四篇
Android源码解析—广播深入细节理解

想收到广播(Broadcast),必须先要注册接收广播的组件—广播接收者(receiver),广播接收者的注册分为动态注册和静态注册,而注册中心就是AMS,AMS再把广播分发到各个广播接收者(receiver)。

image.png

一个广播可以有多个receiver来接收它,注册的方式分为两种,一种是静态注册,一种是动态注册,动态注册广播不是常驻型广播,也就是说广播跟随Activity的生命周期,在Activity结束前,需要移除广播接收器。 静态注册是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。

1.1 动态广播注册

动态注册是由ContextImpl的registerReceiver方法调用registerReceiverInternal来注册的

private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
            IntentFilter filter, String broadcastPermission,
            Handler scheduler, Context context) {
        IIntentReceiver rd = null;
        if (receiver != null) {
            if (mPackageInfo != null && context != null) {
                //为空表示默认为主线程
                if (scheduler == null) {
                  //AMS并不是直接给广播接收者发送广播的,当广播到达应用程序进程的时候,会被封装成一个Message,然后push到主线程消息队列中,然后才会给接收者处理,你也可以指定一个处理的Handler,将onReceive()调度在非主线程执行。
                    scheduler = mMainThread.getHandler();
                }
                rd = mPackageInfo.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);
            } else {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = new LoadedApk.ReceiverDispatcher(
                        receiver, context, scheduler, null, true).getIIntentReceiver();
            }
        }
        try {
          //将rd,filter等发送给AMS
            final Intent intent = ActivityManagerNative.getDefault().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName,
                    rd, filter, broadcastPermission, userId);
            if (intent != null) {
                intent.setExtrasClassLoader(getClassLoader());
                intent.prepareToEnterProcess();
            }
            return intent;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

传进来的receiver不是直接发送给AMS的,首先会把receiver封装成一个IIntentReceiver对象rd,这个rd是一个binder本地对象,具备了跨进程通信的能力。mPackageInfo是LoadedApk,LoadedApk这个类包含了当前加载的apk的主要的信息,其中成员变量mReceivers表就记录了所有动态注册的receiver。

 private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
        = new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();

rd的获取有两种,当mPackageInfo存在时候,就通过mPackageInfo.getReceiverDispatcher()来获取。

    public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
            Context context, Handler handler,
            Instrumentation instrumentation, boolean registered) {
        synchronized (mReceivers) {
            LoadedApk.ReceiverDispatcher rd = null;
            ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
        //registered传进来的是true
            if (registered) {
                map = mReceivers.get(context);
                if (map != null) {
                    rd = map.get(r);
                }
            }
            if (rd == null) {
                rd = new ReceiverDispatcher(r, context, handler,
                        instrumentation, registered);
                if (registered) {
                    if (map == null) {
                        map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
                        mReceivers.put(context, map);
                    }
                    map.put(r, rd);
                }
            } else {
           //检查广播分发者的context、handler是否一致
                rd.validate(context, handler);
            }
            rd.mForgotten = false;
            return rd.getIIntentReceiver();
        }
    }

这个方法内部维护了一张表 ArrayMap

 static final class ReceiverDispatcher {

        final static class InnerReceiver extends IIntentReceiver.Stub {
            final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
            final LoadedApk.ReceiverDispatcher mStrongRef;

            InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
                mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
                mStrongRef = strong ? rd : null;
            }

        .......

        final IIntentReceiver.Stub mIIntentReceiver;
        final BroadcastReceiver mReceiver;
        final Context mContext;
        final Handler mActivityThread;
        .......
        ReceiverDispatcher(BroadcastReceiver receiver, Context context,
                Handler activityThread, Instrumentation instrumentation,
                boolean registered) {
            if (activityThread == null) {
                throw new NullPointerException("Handler must not be null");
            }
            mIIntentReceiver = new InnerReceiver(this, !registered);
            //广播接收者
            mReceiver = receiver;
            //表示哪个发送的广播
            mContext = context;
            //主线程
            mActivityThread = activityThread;
             .......
        }
        .......
        IIntentReceiver getIIntentReceiver() {
            return mIIntentReceiver;
        }
       .......
    }

在内部会创建InnerReceiver,InnerReceiver是ReceiverDispatcher的内部类,是一个实现Binder的本地对象,前面也说过了,最终是将一个InnerReceiver对象注册到了AMS中。

OK,绕了这么一大圈子,其实就是为了封装一个InnerReceiver用于和AMS通信,我也不知道谷歌这帮程序员怎么想的,有点麻烦。忽略跨进程的代码,现在由用户进程走到SystemServer进程了,即走到AMS的registerReceiver方法。

 public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
        enforceNotIsolatedCaller("registerReceiver");
        ArrayList<Intent> stickyIntents = null;
        ProcessRecord callerApp = null;
        int callingUid;
        int callingPid;
        synchronized(this) {
            if (caller != null) {
              //由caller获取当前进程对象
                callerApp = getRecordForAppLocked(caller);
                //进程还没创建,直接抛出异常
                if (callerApp == null) {
                    throw new SecurityException(
                            "Unable to find app for caller " + caller
                            + " (pid=" + Binder.getCallingPid()
                            + ") when registering receiver " + receiver);
                }
                if (callerApp.info.uid != Process.SYSTEM_UID &&
                        !callerApp.pkgList.containsKey(callerPackage) &&
                        !"android".equals(callerPackage)) {
                    throw new SecurityException("Given caller package " + callerPackage
                            + " is not running in process " + callerApp);
                }
                callingUid = callerApp.info.uid;
                callingPid = callerApp.pid;
            } else {
                callerPackage = null;
                callingUid = Binder.getCallingUid();
                callingPid = Binder.getCallingPid();
            }

            userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
                    ALLOW_FULL_ONLY, "registerReceiver", callerPackage);

           //获取IntentFilter中的action
            Iterator<String> actions = filter.actionsIterator();
            if (actions == null) {
                ArrayList<String> noAction = new ArrayList<String>(1);
                noAction.add(null);
                actions = noAction.iterator();
            }

            //从actions中,先把粘性广播帅选出来,放进stickyIntents中
            int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
            while (actions.hasNext()) {
                String action = actions.next();
                for (int id : userIds) {
                //从mStickyBroadcasts中查看用户的sticky Intent,mStickyBroadcasts存了系统所有的粘性广播
                    ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);
                    if (stickies != null) {
                        ArrayList<Intent> intents = stickies.get(action);
                        if (intents != null) {
                            if (stickyIntents == null) {
                                stickyIntents = new ArrayList<Intent>();
                            }
                            stickyIntents.addAll(intents);
                        }
                    }
                }
            }
        }

        ArrayList<Intent> allSticky = null;
        if (stickyIntents != null) {
            final ContentResolver resolver = mContext.getContentResolver();
            // Look for any matching sticky broadcasts...
            for (int i = 0, N = stickyIntents.size(); i < N; i++) {
                Intent intent = stickyIntents.get(i);
                // If intent has scheme "content", it will need to acccess
                // provider that needs to lock mProviderMap in ActivityThread
                // and also it may need to wait application response, so we
                // cannot lock ActivityManagerService here.
                if (filter.match(resolver, intent, true, TAG) >= 0) {
                    if (allSticky == null) {
                        allSticky = new ArrayList<Intent>();
                    }
                    allSticky.add(intent);
                }
            }
        }

        // The first sticky in the list is returned directly back to the client.
        Intent sticky = allSticky != null ? allSticky.get(0) : null;
        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
        //如果receiver为空,就直接返回了
        if (receiver == null) {
            return sticky;
        }

        synchronized (this) {
            if (callerApp != null && (callerApp.thread == null
                    || callerApp.thread.asBinder() != caller.asBinder())) {
                // 进程不存在(死亡了),也是不能注册成功的
                return null;
            }
           //mRegisteredReceivers表存了所有动态注册的广播接收者,由receiver作为key,获取到ReceiverList,为什么是ReceiverList,而不是一个Receiver呢,因为一个广播可能会有多个接收者,最好整成一个队列或者链表的形式,而ReceiverList继承ArrayList,满足这个需求。每个ReceiverList都对应着Client端的一个ReceiverDispatcher。
            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
            if (rl == null) {
                rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                        userId, receiver);
                if (rl.app != null) {
                    //把广播接收者列表加到这个进程对象的receivers中
                    rl.app.receivers.add(rl);
                } else {
                    try {
                        //进程不存在,注册死亡通知
                        receiver.asBinder().linkToDeath(rl, 0);
                    } catch (RemoteException e) {
                        return sticky;
                    }
                    rl.linkedToDeath = true;
                }
                //新创建的接收者队列,添加到已注册广播队列。
                mRegisteredReceivers.put(receiver.asBinder(), rl);
            } else if (rl.uid != callingUid) {
                throw new IllegalArgumentException(
                        "Receiver requested to register for uid " + callingUid
                        + " was previously registered for uid " + rl.uid);
            } else if (rl.pid != callingPid) {
                throw new IllegalArgumentException(
                        "Receiver requested to register for pid " + callingPid
                        + " was previously registered for pid " + rl.pid);
            } else if (rl.userId != userId) {
                throw new IllegalArgumentException(
                        "Receiver requested to register for user " + userId
                        + " was previously registered for user " + rl.userId);
            }
          //在AMS内部,广播接收者实际上是BroadcastFilter来描述的,由filter等参数创建BroadcastFilter对象,并添加到接收者队列,注意只有registerReceiver()过程才会创建BroadcastFilter,也就是该对象用于动态注册的广播Receiver;,静态的接收者对象不是BroadcastFilter。
            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
                    permission, callingUid, userId);
            rl.add(bf);
            if (!bf.debugCheck()) {
                Slog.w(TAG, "==> For Dynamic broadcast");
            }
            mReceiverResolver.addFilter(bf);

            //如果是粘性广播,创建BroadcastRecord,并添加到BroadcastQueue的并行广播队列(mParallelBroadcasts),注册后调用AMS来尽快处理该广播。
            if (allSticky != null) {
                ArrayList receivers = new ArrayList();
                receivers.add(bf);

                final int stickyCount = allSticky.size();
                for (int i = 0; i < stickyCount; i++) {
                    Intent intent = allSticky.get(i);
                    BroadcastQueue queue = broadcastQueueForIntent(intent);
                    BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                            null, -1, -1, null, null, AppOpsManager.OP_NONE, null, receivers,
                            null, 0, null, null, false, true, true, -1);
                    queue.enqueueParallelBroadcastLocked(r);
                    queue.scheduleBroadcastsLocked();
                }
            }

          //返回值是一个Intent
            return sticky;
        }
    }

总结一下:动态注册是调用registerReceiver来注册的,大致流程如下:

在Android系统中,系统每加载一个apk,就会有一个LoadedApk对象。而每个LoadedApk对象里会有一张名字为mReceivers的HashMap,用来记录每个apk里面动态注册了那些广播接收者。mReceivers的类型是ArrayMap

1.2 静态广播注册

静态注册就是在manifest中注册。

<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="android.intent.action.MY_BROADCAST"/>
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</receiver>

它们的信息会在系统启动时,由PackageManagerService(PMS)解析(在该类的构造方法中会对各个应用安装目录的apk文件进行扫描解析)并记录下来。

 if (tagName.equals("activity")) {
                Activity a = parseActivity(owner, res, parser, flags, outError, false,
                        owner.baseHardwareAccelerated);
                if (a == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }
                owner.activities.add(a);
            } else if (tagName.equals("receiver")) {
                Activity a = parseActivity(owner, res, parser, flags, outError, true, false);
                if (a == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }
                owner.receivers.add(a);
            } else if (tagName.equals("service")) {
                Service s = parseService(owner, res, parser, flags, outError);
                if (s == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }
                owner.services.add(s);
            } else if (tagName.equals("provider")) {
                Provider p = parseProvider(owner, res, parser, flags, outError);
                if (p == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }
                owner.providers.add(p);
            }

经过上面的解析receiver就被保存到了owner.receivers中去了。然后AM会调用PMS的接口来查询“和intent匹配的组件”时,PMS内部就会去查询当初记录下来的数据,并把结果返回AMS。

  List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
                        .queryIntentReceivers(intent, resolvedType, pmFlags, user).getList();
 @Override
    public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent,
            String resolvedType, int flags, int userId) {
        return new ParceledListSlice<>(
                queryIntentReceiversInternal(intent, resolvedType, flags, userId));
    }

    private @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
            String resolvedType, int flags, int userId) {
        if (!sUserManager.exists(userId)) return Collections.emptyList();
        flags = updateFlagsForResolve(flags, userId, intent);
        ComponentName comp = intent.getComponent();
        if (comp == null) {
            if (intent.getSelector() != null) {
                intent = intent.getSelector();
                comp = intent.getComponent();
            }
        }
        if (comp != null) {
            List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
            ActivityInfo ai = getReceiverInfo(comp, flags, userId);
            if (ai != null) {
                ResolveInfo ri = new ResolveInfo();
                ri.activityInfo = ai;
                list.add(ri);
            }
            return list;
        }

        // reader
        synchronized (mPackages) {
            String pkgName = intent.getPackage();
            if (pkgName == null) {
                return mReceivers.queryIntent(intent, resolvedType, flags, userId);
            }
            final PackageParser.Package pkg = mPackages.get(pkgName);
            if (pkg != null) {
                return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,
                        userId);
            }
            return Collections.emptyList();
        }
    }

因为涉及PMS,这段逻辑想写清楚篇幅会比较大,所以,不深入讨论,以上关于广播的动态注册和静态注册就介绍完了。

作者:u013263323 发表于2017/7/20 19:17:29 原文链接
阅读:195 评论:0 查看评论

编程学习应用 Growth 发布 3.0,在碎片时间里练习

$
0
0

Growth 1.0~2.0 已经有 2101 次提交,而 Growth 3.0 则已经拥有了 900+ 提交。这意味着 Growth 整个项目有多达 3000 次提交,感谢每一个为 Growth 项目作为贡献的开源先锋。

特别感谢:@travelgeezer 为 Growth 3.0 编写了大量的功能。

现在,让我来开启装逼模式。

使用 React Native 重写,性能提升了 N + 1 倍

在 Growth 1.x 里,我们使用了 Ionic 1.x + Angular 1.x 来开发,而当时 Angular 1.x 已经过时了。

在 Growth 2.x 里,我们使用了 Ionic 2.x + Angular 2.x~4.x 重写了一遍,而我们发现性能不能让人满意。

因此在 Growth 3.x 里,我们使用了 React Native 重写了整个应用,再一次。

使用 React Native,从开发速度上来说,真心没有 WebView 来得快,但是性能上的提升是有目共睹的。至少,打开速度比原来快了好多倍。如我们所料,RN 的坑很多,过些日子,我再写一篇文章吧。

现在,让我们再看看 Growth 这个应用里,有什么新功能。

LeetCode 试题,适合于地铁思考模式

记得很多新手程序员问过我,他们每天要在地铁上花很多的时间,有什么东西可以在这段时间学习的。看书吧,不是很合适,如我这样的人就容易因引此而晕车——所以我一般不喜欢在公交车上玩手机。我给的答案是:这个时候适合思考问题

那么,在这个时候可以来一个 LeetCode 题,每天在路上成长一点点。LeetCode 据说是,一些经典的公司用来面试应聘者的面试题。

LeetCode

目前相应的题目是在 GitHub 上的,下载速度有点慢,希望有产商可以提供个服务器,哈哈哈~~。

如果你每天要在地铁上站一小时,那么请带上一个 LeetCode 问题练习。

算法动画学习工具

基于 AlgorithmVisualizer

既然,我们已经有了那么多算法题,那么我们也应该在手机上学习。于是,Growth 的用户 @ ivanberry 便提出了可以参考:https://visualgo.net/en,可是这个是不开源的。

找了一二个星期,看到了 Algorithm Visualizer 项目,它可以用动画展示算法、展示每一步的执行过程、拥有相应的示例代码。便花了两星期的时间,做成了下面的模样。

Algorithm

上半部分的动画内容是 WebView,下面则是 React Native。

如果你每天要等上半个小时的公交,那么不妨学一个算法知识。

正则表达式练习

基于 Regexper

同样的这个功能,也是由 Growth 用户 @allmelgr 提出来的,他对于 regexer.com 的评价是:用过的人都说简单易懂。是的,一看就懂的样子:

Regex

而为了更方便的学习,便结合 RegexHub 提供一些正则表达式示例,只需要选择对应的正则表达式,就可以展示 相应的正则关系。

如果你每天要在公交车上坐一小时,那么不妨练习一人小时的正则。

设计模式简介

基于 Design Patterns for Humans

既然我们都已经有算法和数据结构、正则表达式,我便将 GitHub 上的 Design Patterns for Humans 也集成了到 APP 里。

Design Pattern

如果你已经能完成工作,但是迷茫,那么试试设计模式

内置 VS Code 的编辑器

基于 Monaco Editor

既然都已经有了 LeetCode,那么我们应该还有一个编辑器。找了几天,终于找到将 VisualStudio Code 的同款编辑器 Monaco Editor 集成进去了。

Code

而显然它不仅仅是一个编辑器,它还可以运行 JavaScript 代码,一个『伟大的手机 IDE』就此诞生了。输入完代码,点击运行,即可运行代码。console.log 会以 Toast 的形式展示出来。

这绝逼是我在 Growth 做的最吊的,但是没有什么卵用的功能。

如果你想试试某行代码,那么试试新的编辑器。

成长路线与技能树

依旧的,仍然可以在上面看到技能树,并且速度更快了:

SkillTree

如果你对某一个领域迷茫,那么来看看对应的技能树。

与此同时,我们更新了一下成长路线:

Roadmap

如果你对某一个领域迷茫,那么来看看对应的学习路线。

未来,我们将结合网上的资源,整合学习路线和技能树——如果有更多的人来参与的话。

Awesome 列表

基于 Awesome

某一天,我在对着 Awesome React Native 发呆的时候,便顺手集成了 Awesome Awesomes 项目——写了个脚本,解析 awesome 项目生成对应的 JSON 文件。

Extends

不过就是这个列表有点太长太长太长太长太长,即使使用了 RN 的 FlatList 也不能很好地解决问题。

如果你找不到合适的灵感,那么不妨看看 Awesome 列表吧。

开源电子书

基于免费的编程中文书籍索引

有一天,我在想虽然我号称是最伟大的『md程序员』,但是一个我,肯定比不上一百个我的写作速度快。于是,我们便想将那些在 GitHub、GitBook 上的书,制作成方便在手机上阅读的内容。便有了:

Extends

实际上,这个功能实现得是最早的。但是在当前并没有什么用,当前只是链接。未来,将制作对应的 API 来获取不同书的内容,就是工作量有点巨大巨大巨大巨大巨大。

如果你找不到免费的电子书,那么试试开源的编程中文书籍。

Growth

在 Growth 3.0,Growth 原先的内容仍然还在,只是还有一些 Bug,啊哈哈

Growth

对于支持 Growth 指南对应的纸质书籍《全栈应用开发:精益实》,觉得好的就给个好评,差的就算了吧~~。

如果你想成长顶尖开发者,那么试试 Growth 吧~。

Discover

在探索栏目里,我们依旧准备了丰富的阅读、练习资源。

Discover

如果你觉得无聊,那么可以在探索和社区里,了解更广泛的世界。

So

这就是 Growth 3.0,让你练习更多。

这就是 Growth 3.0,让你练习更多。

这就是 Growth 3.0,让你练习更多。

PS:应用已在 App Store、Google Play、应用宝、小米应用商店、360应用商店上架,其它用户可以从应用宝 直接下载 APK。

欢迎到 GitHub 来支持我们的开发:https://github.com/phodal/growth

作者:gmszone 发表于2017/7/20 20:26:50 原文链接
阅读:181 评论:0 查看评论

轻松学,细说 Dagger2 中容易让人迷惑的问题

$
0
0

Dagger2 确实比较难学,我想每个开发者学习的时候总是经历了一番痛苦的挣扎过程,于是就有了所谓的从入门到放弃之类的玩笑,当然不排除基础好的同学能够一眼看穿。本文的目的尝试用比较容易理解的角度去解释 Dagger2 这样东西。

Dagger2 是有门槛的,这样不同水平能力的开发者去学习这一块的时候,感受到的压力是不一样的。

我个人总结了大家在学习 Dagger2 时,为什么感觉难于理解的一些原因。

  1. 对于 Java 注解内容不熟悉。
  2. 对于依赖注入手段不熟悉。
  3. 对于 Java 反射不熟悉。
  4. 对于 Dagger2 与其它开源库的使用方法的不同之处,没有一个感性的认知。
  5. 对于 Dagger2 中极个别的概念理解不够。
  6. 对于 Dagger2 的用途与意义心生迷惑。

其实以上几点,都可以归类到基础技能不扎实这个范畴内,但正如我所说的,学习 Dagger2 时开发者的水平是不一样的,所以困扰他们的原因就不一样。下面,我针对这些情况,一一给出自己的建议。

对于 Java 注解不熟悉

这一部分的开发者基础知识确实薄弱,那么怎么办呢?当然是学习了。就算不为 Dagger2,注解的知识内容也应该好好值得学习,虽然在平常开发中,我们自己编写注解的机会很少,但是我们运用第三方开源库的时候,应该会经常看见注解的身影,所以熟悉注解不是为了自己编写注解代码,而是为了开发过程中更加高效从容而已。

如果,对 Java 注解一无所知,我可以给大家一个感性的认知。

一般,我们评价某人会说,这是一个好人、坏人、男神、女神、大神、单身狗等等,这是我们人为贴得标签,这些标签有助于我们自己或者其他人去获取被评价的人的基本信息。

而在 Java 软件开发中,我们也可以给某些类,某些字段贴上作用类似的标签,这种标签的名字就叫做注解,只不过这种标签是给代码看的。

这里写图片描述

标签只对特定的人起作用,比如小张被人贴了一个小气鬼的标签,所以小红认为小张是一个小气鬼,但是小张本人不会因为这个标签而改变自己变得不是小张,也许本质上小张是个大方的人。

所以,注解本身也不会影响代码本身的运行,它只会针对特定的代码起到一定的用处,用来处理注解的代码被称作 APT(Annotation Processing Tool)。

更详细的内容请阅读这篇文章《秒懂,Java 注解 (Annotation)你可以这样学》

对依赖注入手段不熟悉

这一块而言,如果让很多人慌张的原因,我觉得可能是依赖注入这个词过于学术化了。而从小到大,10 多年的应试教育让绝大部分的同学对于这些枯燥无味的概念产生了恐惧与绝望。其实,没有那么夸张的,不要被这些东西吓倒。

因为,Java 学习的时候,我们一直写这样的代码。

class B{}


class A {
    B b;

    public A() {
        b = new B();
    }
}

这样的代码,一点问题都没有,类 A 中有一个成员变量 b,b 的类型是类 B。所以,在软件开发中,可以称 A 依赖 B,B 是 A 的依赖,显然,A 可以依赖很多东西,B 也可以依赖很多东西。

通俗地讲,依赖这个概念也没有什么神奇的,只是描述了一种需求关系。

我们再来看一种情况,现在,业务需要,代码越来越复杂。

class B{}

class C{
    int d;
    public C (int value) {
        this.d = value;
    }
}


class A {
    B b;
    C c;

    public A() {
        b = new B();
        c = new C(3);
    }
}

现在,A 有了一个新的依赖 C。不过,由于业务的演进,C 这个类经常发生变化,最明显的变化就是它的构造方法经常变动。

class C{
    int d;
    String e;
    public C (String value) {
        this.e = value;
    }
}


class A {
    B b;
    C c;

    public A() {
        b = new B();
        //c = new C(3);
        c = new C("hello");
    }
}

C 变动的时候,由于 A 依赖于它,A 不得不修改自己的代码。但是,事情还没有完。C 还会变动,C 把 B 也带坏了节奏。

class B{
    int value;

    public B(int value) {
        this.value = value;
    }

}

class C{
    int d;
    String e;
    public C (int index,String value) {
        this.d = index;
        this.e = value;
    }
}


class A {
    B b;
    C c;

    public A() {
        b = new B(110);
//      b = new B();
        //c = new C(3);
//      c = new C("hello");
        c = new C(12,"hello");
    }
}

可以想像的是,只要 B 或者 C 变动一次,A 就可能需要修改自己的代码,用专业术语描绘就是A 与依赖模块太过于耦合,这个可是犯了软件设计的大罪,

我们再可以想像一下,A 是领导,B 和 C 是小兵,如果因为 B 和 C 自身的原因,导致领导 A 一次次地改变自己,那么以现在流行的话来说就是,“你良心不会痛吗?”。所以我们需要的就是进行一些变化来进行解耦,也就是解除这种耦合的关系。让 A 不再关心 B 和 C 的变化,而只要关心自身就好了。

class A {
    B b;
    C c;

    public A(B b, C c) {
        this.b = b;
        this.c = c;
    }

}  

在上面代码中,A 不再直接创建 B 与 C,它把依赖的实例的权力移交到了外部,所以无论 B 和 C 怎么变化,都不再影响 A 了。这种实例化依赖的权力移交模式被称为控制反转(IoC),而这种通过将依赖从构造方法中传入的手段就是被传的神乎其乎的依赖注入(DI)。其实,本质上也没有什么神奇的地方,只是起了一个高大上的名字而已,好比东北的马丽,在国际化上的大舞台,宣称自己是来自神秘东方的 Marry 一样。

依赖注入有 3 种表现形式。
构造方法注入

class A {
    B b;
    C c;

    public A(B b, C c) {
        this.b = b;
        this.c = c;
    }

} 

Setter 注入

class A {
    B b;
    C c;


    public void setB(B b) {
        this.b = b;
    }


    public void setC(C c) {
        this.c = c;
    }

}

接口注入

interface Setter {
    void setB(B b);
    void setC(C c);
}


class A implements Setter{
    B b;
    C c;

    public void setB(B b) {
        this.b = b;
    }


    public void setC(C c) {
        this.c = c;
    }

}

大家肯定会想,依赖注入的引进,使得需求方不需要实例化依赖,但总得有地方去实例化这些依赖啊。确实,依赖注入引进了第三方,你可以称它为 IoC 容器,也可以称它为注射器(injector),为了便于理解,我们之后都有注射器来指代吧,通过注射器可以将依赖以上面 3 种注入方式之一注入到需求方。

这里写图片描述

病人需要的是药水,所以病人是需求者,药水是病人的依赖,注射器把药水注射给病人。

更多细节,请阅读《轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)》

而 Dagger2 就是一个依赖注入框架,你也可以想像它是一位非常智能化的服务员,用来处理大量的顾客的各种订餐需求,然后针对不同的菜单提供给不同的顾客不同类型的餐具。

对于 Java 反射不熟悉

对于这一块不熟悉的同学同样是基础知识太薄弱,需要补强。

相对于正常流程开发,Java 反射是非常规化手段。如果正常流程开发是司机驾驶一辆汽车,那么反射的运用就是采用无人驾驶的手段。

Dagger2 中也应用了反射,不过开发者本身不需要运用反射,Dagger2 是自身框架通过反射处理注解。

学习反射内容可以阅读这篇文章《细说反射,Java 和 Android 开发者必须跨越的坎》

Dagger2 与其它开源库略有不同

开源软件的出现,大大造福了程序员,所以,大家都说不要重复创造轮子

但是,我个人一直认为,不重复创造轮子,不代表可以不去深入了解这些轮子。

我把 Android 开发中所应用到的开源库当作武装

武装与两部分构成,武器装备

那么,在 Android 中什么样的库可以当作是武器呢?什么样的库可以当作是装备呢?

大家想一下,武器什么用途?战斗进行中,用来杀敌的

装备呢?战斗开始时,就要穿上或者安装好的物件。
这里写图片描述

刀、枪、棍、棒是武器,盔甲是装备。
武器拿来就用,盔甲等却要在开始战斗前就装备上。

Java 软件代码是在虚拟机中运行的,所以在这里可以把 jvm 当作战场。

Piccso、Logger、sweet-alert-dialog 等等,这些开源库都是在程序运行过程中拿来就用的。

而 GreenDao、Butterknife、Dagger2 这些因为涉及到了反射处理,而反射处理相对于正常开发速度很慢,所以它们通常在编译时产生一些新的代码,然后才能在程序运行过程中使用,也就是说它们都把反射处理移动到编译器编译代码时的阶段,而程序运行时并不涉及到反射,这就是这些框架运用了反射技术,但是仍然高效的秘诀所在。

所以,Dagger2 会产生中间代码,不少同学应该会有迷惑,为什么引进了 Dagger2 时,要先编译一次代码,不然就会报错。现在,可以解释了,编译代码是为了生成中间代码,然后在中间代码的基础上按照正常的流程开发。

Dagger2 并非横空出世

都说要站在巨人的肩膀上,Dagger2 其实也算站在巨人的肩膀上。

Dagger2 是一款依赖注入的框架,但依赖注入的框架有 ,所以 Dagger2 也并不算是一款新鲜事物,大家觉得新奇不过是因为对于依赖注入框架本身了解过少罢了。

Dagger2 是在 Dagger 的基础上来的,Dagger 是由 Square 公司开发的,Dagger2 基于 Dagger 由 Google 公司开发并维护。
Square 是一家伟大的公司,Android 大神 JakeWoton 之前就在它任职,不久前才离职。而我们熟悉的 RxJava、Butterknife、Retrofit、OKHttp 等等都是 Square 提供的,外号 Square 全家桶。
这里写图片描述

当然,Google 公司更是一家伟大的公司,这个无需多言。

最后,有个重要的地方就是 Dagger2 是基于注解开发的,而 Dagger2 中所涉及到的注解其实是基于 javax.inject 上开发的,它出自 JSR330
这里写图片描述

JSR330 是规范,建议大家怎么做,而 Dagger2 则实现了这个规范。

因此,对于普通开发者而言,学习 Dagger2 其实就是学习相关的注解的意义与用途。

Dagger2 的引进

Dagger2 是适应于 Java 和 Android 开发的依赖注入框架,记住得是它不仅仅对 Android 开发有效。

Dagger2 官网地址是 https://google.github.io/dagger//

对于 Eclipse 开发而言,需要下载相应的 jar 包。

对于 AndroidStudio 开发而言,只需要在相应的 build.gradle 引入对应的依赖就好了。

如果你 AndroidStudio 的 gradle build tool 版本在 2.2 以上,直接在引进就好了

dependencies {
  compile 'com.google.dagger:dagger:2.4'
  annotationProcessor 'com.google.dagger:dagger-compiler:2.4'
}

如果你的 gradle build tool 版本在 2.2 以下,则需要引进 apt 插件。
首先需要在 Project 层级的 build.gradle 文件中引入依赖

buildscript {
    repositories {
      mavenCentral()
    }
    dependencies {
        // replace with the current version of the Android plugin
        classpath 'com.android.tools.build:gradle:2.1.0'
        // the latest version of the android-apt plugin
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

然后在 Module 层级的 build.gradle 引入相应的插件和依赖

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


dependencies {
     apt 'com.squareup.dagger:dagger-compiler:2.4'
     compile 'com.squareup.dagger:dagger:2.4'
     //java注解
     compile 'org.glassfish:javax.annotation:10.0-b28'
}

Dagger2 的基本概念

前面讲到过 Dagger2 基于 JSR330 注解,在普通开发者视角中,就是这些注解构成了 Dagger2 的全部。

前面文章我提到过,注解如同标签,给一个人贴标签有助于自己去理解这个人,而给代码贴标签,有助于 APT 程序去处理相应的代码,Dagger2 有自己的注解,而这些注解也有特定的意义,它们大体上都是为了实现依赖注入。

我们说依赖注入有 3 种手段:
- 构造方法注入
- Setter 注入
- 接口注入

但是,如果不借助于框架的话,我们就必须自己编写相应的代码,这些代码充当了注射器的角色。

B b = new B(5);
C c = new C(110,"110");

A a = new A();
a.setB(b);
a.setC(c);

A 将内部的依赖 B 和 C 的实例化的权力移交到了外部,通过外部的注入。

Dagger2 这类依赖注入框架的出现进一步解放了我们的双手,Dagger2 有一套自己的依赖注入机制,我们不再手动编写注射器,而只要按照规则配置好相应的代码就好了,Dagger2 会自动帮我们生成注射器,然后在适当的时候进行依赖注入。

什么意思呢?意思就是我们不需要调用 a.setB() 和 a.setC() 方法,只需对代码添加一些注解就好了。

class B{
    int value;
    @Inject
    public B(int value) {
        this.value = value;
    }

}

class C{
    int d;
    String e;
    @Inject
    public C (int index,String value) {
        this.d = index;
        this.e = value;
    }
}

class A {
    @Inject
    B b;
    @Inject
    C c;
}

看起来,不可思议,不是吗?@Inject 是一个注解,只要按照 Dagger2 的配置,就能颠覆我们之前的编码习惯。

但不管看起来怎么神奇,任何事都有一个本质。

因此这个本质就是,Dagger2 是一个依赖注入框架,依赖注入的目的就是为了给需求方在合适的时候注入依赖。

对 Dagger2 学习过程如果感到不适与难以理解,回过头来想想它的本质好了。

这里写图片描述
Dagger2 的使命就是为了给需求者注射依赖。

@Inject 注解就如同一个标签,或者说它是一个记号,它是给 Dagger2 看的。它运用的地方有两处。

  1. @Inject 给一个类的相应的属性做标记时,说明了它是一个依赖需求方,需要一些依赖。

  2. @Inject 给一个类的构造方法进行注解时,表明了它能提供依赖的能力。

就这样,通过 @Inject 注解符号,就很容易标记依赖和它的需求方。但是,单单一个 @Inject 是不能让 Dagger2 正常运行的。还需要另外一个注解配合。这个注解就是 @Component。

而 @Component 相当于联系纽带,将 @inject 标记的需求方和依赖绑定起来,并建立了联系,而 Dagger2 在编译代码时会依靠这种关系来进行对应的依赖注入。

@Inject 和 @Component

我们来编写代码,验证一下。

假设有这么一个场景:

一个宅男,他喜欢在家玩游戏,所以饿了的时候,他不想自己煮饭吃,也不愿意下楼去餐厅,他选择了外卖。

public class ZhaiNan {

    @Inject
    Baozi baozi;

    @Inject
    Noodle noodle;

    @Inject
    public ZhaiNan() {

    }

    public String eat() {
        StringBuilder sb = new StringBuilder();
        sb.append("我吃的是 ");
        if ( baozi != null ) {
            sb.append(baozi.toString());
        }

        if (noodle != null) {
            sb.append("  ");
            sb.append(noodle.toString());
        }
        return sb.toString();
    }
}

public class Baozi {

    @Inject
    public Baozi() {
    }

    @Override
    public String toString() {
        return "小笼包";
    }
}

public class Noodle {

    @Inject
    public Noodle() {
    }

    @Override
    public String toString() {
        return "面条";
    }
}

上面代码可以看到,@Inject 注解的身影,需求方是 ZhaiNan 这个类,而 Baozi 和 Noodle 是它的依赖。前面说过,光有 @Inject 的话还不行,需要 @Component 配合。

@Component 怎么使用呢?

很简单,它只需要注解在一个接口上就好了。

@Component()
public interface Platform {
    ZhaiNan waimai();
}

Platform 是一个接口,它代表着外卖平台,它内部有一个 waimai() 的方法,返回 ZhaiNan 的类型。

这个接口特别的地方就是它的方法中的返回类型。如果一个方法返回了一个类型,那么其实也算是一种依赖的提供,我们可以在后续的代码中感受。

既然是接口,那么它就需要实现类,但是 Dagger2 会自动帮我们生成一个实现类,前提是使用这个类的时候,要先对工程进行编译。前面用装备解释过 Dagger2 这种类型的库,它会在编译阶段产生中间代码,这些中间代码就包括自动实现了被 @Component 注解过的接口实现类。

所以,我们如果要使用 Dagger2 为了我们自动生成的类时,我们就应该先 Build->Make Project 编译一次代码。生成的代码位置在 app 模块 build 文件夹中,在 AndroidStudio 切换 Project 视角就可以看到。
这里写图片描述

这个目录下都是 Dagger2 产生的中间产物,DaggerPlatform 就是 Dagger2 为我们自动实现的 Platform 这个接口的实现类,注意它的名字都是 Dagger+接口名称

有了 DaggerPlatform,我们就能够使用 Dagger2 进行代码的依赖注入了。

public class MainActivity extends AppCompatActivity {

    Button mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.btn_test);

        final ZhaiNan zainan = DaggerPlatform.builder()
                .build()
                .waimai();

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this,zainan.eat(),Toast.LENGTH_LONG).show();
            }
        });

    }
}

然后,测试效果是:
这里写图片描述

需要注意的地方是,Component 的实现类是由 Dagger2 自动生成的,它的名字前面说了是 Dagger+接口名称。但这是通常情况,因为 @Component 注解的都是顶级类。但还有一种情况是。

class Foo {
  static class Bar {
    @Component
    interface BazComponent {}
  }
}

它只是一个内部类的接口,Dagger2 针对这种情况需要把外部的类的名字加下划线的形式拼接起来,所以上例中 Dagger2 生成的 Component 实现类类名是 DaggerFoo_Bar_BazComponent。

我们并没有在任何地方用 new 关键字亲自创建 ZhaiNan 这个类的实例,但是它确实有效,而且它的内部依赖 Baozi 和 Noodle 都被实例化了,也就是说依赖被正确地注入到了 ZhaiNan 的实例对象当中。

所以,@Component 和 @Inject 的配合就能够使用 Dagger2 了,但这里面存在一个局限,@Inject 只能标记在我们自己编写的类的构造方法中,如果我们使用第三方的库或者标准库的话,是不是代表我们对于这些就无能为力了呢?

答案显然是否定的,Dagger2 作为一款优秀的框架必须考虑到开发过程中的方方面面,不然谈何优秀呢?

Dagger2 为了能够对第三方库中的类进行依赖注入,提供了 @Provides 和 @Module 两个注解。

@Provides 和 @Module

Provide 本身的字面意思就是提供,显然在 Dagger2 中它的作用就是提供依赖。
Module 是模块的意思,Dagger2 中规定,用 @Provides 注解的依赖必须存在一个用 @Module 注解的类中。

@Module
public class ShangjiaAModule {
    @Provides
     public Baozi provideBaozi() {
        return new Baozi("豆沙包");
    }
    @Provides
     public Noodle provideNoodle() {
        return new Noodle();
    }
}

public class Baozi {

    String name;

    @Inject
    public Baozi() {
        name = "小笼包";
    }

    public Baozi(String name) {
        this.name = name;
    }

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

值得注意的地方有
- @Provides 修饰的方法一般用 provide 作用方法名前缀。
- @Module 修饰的类一般用 Module 作为后缀。

前面有讲过,@Component 是依赖双方的联系纽带,现在多了一个 @Module 注解,怎么配合使用呢?方法,很简单。只要在 @component 注解后面的括号中取值就是。

@Component(modules = ShangjiaAModule.class)
public interface WaimaiPingTai {
    ZhaiNan waimai();
}

然后编写测试代码

mBtnTestModule = (Button) findViewById(R.id.btn_test_module);

final ZhaiNan zainan1 = DaggerWaimaiPingTai.builder()
        .build()
        .waimai();

mBtnTestModule.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(MainActivity.this,zainan1.eat(),Toast.LENGTH_LONG).show();
    }
});

演示效果如下:
这里写图片描述

我们再看看 @Provides 用法。

@Module
public class ShangjiaAModule {
    @Provides
     public Baozi provideBaozi() {
        return new Baozi("豆沙包");
    }
    @Provides
     public Noodle provideNoodle() {
        return new Noodle();
    }
}

@Provides 注解的方法中直接用 new 创建了依赖,其实还有另外一种方式。我们先对 Noodle 进行重构,让它作为面条的基类,然后编写它一个继承类。

public  class Noodle {
    @Inject
    public Noodle() {
    }
}

public class Tongyi extends Noodle{

    @Inject
    public Tongyi() {
    }

    @Override
    public String toString() {
        return "统一方便面";
    }
}

ZhanNan 这个类不用改变,然后,用另外一种方式编写 @Provides 注解的方法。

@Module
public class ShangjiaAModule {
    @Provides
     public Baozi provideBaozi() {
        return new Baozi("豆沙包");
    }
    @Provides
     public Noodle provideNoodle(Tongyi noodle) {
        return noodle;
    }
}

测试代码也不需要更改,演示效果如下:
这里写图片描述
那么,两种方式有什么区别呢?

@Provides
public Noodle provideNoodle(Tongyi noodle) {
    return noodle;
}

@Provides
public Noodle provideNoodle(Tongyi noodle) {
    return noodle;
}

什么时候用 new 关键字?什么时候直接返回传入进来的参数?
我们不妨再创建一个类 Kangshifu,同样继承自 Noodle 这个基类。

public class Kangshifu extends Noodle{

    public Kangshifu() {
    }

    @Override
    public String toString() {
        return "康师傅方便面";
    }
}

与 Tongyi 这个类不同的地方是,它并没有用 @Inject 注解构造方法。
我们再尝试更改 @Provides 注解的相应方法。

@Module
public class ShangjiaAModule {
    @Provides
     public Baozi provideBaozi() {
        return new Baozi("豆沙包");
    }
    @Provides
     public Noodle provideNoodle(Kangshifu noodle) {
        return noodle;
    }
//    @Provides
//     public Noodle provideNoodle(Tongyi noodle) {
//        return noodle;
//    }
}

再进行编译的时候,会发现 IDE 报错了。

Error:(10, 13) 错误: com.frank.dagger2demo.Kangshifu cannot be provided without an @Inject constructor or from an @Provides-annotated method.
com.frank.dagger2demo.Kangshifu is injected at
com.frank.dagger2demo.ShangjiaAModule.provideNoodle(noodle)
com.frank.dagger2demo.Noodle is injected at
com.frank.dagger2demo.ZhaiNan.noodle
com.frank.dagger2demo.ZhaiNan is provided at
com.frank.dagger2demo.WaimaiPingTai.waimai()

Log 提示的错误信息是 Kangshifu 这个类代码中没有被 @Inject 注解过的构造方法,也没有办法从一个被 @Provides 注解过的方法中获取。

所以,什么时候用 new 创建对象,什么时候可以直接返回传入的参数就很明显了。对于被 @Inject 注解过构造方法或者在一个 Module 中的被 @Provides 注解的方法提供了依赖时,就可以直接返回传入的参数,而第三方的库或者 SDK 自带的类就必须手动创建了。

@Module
public class ShangjiaAModule {
    @Provides
     public Baozi provideBaozi() {
        return new Baozi("豆沙包");
    }


    @Provides
     public Kangshifu provideKangshifu() {
        return new Kangshifu();
    }
//    @Provides
//     public Noodle provideNoodle(Tongyi noodle) {
//        return noodle;
//    }
}

这段代码,是可以正常运行的。
这里写图片描述

现在,我有一个新的需求更改。我在 ZhaiNan 类中 eat() 方法中要把餐厅名字打印出来。

public class ZhaiNan {

    @Inject
    Baozi baozi;

    @Inject
    Noodle noodle;

    @Inject
    public ZhaiNan() {

    }

    @Inject
    String resturant;

    public String eat() {
        StringBuilder sb = new StringBuilder();
        sb.append("我从 ");
        sb.append(resturant.toString());
        sb.append("订的外卖,");
        sb.append("我吃的是 ");
        if ( baozi != null ) {
            sb.append(baozi.toString());
        }

        if (noodle != null) {
            sb.append("  ");
            sb.append(noodle.toString());
        }
        return sb.toString();
    }
}

注意的是,我要新增了一个 String 类型的字段 resturant 来代表餐厅,并且用 @Inject 注解它。
于是,相应的 Module 也要更改。

@Module
public class ShangjiaAModule {
    @Provides
     public Baozi provideBaozi() {
        return new Baozi("豆沙包");
    }
    @Provides
     public Noodle provideNoodle(Kangshifu noodle) {
        return noodle;
    }

    @Provides
     public Kangshifu provideKangshifu() {
        return new Kangshifu();
    }

    @Provides
     public String provideResturant() {
        return "王小二包子店";
    }
//    @Provides
//     public Noodle provideNoodle(Tongyi noodle) {
//        return noodle;
//    }
}

然后,再编译测试。
这次的编译报错了。

Error:(10, 13) 错误: java.lang.String cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method.
java.lang.String is injected at
com.frank.dagger2demo.ZhaiNan.resturant
com.frank.dagger2demo.ZhaiNan is provided at
com.frank.dagger2demo.Platform.waimai()

报错的原因是之前编写的接口 Platform 没有办法获取 ZhanNan 中 resturant 的依赖。因为之前并没有为 Platform 指定 Module。
那么现在我们就为它指定 ShangjiaAModule 吧。

编译后,进行测试,程序正常运行。
这里写图片描述

现在,我把代码再重构。在 ShangjiaAModule 中提供餐厅名字的时候,直接返回了“王小二包子店”,这个过于直接,缺少变动,现在针对这个进行变化。

@Module
public class ShangjiaAModule {

    String restaurant;

    public ShangjiaAModule(String restaurant) {
        this.restaurant = restaurant;
    }

    ......

    @Provides
     public String provideResturant() {
        return restaurant;
    }

}

restaurant 在 ShangjiaAModule 创建的时候被赋值,但我们之前的代码好像并没有处理 ShangjiaAModule 的创建,那么它如何创建呢?
我们先尝试重新编译代码并运行。

编译没有出错,但运行的时候出错了。

Caused by: java.lang.IllegalStateException: com.frank.dagger2demo.ShangjiaAModule must be set

at com.frank.dagger2demo.DaggerPlatform$Builder.build(DaggerPlatform.java:64)
at com.frank.dagger2demo.MainActivity.onCreate(MainActivity.java:21)

Log 提示的是调用 DaggerPlatform中的Builder.build() 方法时出错了,因为 ShangjiaAModule 并没有出错。我们定位代码。

final ZhaiNan zainan = DaggerPlatform.builder()
                .build()
                .waimai();

我之前没有讲的是,如果一个 Module 没有实现任何构造方法,那么在 Component 中 Dagger2 会自动创建,如果这个 Module 实现了有参的构造方法,那么它需要在 Component 构建的时候手动传递进去。怎么传呢?Component 中生成的 Builder 构造器有与 Module 名字相同的方法,并且参数类型就是 Module 类型。大家细细体会下面代码就明白了。

final ZhaiNan zainan = DaggerPlatform.builder()
                .shangjiaAModule(new ShangjiaAModule("王小二包子店"))
                .build()
                .waimai();


final ZhaiNan zainan1 = DaggerWaimaiPingTai.builder()
                .shangjiaAModule(new ShangjiaAModule("衡阳鱼粉店"))
                .build()
                .waimai();

再编译运行。
这里写图片描述

另外,还有一种特殊情况就是,像在 Android 中,MainActivity 这样的代码是我们自己编写的,所以我们可以给相应的属性添加 @Inject 注解,但是 MainActivity 对象的创建却是由 Android Framework 框架决定的,那么,Dagger2 有没有针对这种内部拥有 @Inject 标注的属性,但还没有进行依赖绑定的类的对象进行依赖注入呢?答案是肯定的。

我们知道,Component 是一个接口,它里面可以定义很多方法。方法的返回值可以提供一种类型的对象,前提是这个类的对象被 @Inject 注解过构造方法或者在 Module 中被 @Provides 注解过的方法提供。

@Component(modules = ShangjiaAModule.class)
public interface WaimaiPingTai {
    ZhaiNan waimai();
}

ZhaiNan 能够在一个 Component 中的方法中作为类型返回是因为它符合我上面说的条件,它的构造方法被 @Inject 注解过。

需要注意的是,Component 中方法除了可以返回类型,还可以在方法中传入类型参数。目的是针对这个参数对象进行依赖注入。
比如

@Component(modules = ShangjiaAModule.class)
public interface WaimaiPingTai {
    ZhaiNan waimai();

    void zhuru(ZhaiNan zhaiNan);
}

我新增了一个方法,zhuru() 中的参数就是 ZhaiNan 类型,代表 DaggerWaimaiPingTai 调用这个方法时能够对一个 ZhaiNan 对象进行依赖注入。

可以编写代码验证。

mBtnTestZhuru = (Button) findViewById(R.id.btn_test_zhuru);
final ZhaiNan zhaiNan = new ZhaiNan();
WaimaiPingTai daggerWaimaiPingTai = DaggerWaimaiPingTai.builder()
        .shangjiaAModule(new ShangjiaAModule("常德津市牛肉粉"))
        .build();
// 通过调用接口中的方法给 zhaiNan 进行依赖注入
daggerWaimaiPingTai.zhuru(zhaiNan);

mBtnTestZhuru.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(MainActivity.this,zhaiNan.eat(),Toast.LENGTH_LONG).show();
    }
});

运行结果如下:
这里写图片描述

所以,我们可以给接口方法参数传值的形式来给 Activity 进行依赖注入。

@Module
public class ActivityModule {

    @Provides
    public int provideActivityTest(){
        return 1234567890;
    }
}


@Component(modules = {ShangjiaAModule.class,ActivityModule.class})
public interface WaimaiPingTai {
    ZhaiNan waimai();

    void zhuru(ZhaiNan zhaiNan);

    void inject(MainActivity mainActivity);
}  

我们编写了新的 Module,然后把它放时 WaimaiPingTai 这个 Component 中去,再添加了 inject() 方法,为的是能够给 MainActivity 实例进行依赖注入。

现在我们添加测试代码,首先在 MainActivity 中添加一个 int 类型的成员变量。

@Inject
int testvalue;

然后要调用相关注入方法

mBtnTestActivity = (Button) findViewById(R.id.btn_test_inject_act);
final ZhaiNan zhaiNan = new ZhaiNan();
WaimaiPingTai daggerWaimaiPingTai = DaggerWaimaiPingTai.builder()
        .shangjiaAModule(new ShangjiaAModule("常德津市牛肉粉"))
        .build();

daggerWaimaiPingTai.inject(this);

mBtnTestActivity.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(MainActivity.this,"testvalue is "+ testvalue,Toast.LENGTH_LONG).show();
    }
});

运行结果:
这里写图片描述

Component 的创建方式

我们可以看到,创建 Component 都是通过它的 Builder 这个类来进行构建的。其实还有另外一种方式。那就是直接调用 Component 实现类的 create() 方法。

public class Test {}

@Component(modules = TestCreate.class)
public interface TestCreateComponent {
    Test ceshi();
}

TestCreateComponent testCreateComponent = DaggerTestCreateComponent.create();
Test test = testCreateComponent.ceshi();

上面代码中创建 TestCreateComponent 并没有借助于 Builder,而是直接调用了 DaggerTestCreateComponent 的 create() 方法,但是它有一个前提,这个前提就是 Component 中的 module 中被 @Provides 注解的方法都必须是静态方法,也就是它们必须都被 static 修饰。

@Module
public class TestCreate {

    @Provides
    public static int provideTest1() {
        return 1;
    }

    @Provides
    public static String provideTest2() {
        return "test component create()";
    }

    @Provides
    public static Test provideTest(){
        return new Test();
    }
}

因为不需要创建 Module 对象实例,所以 Builder 自然就可以省去了。

@Inject 和 @Provides 的优先级

可能有心思细腻的同学会问,同样是提供依赖,如果一个类被 @Inject 注解了构造方法,又在某个 Module 中的 @Provides 注解的方法中提供了依赖,那么最终 Dagger2 采用的是哪一个?

public class Baozi {

    String name;

    @Inject
    public Baozi() {
        name = "小笼包";
    }

    public Baozi(String name) {
        this.name = name;
    }

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

@Module
public class ShangjiaAModule {

    String restaurant;

    public ShangjiaAModule(String restaurant) {
        this.restaurant = restaurant;
    }

    @Provides
     public Baozi provideBaozi() {
        return new Baozi("豆沙包");
    }

}

Baozi 这个类就符合我上面给的情景,一方面它确实拥有被 @Inject 注解过的构造方法,另一方面在 Module 中它又通过 @Provides 提供了依赖。那么,最终,Dagger2 采取了哪一种呢?

答案是 Module,其实现象我们在之前的测试时已经可以观察到了,最终屏幕显示的是豆沙包选项。

Dagger2 依赖查找的顺序是先查找 Module 内所有的 @Provides 提供的依赖,如果查找不到再去查找 @Inject 提供的依赖。

到这里,我们讲解了 Dagger2 中最常见的 4 个注解:@Inject、@Component、@Module、@Provides。

正常情况下,这 4 个注解能够很好的完成一般的代码开发了。但是,这都是基础功能,Dagger2 提供了更多的一些特性。

Dagger2 中的单例 @Singleton

我们在平常开发中经常要涉及到各种单例。比如在 Android 中开发,数据库访问最好要设计一个单例,网络访问控制最好设计一个单例。我们经常编写这样的代码。

public class DBManager {

    private static DBManager instance;

    private DBManager() {
    }

    public static DBManager getInstance() {
        if ( instance == null ) {
            synchronized ( DBManager.class ) {
                if ( instance == null ) {
                    instance = new DBManager();
                }
            }
        }

        return instance;
    }
}

这种代码手段千遍一律,而 Dagger2 提供了另外一种可能。那就是利用 @Singleton 注解解决它。@Singleton 怎么使用呢?我们用代码来说明。

@Singleton
public class TestSingleton {

    @Inject
    public TestSingleton() {
    }
}

@Singleton
@Component
public interface ActivityComponent {
    void inject(SecondActivity activity);
}

用 @Singleton 标注在目标单例上,然后用 @Singleton 标注在 Component 对象上。

编写测试代码

public class SecondActivity extends AppCompatActivity {
    @Inject
    TestSingleton testSingleton1;
    @Inject
    TestSingleton testSingleton2;

    Button mBtnTestSingleton;

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

        mBtnTestSingleton = (Button) findViewById(R.id.btn_test_singleton);

        DaggerActivityComponent.builder()
                .build()
                .inject(this);

        mBtnTestSingleton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(SecondActivity.this,"test1 hashcode:"+testSingleton1.toString()
                    +" test2 hashcode:"+testSingleton2.toString(),Toast.LENGTH_LONG).show();
            }
        });

    }
}

编译后,测试结果往下:
这里写图片描述

可以看到,两个对象的 hashcode 是一样的,说明 TestSingleton 这个类实现了单例。
另外,如果要以 @Provides 方式提供单例的话,需要用 @Singleton 注解依赖提供的方法。如

@Module
public class SecondActivityModule {

    @Provides
    @Singleton
    public TestSingleton provideTestSingleton(){
        return new TestSingleton();
    }
}

@Singleton 引出 @Scope

我们在上一节的内容可以看到,通过 @Singleton 注解就可以实现一个单例了。本节的目标就是深入分析一下 @Singleton。

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

@Singleton 是一个注解,但是它被一个元注解 @Scope 注解了,所以,可以猜测到的是 @Scope 是真正厉害的角色。而实际上 @Singleton 只是 @Scope 一个默认的实现而已,但是因为它更具可读性,能够让开发者一眼就明白它的作用是为了单例。但是,单例也是有范围限制的。

分析 @Singleton 其实就等同于分析 @Scope 。Scope 的字面意思是作用域,也就是表达一种能力的范围。那么在 Dagger2 中它表达了一种什么样的能力范围呢?

大家有没有想过,为什么要用 @Singleton 同时标注 @Provides 和 @Component ?

文章一开始就讲过,Component 是联系需求与依赖的纽带,所以用 @Singleton 确定的单例作用域应该也是在 Component 的范围内。也就是说 @Scope 的作用范围其实就是单例能力范围,这个范围在单个的 Component 中。

在上面的代码中,MainActivity 和 SecondActivity 运用了不同的 Component 现在我们可以测试一下它们所获取的 TestSingleton 会不会是同一个对象。

public class MainActivity extends AppCompatActivity {

    @Inject
    public  TestSingleton testSingleton;

    onCreate() {

        mBtnJumpToSecond.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                startActivity(intent);

                Toast.makeText(MainActivity.this,"testsingleton is "+ testSingleton,Toast.LENGTH_LONG).show();
            }
        });
    }

}

@Module
public class ActivityModule {

    @Provides
    public int provideActivityTest(){
        return 1234567890;
    }

    @Provides
    @Singleton
    public TestSingleton provideSingleton(){
        return new TestSingleton();
    }
}

@Singleton
@Component(modules = {ShangjiaAModule.class,ActivityModule.class})
public interface WaimaiPingTai {
    ZhaiNan waimai();

    void zhuru(ZhaiNan zhaiNan);

    void inject(MainActivity mainActivity);
}

我们改写了 MainActivity 和它涉及到的 Module、Component。
现在,我们检测 MainActivity 和 SecondActivity 中的 TestSingleton 是不是同一个。
这里写图片描述
可以发现,它们并不是同一个。也就是说 @Singleton 所拥有的单例能力是以 Component 为范围的限定的。

@Singleton 起作用是因为它被 @Scope 注解,所以,如果可能,我们也可以自己定义 Scope。

@Scope
@Documented
@Retention(RUNTIME)
public @interface PageScope {}

我自己定义一个 @PageScope 注解,我的想法是一个 Activity 有不同的 Fragment,所以以 @PageScope 标注的依赖对象在这些 Fragment 之间是同一个对象,也就是说在这个 Activity 中实现了单例。而在另外一个 Activity 中因为采取了不同的 Component 对象,所以它们的 Fragment 也共用了同一个依赖对象,但是两个 Activity 中各自的依赖确不是同一个对象。

大家细细体会。

@Qualifiers 和 @Name

Qualifiers 是修饰符的意思,那么它修饰的是什么呢?不知道大家有没有察觉到,前面的演示代码其实很简单,经不起太多推敲。

在一个 Module 中 @Provides 提供的依赖是由返回值决定的。这样就会出现问题,同一种类型不同实例,怎么去区别?比如

public class SecondActivity extends AppCompatActivity {


    @Inject
    String phone;

    @Inject
    String computer;

}

phone 和 computer 应该要对应不同的字符串。但是,我们该如何在 Module 中进行编码呢?

@Module
public class SecondActivityModule {

    @Provides
    @Singleton
    public TestSingleton provideTestSingleton(){
        return new TestSingleton();
    }

    @Provides
    public String providePhone() {
        return "手机";
    }

    @Provides
    public String providePhone() {
        return "电脑";
    }

}

大家可能会想到这样编码,但是这样的代码根本编译不过。因为 Dagger2 是根据返回的类型来进行依赖关系确定的。如果存在两个方法返回一样的类型,那么正常情况下 Dagger2 显然就没有办法处理了。

不过,Dagger2 给出了解决方案。用 @Name 注解就好了,配合 @Inject 和 @Provides 一起使用。例如

 @Inject
@Named("phone")
String phone;

@Inject
@Named("computer")
String computer;

@Module
public class SecondActivityModule {

    @Provides
    @Singleton
    public TestSingleton provideTestSingleton(){
        return new TestSingleton();
    }

    @Provides
    @Named("phone")
    public String providePhone() {
        return "手机";
    }

    @Provides
    @Named("computer")
    public String provideComputer() {
        return "电脑";
    }

}

当然,如果你嫌每次给 @Name 麻烦,你可以自定义注解。

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

@Name 只是被 @Qualifier 注解的一个注解。所以,它能够有效完全是因为 @Qualifier。

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Phone {
}


@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Computer {
}

通过 @Qualifier 建立了 @Phone 和 @Computer 注解。

@Inject
@Phone
String phone;

@Inject
@Computer
String computer;

@Module
public class SecondActivityModule {

    @Provides
    @Singleton
    public TestSingleton provideTestSingleton(){
        return new TestSingleton();
    }

    @Provides
    @Phone
    public String providePhone() {
        return "手机";
    }

    @Provides
    @Computer
    public String provideComputer() {
        return "电脑";
    }

}

这样的效果是一样的。但是好处在于 @Name() 中要传入字符串,一不小心就容易将单词拼错,容易出错。

Dagger2 中的延迟加载

有些时候,我们希望依赖只有在我们使用的时候再去实例化,这样的机制叫做延迟加载。
比如

public class TestLazy {

    String name;

    public String getName() {
        if ( name == null ) {
            name = "TestLazy";
        }

        return name;
    }
}

只有调用 TestLazy 实例的 getName() 方法时,name 才会被初始化。

Dagger2 提供了延迟加载能力。只需要通过 Lazy 就好了,Lazy 是泛型类,接受任何类型的参数。

public class TestLazy {

    @Inject
    @Named("TestLazy")
    Lazy<String> name;

    public String getName() {
        return name.get();
    }

}

@Module
public class SecondActivityModule {

    @Provides
    @Singleton
    public TestSingleton provideTestSingleton(){
        return new TestSingleton();
    }

    @Provides
    @Phone
    public String providePhone() {
        return "手机";
    }

    @Provides
    @Computer
    public String provideComputer() {
        return "电脑";
    }

    @Provides
    @Named("TestLazy")
    public String provideTestLazy() {
        return "TestLazy";
    }

}

这样,只有第一次调用 TestLazy 的 getName() 方法时,name 都会被注入。

Provider 强制重新加载

应用 @Singleton 的时候,我们希望每次都是获取同一个对象,但有的时候,我们希望每次都创建一个新的实例,这种情况显然与 @Singleton 完全相反。Dagger2 通过 Provider 就可以实现。它的使用方法和 Lazy 很类似。


public class TestProvider {
    @Inject
    Provider<Integer> randomValue;

    public int getRandomValue () {
        return randomValue.get().intValue();
    }
}


@Module
public class SecondActivityModule {

    ......

    @Provides
    public int provideRandomValue(){
        return (int) Math.random();
    }

}

但是,需要注意的是 Provider 所表达的重新加载是说每次重新执行 Module 相应的 @Provides 方法,如果这个方法本身每次返回同一个对象,那么每次调用 get() 的时候,对象也会是同一个。

Dagger2 中 Component 之间的依赖。

在程序开发中,可以存在多个 Component,而且 Component 之间还可以有依赖关系。比如

public class Guazi {}

public class Huotuichang {}

@Module
public class XiaoChiModule {

    @Provides
    public Guazi provideGuazi() {
        return new Guazi();
    }

    @Provides
    public Huotuichang provideHuotuichang() {
        return new Huotuichang();
    }
}

@Component(modules = XiaoChiModule.class)
public interface XiaoChiComponent {
    Guazi provideGuazi();

    Huotuichang provideHuotuichang();
}

XiaoChiComponent 这个 Component 主要作用是提供瓜子和火腿肠这些依赖。

现在,我要新建立一个 Component 代表食物类,并且食物类包括小吃,因此,我们得想办法利用 XiaoChiCompoent 这个现成的 Component。

@Module
public class FoodModule {

    @Provides
    public Baozi provideBaozi() {
        return new Baozi();
    }

    @Provides
    public Noodle provideNoodle() {
        return new Kangshifu();
    }
}

@Component(modules = XiaoChiModule.class
        ,dependencies = XiaoChiComponent.class)
public interface FoodComponent {
    void inject(ThirdActivity activity);
}

只需要在 @Component 中的 dependencies 属性取值为相应的依赖就可以了。这样,FoodComponent 也提供了瓜子和火腿肠的依赖。我们再看如何使用?

public class ThirdActivity extends AppCompatActivity {

    Button mBtnTest;

    @Inject
    Guazi guazi;
    @Inject
    Huotuichang huotuichang;
    @Inject
    Baozi baozi;
    @Inject
    Noodle noodle;

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

        mBtnTest = (Button) findViewById(R.id.test_dependency);

        XiaoChiComponent xiaoChiComponent = DaggerXiaoChiComponent.builder()
                .build();

        DaggerFoodComponent.builder()
                .xiaoChiComponent(xiaoChiComponent)
                .build()
                .inject(this);

        mBtnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(ThirdActivity.this,
                        baozi+" "+
                        noodle+" "
                        +guazi+""+huotuichang,Toast.LENGTH_LONG).show();
            }
        });

    }
}

只需要在 DaggerFoodComponent 构建的时候,将所依赖的 Component 传递进去就是了,但前提是要先创建 XiaochiComponent ,然后创建 FoodComponent 的时候将它传递进去。如上面调用了 DaggerFoodComponent.Builder 的 xiaochiComponent() 方法。

继续查看结果。
这里写图片描述

Dagger2 中的 SubComponent

在 Java 软件开发中,我们经常面临的就是“组合”和“继承”的概念。它们都是为了扩展某个类的功能。
前面的 Component 的依赖采用 @Component(dependecies=othercomponent.class) 就相当于组合。
那么在 Dagger2 中,运用 @SubComponent 标记一个 Component 的行为相当于继承。

@Subcomponent(modules = FoodModule.class)
public interface SubComponent {
    void inject(ThirdActivity activity);
}

@Component(modules = XiaoChiModule.class)
public interface ParentComponent {
    SubComponent provideSubComponent();
}


DaggerParentComponent.builder().build()
                .provideSubComponent().inject(this);

使用 Subcomponent 时,还是要先构造 ParentComponent 对象,然后通过它提供的 SubComponent 再去进行依赖注入。

大家可以细细观察下它与 depedency 方法的不同之处。

但是,SubComponent 同时具备了 ParentComponent 和自身的 @Scope 作用域。所以,这经常会造成混乱的地方。大家需要注意。

如果你要我比较,SubComponent 和 dependency 形式哪种更好时,我承认各有优点,但我自己倾向于 dependency,因为它更灵活。

不是说 组合优于继承嘛

到这里的时候,Dagger2 的基础知识都介绍的差不多了,它还有一些知识点,但是应用的场景太复杂,所以没有必要细究。有兴趣的同学可以到官网上自行研究。

Dagger2 在什么地方进入依赖注入?如何注入?

也许会有一部分同学,执着于细节。因为 Dagger2 帮我们进行了依赖注入,但这一切过程是透明的,我们并不知晓。有探索精神的同学总想去获取更多的细节,这种精神值得称赞。

我简单说一下,Dagger2 运用了 APT 插件,这种插件会在编译时根据 @Provide、@Inject、@Moudle、@Component 这些注解生成许多中间代码,但是不管它多么复杂它的目的也只是为了依赖注入。所以,肯定有一个地方进行了。


a.setB(b);

这样的操作。

在依赖注入概念中,我分了需求者、注射者、依赖三个角色。
这里写图片描述
总是注射者给需求者注入了依赖。

前面说过 Component 是需求与依赖的联系,因此可以在 Component 的实现类代码中找出分别代表需求、注射者、依赖 3 个角色,然后找出依赖注入发生时的代码,这个问题就算解答完成了。

@Component(modules = SecondActivityModule.class)
public interface ActivityComponent {
    void inject(SecondActivity activity);
}

我们以 ActivityComponent 为例解释说明,在这里显然 SecondActivity 的实例是需求者。

@Inject
TestSingleton testSingleton1;
@Inject
TestSingleton testSingleton2;

@Inject
@Phone
String phone;

@Inject
@Computer
String computer;

SecondActivity 它需要 2 种依赖,TestSingleton、String。我们去查看最关键的类,也就是 Dagger2 帮助我们生成的 DaggerActivityComponent。

public final class DaggerActivityComponent implements ActivityComponent {
  private Provider<TestSingleton> provideTestSingletonProvider;

  private Provider<String> providePhoneProvider;

  private Provider<String> provideComputerProvider;

  private MembersInjector<SecondActivity> secondActivityMembersInjector;

  private DaggerActivityComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

}

我在前面讲解过 Provider 是什么用处,大家一看就懂。而 MembersInjector 以 Injector 为后缀,所以它肯定是一个注射器。我根本不需要去查看它的定义和相关源码。

好了,现在注射者(MembersInjector)找到了,依赖(Provider provideTestSingletonProvider 等等)找到了,需求者我们知道是 SecondActivity,所以我们只要把依赖注入发生的代码找出来,问题就解答完成。

@Override
public void inject(SecondActivity activity) {
    secondActivityMembersInjector.injectMembers(activity);
}

inject() 方法显然是依赖注入发生的地方,但它内部调用了 secondActivityMembersInjector.injectMembers() 方法,我们跟踪进去。

public void injectMembers(SecondActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.testSingleton1 = testSingleton1AndTestSingleton2Provider.get();
    instance.testSingleton2 = testSingleton1AndTestSingleton2Provider.get();
    instance.phone = phoneProvider.get();
    instance.computer = computerProvider.get();
}

代码交待的一清二楚,SecondActivity 实例 instance 的 testSingleton1、testSingleton2、phone、computer 4 个依赖,全部在这里进行赋值,也就是在这里进行依赖注入。

不过,我们都知道依赖都是由 Module 提供的,回到 DaggerActivityComponent 的源码当中关注它的 initialize 方法。

private void initialize(final Builder builder) {

    this.provideTestSingletonProvider =
        DoubleCheck.provider(
            SecondActivityModule_ProvideTestSingletonFactory.create(builder.secondActivityModule));

    this.providePhoneProvider =
        SecondActivityModule_ProvidePhoneFactory.create(builder.secondActivityModule);

    this.provideComputerProvider =
        SecondActivityModule_ProvideComputerFactory.create(builder.secondActivityModule);

    this.secondActivityMembersInjector =
        SecondActivity_MembersInjector.create(
            provideTestSingletonProvider, providePhoneProvider, provideComputerProvider);
  }

它们都是通过工厂方法创建的。大家应该都懂工厂方法都是用来创建对象的。我不嫌麻烦,挑出 SecondActivityModule_ProvidePhoneFactory 这个个例来进行讲解。它代表 Phone 对象的工厂。

public final class SecondActivityModule_ProvidePhoneFactory implements Factory<String> {
  private final SecondActivityModule module;

  public SecondActivityModule_ProvidePhoneFactory(SecondActivityModule module) {
    assert module != null;
    this.module = module;
  }

  @Override
  public String get() {
    return Preconditions.checkNotNull(
        module.providePhone(), "Cannot return null from a non-@Nullable @Provides method");
  }

  public static Factory<String> create(SecondActivityModule module) {
    return new SecondActivityModule_ProvidePhoneFactory(module);
  }
}


public interface Factory<T> extends Provider<T> {
}

调用它的 create 方法会返回一个 Factory 对象,但是 Fractory 是 Provider 的子类。
前面讲过调用 Provider 方法的 get() 方法能够得到实例,所以最终会调用

public String get() {
    return Preconditions.checkNotNull(
        module.providePhone(), "Cannot return null from a non-@Nullable @Provides method");
}

所以最终会调用

@Module
public class SecondActivityModule {

    @Provides
    @Phone
    public String providePhone() {
        return "手机";
    }

}

其它几个流程类似,那就不一一分析了。有兴趣的同学可以去查看源码,我只提示一个信息,你去观察 MembersInjector 怎么创建 @Singleton 注解的依赖时会发现它的实现步骤,跟我前面文章手动生成 DBManager 单例用的手段是很相似的。

对于 Dagger2 的用途与意义心生迷惑

我想,还是会有一大部分的同学看到这里的时候仍然不明白,Dagger2 的妙处。
其实在文章开始的地方我就讲了 Dagger2 的本质,它本质就是一款依赖注入框架,用来解耦的。

掌握上面的 Dagger2 基础知识已经足够让你进行此类代码编写了,另外也足够让你去看懂一些运用了 Dagger2 的优秀开源项目,比如 Google 提供的示例
todo-mvp-dagger

这个项目示例就是为了演示 Dagger2 与 MVP 架构的配合使用。由于文章篇幅所限,我不作过多的讲解,大家自行研究。有机会,我会专门写一篇文章来讲述 Dagger2 在一个完整项目工程中如何进行解耦的。

如果你对 Dagger2 兴趣更浓烈了

Dagger2 的知识内容稍多,所以如果你耐着性子学习完后,兴致依赖不减,我可以给你一些建议。

  1. 自己去阅读官网文档。因为那才是第一手资料,虽然它写的不是很好,但毕竟权威。
  2. 多去观察不同的博文,因为每个人思考方式不一样,所以观察问题的角度可能不一样。
  3. 自己多练,任何没有经过自己实践的行为在软件编程中都不可取。
  4. 阅读优秀的开源代码,并思考。
  5. 在思考的同时,纠正自己的理解,然后再实践,再思考,再总结。我总说主动学习要好过被动学习,任何没有经过自己思考和求索的学习都是被动学习,看文档、看博文、看代码那都是别人的知识,你需要的就是用自己把这些纳入自己的知识体系中,当你也能讲述给其他人听的时候,那时你就可以确定你掌握它了。

如果你仍然意识不到 Dagger2 的美好

这个其实也没有多大关系。不要迷恋武器。

也许你写的代码中类文件不是很多,模块之间的耦合并不是很强,或者是整个系统并不复杂,强行引进 Dagger2 只会让你感受复杂,多了很多类,多了很多编译的步骤,还增加了学习成本,你会觉得不划算。

我们总说优化代码,设计架构,其实对于很多开发人员而言,大量的产品需求就能让自己加好几个晚上的班,并且需求还经常变动,如果你这个时候跟他讲代码规范什么的,肯定不现实。现实的事情是,完成需求永远第一要务。

饱暖才能思淫欲。

我与其劝你去感受 Dagger2 的美好,还不如劝你细细去体会一下依赖注入的美好。

如果,你仍然觉得 Dagger2 麻烦,中肯地讲一句:那么索性放弃它算了,Dagger2 不重要,依赖注入才重要

等到哪一天,你真的有强烈的对于代码解耦需求,也许你会想起 Dagger2 这么一款框架,那时候回过来学习,我保证你的效果会非常明显,你的理解力也会较现在更加的深刻。

所以,我最终的目的仍然是希望你能够好好学习 Dagger2,希望大家不要误解我这一节的真实用意。

demo

作者:briblue 发表于2017/7/20 22:14:00 原文链接
阅读:304 评论:4 查看评论

使用UDP方式 与iOS端App通讯

$
0
0
  • 首先需要安装一个TCP&UDP测试工具
  • 连接类型选择UDP
  • 目标IP 设置手机的IP, 端口8888 (这个端口在App端用来绑定)
  • 指定端口, 是App向回发信息所需要的端口, 具体设置如下图所示

创建连接

接下来为减少代码的键入, 我直接使用CocoaAsyncSocket这个三方库,作为中间媒介完成整个过程

{
      GCDAsyncUdpSocket *udpSocket; // 定义一个socket的对象 签订代理 GCDAsyncUdpSocketDelegate
}
2017/7/20 17:05:40
    /*************** UDP ***********************/
    udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
    NSError *error = nil;
    // 绑定端口
    [udpSocket bindToPort:8888 error:&error];
    // 启用广播
    [udpSocket enableBroadcast:YES error:&error];

    if (error) {
        [SVProgressHUD showErrorWithStatus:@"启用失败"];
    }else {
        NSLog(@"%@", [udpSocket localHost]);
        // 开始接收消息
        [udpSocket beginReceiving:&error];

    }
    /*************** UDP ***********************/
#pragma mark - GCDAsyncUdpSocketDelegate
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(nullable id)filterContext {

    NSLog(@"success");
    NSString *ip = [GCDAsyncUdpSocket hostFromAddress:address];
    NSString *s = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"收到响应 %@ %@", ip, s);
    [sock receiveOnce:nil];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        uint16_t port = 9999;
        [self sendBackToHost:ip port:port withMessage:s];
    });
}
- (void)sendBackToHost:(NSString *)ip port:(uint16_t)port withMessage:(NSString *)s{
    // 回一个 hello summerxx too
    char *str = "hello summerxx too" ;
    NSData *data = [NSData dataWithBytes:str length:strlen(str)];
    [udpSocket sendData:data toHost:ip port:port withTimeout:0.1 tag:200];
}

收到信息

发回一条信息

作者:sinat_30162391 发表于2017/7/20 23:21:20 原文链接
阅读:189 评论:0 查看评论

Kotlin学习之-6.4 Coroutines

$
0
0

Kotlin学习之-6.4 Coroutines

在Kotlin V1.1中Coroutines 还处在实验阶段

有些接口会做一些耗时的操作例如网络IO请求,文件IO, CPU或者GUP密集的工作等,并且要求调用者阻塞知道操作完成。Coroutines提供了一种能够替换避免阻塞线程的方法并且代价更小、控制性更好的操作:suspension of a coroutine

Coroutines通过把复杂的管理放到代码库中来简化异步编程。程序的逻辑可以在Coroutine中线性的表达,然后使用的库会帮我们解决异步处理的问题。库可以把我们相关的代码包装成回调,订阅给相关的事件,分配到不同线程上执行,最终代码仍然保持和线性顺序执行一样简单。

在其他语言中有很多异步机制可以用来实现Kotlin的coroutines。这包括C#和ECMASCript语言中的async/await,Go语言中channelsselect,还有C#和Python中的generator/yield

阻塞和挂起

通常来说,coroutines是那些可以被挂起但不需要阻塞线程的操作。阻塞线程是非常昂贵的,尤其是在高负载的情况下,因为只有一个数量相对小的线程在实际中持续运行,因此阻塞其中的一个会导致一些重要的工作被延迟。

另一方面,Coroutine挂起几乎是没有损耗的。没有上下文切换或者其他操作系统的需求。并且,挂起还可以通过一个用户库来控制,作为库的作者,我们可以决定在挂起时要做什么并且可以根据我们的需求来优化、添加日志或者拦截相应的操作。

另外一个区别是coroutines不能被随机的指令挂起,而只能在一些特殊的挂起点处挂起,他们会调用特殊标记的函数。

挂起函数

挂起发生在我们调用一个标记了suspend的函数的时候:

suspend fun doSomething(foo: Foo): Bar {
}

这样的函数叫作挂起函数,因为调用这些函数可能会挂起一个coroutine(库可以决定执行是否挂起,如果调用的结果已经可用的话)。 挂起函数可以和普通函数一样有参数和返回值,但是他们只能在coroutines 中或者其他挂起函数中调用。实际上,要启动一个coroutine,必须至少有一个挂起函数,并且通常是匿名,例如一个挂起lambda表达式。 我们看一个例子,一个简化的async()函数(来自kotlinx.coroutines库)

fun<T> async(block: suspend () -> T)

这里,async()是一个普通函数(不是一个挂起函数),但是block参数有一个suspend修饰的函数类型:suspend () -> T。因此,当我们传递一个lambda表达式给async()的时候,他就是一个挂起lambda表达式,我们可以在它里面调用一个挂起函数。

async {
    doSomething(foo)
}

为了继续类比,await()函数可是是一个挂起函数(因此可以在async() 代码块中调用),它会挂起一个coroutine知道有些计算完成并且返回。

async {
    val result = computation.await()
}

更多关于真正async/await函数工作的信息可以在kotlinx.coroutines中找到,点击这里

注意挂起函数await()doSomething不在在一个普通函数被调用,如main()函数

fun main(args: Array<String>) {
    doSomething()
}

还有要注意的是挂起函数可以是虚函数,并且当他们被复写的时候,suspend修饰符必须要指明。

interface Base {
    suspend fun foo()
}

class Derived: Base {
    override suspend fun foo() { }
}

@RestrictsSuspension 注解

扩展函数和lambda表达式也可以标记成suspend,就想普通函数一样。这使得可以创建用户可以扩展的DSL和其他API。在有些情况下库的作者需要放置用户添加新的方式来挂起一个coroutine。

为了达到这个,可以使用@RestrictsSuspension注解。当一个接收类或者接口R是用它注解的时候,所有挂起的扩展函数都需要代理到其他R的成员或者其他扩展上。因为扩展不能相互无线代理(这样程序就无法结束了), 这保障了所有的挂起都发生在调用R的成员的时候,而这些是库的作者可以完全控制的。

这和一种少见的情况相关,当每一个挂起在库中是用一个特殊的方式处理的。例如,当通过buildSequence()函数来实现生成器的时候,我们需要确保任何挂起调用都在coroutine并用调用yield()或者yieldAll()来结束,而没有任何其他函数。 这就是SequenceBuilder 是用@RestrictsSuspension来注解的原因。

@RestrictsSuspension
public abstract class SequenceBuilder<in T> {
}

源代码见Github

coroutine 的内部工作

这里我们不会完全的讲解coroutine的背后的工作原理,但是简要地知道发生了什么还是很重要的。

Coroutines的实现是完全通过一种编译技术,(不需要VM 和OS 的支持)并且挂起是通过代码转换来实现的。 基本上,所有挂起函数(可能会优化,但是这里先不详细展开)都转换成一个状态机它的状态对应了挂起调用的不同状态。然后,在挂起之前,下一个状态存储在一个编译器生成的类的对象和其他相关的局部变量中。当coroutine恢复执行时,局部变量会恢复并且状态机会从挂起之前的状态继续执行。

一个挂起的coroutine可以存储起来并当做一个对象传递,保留它的挂起状态和局部变量。这种类型的对象是Continuation,并且所有代码转换都描述在这里和经典的Continuation-passing style相对应。结果就是,具体实现中挂起函数使用一个额外的Continuation类型的参数。

更多关于cotoutines如何实现的细节可以在设计文档中找到。关于其他语言中async/await的解释相关,尽管他们的语言特性使得不会像Kotlin的coroutine这样通用。

coroutine的实验状态

coroutine的设计还是实验性的,这意味着它可能会在将来的发布中变化。当用Kotlin v1.1编译coroutine的时候,会有一个默认的警告:coroutine特性是实验性的。 想要移除这个警告,你需要标明opt-in标志位。

由于他的实验状态,在标准库中和coroutine相关的API都放在了kotlin.coroutines.experimental包下。 当设计完成时,实验状态将被移除,最终的API会被移动到kotlin.coroutines包下,并且实验状态的包会被保留下来用来向后兼容旧版本。

重要提示:我们建议库作者遵循同样的规范:添加”experimental” 后缀到你的包后面,如果它暴露了基于coroutine的API,这样你的库就能保持二进制的兼容性。当正式API发布时,遵循以下步骤:

  • 拷贝所有API 到com.example下(不带experimental后缀的包名)
  • 保留实验的包,为了向后兼容性。

这将会减少用户的移植问题。

标准API

coroutine主要包含3部分:

  • 语言支持(挂起函数,如上所述)
  • Kotlin标准库底层核心API
  • 可以直接被用户使用的高层API

底层API: kotlin.coroutines

底层API想对很小并且永远不应该用来做除了构建高层库的事情。它包含两个主要的包:

  • kotlin.coroutines.exmperimental,包含主要类型和基础元素,例如
    • createCoroutine()
    • startCoroutine()
    • suspendCoroutine()
  • kotlin.coroutines.experimental.intrinsics包含更底层的核心接口例如suspendCoroutineOrReturn

更多关于如何使用这些接口的细节,点击这里

kotlin.coroutines中的生成器接口

kotlin.coroutines.experimental包中仅有的应用程序级别函数是

这些接口和kotlin-stadlib一起发布是因为他们和序列相关。实际上,这些函数实现了生成器,例如提供一种简单构造延迟序列的方法:

import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
    val fibonacciSeq = buildSequence {
        var a = 0
        var b = 1

        yield(1)

        while (true) {
            yield(a + b)

            val tmp = a + b
            a = b
            b = tmp
        }
    }

    // Print the first five Fibonacci numbers
    println(fibonacciSeq.take(8).toList())
}

// output
[1, 1, 2, 3, 5, 8, 13, 21]

这生成了一个延迟加载,可能是无线的斐波那契数列通过创建一个能够生成连续斐波那契数字的函数的coroutine。当遍历这样的序列时,每一步迭代器执行生成下一个数字。因此,我们可以从列表中获取任意有限数量的数列, 例如fibonacciSeq.take(8).toList()返回[1, 1, 2, 3, 5, 8, 13, 21]。并且coroutine代价很低足可以让这种方式非常实用。

为了掩饰序列到底是如何延迟加载的,我们在调用buildSequence()时打印一些调试信息:

import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
    val lazySeq = buildSequence {
        print("START ")
        for (i in 1..5) {
            yield(i)
            print("STEP ")
        }
        print("END")
    }

    // Print the first three elements of the sequence
    lazySeq.take(3).forEach { print("$it ") }
}

// output
START 1 STEP 2 STEP 3 

运行上面的代码发现如果我们打印前3个元素,那么数字会和STEP的输出信息交织在一起。这意味着实际上计算是延迟的。为了打印1,我们只需要执行到第一个yield(i),并且START也会打印出来。接下来,要输出2,我们需要执行到下一个yield(i),这会输出STEP.输出3的情况类似。最后一个STEPEND不会被输出,因为我们没有请求更多的序列元素。

可以使用yieldAll()函数来一次生成一个集合

import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
    val lazySeq = buildSequence {
        yield(0)
        yieldAll(1..10) 
    }

    lazySeq.forEach { print("$it ") }
}

// output
0 1 2 3 4 5 6 7 8 9 10

函数buildIterator()buildSequence()工作方式很像,但是返回一个延迟的迭代器。

我们可以给buildSequence()添加自定义逻辑,通过写挂起扩展方法给SequenceBuilder类。

import kotlin.coroutines.experimental.*

suspend fun SequenceBuilder<Int>.yieldIfOdd(x: Int) {
    if (x % 2 != 0) yield(x)
}

val lazySeq = buildSequence {
    for (i in 1..10) yieldIfOdd(i)
}

fun main(args: Array<String>) {
    lazySeq.forEach { print("$it ") }
}

// output
1 3 5 7 9 

其他高级接口: kotlinx.coroutines

只有和coroutine相关的核心接口可以在Kotlin标准库中存在。这包含了大部分的核心基础内容和接口,这些可能会被coroutine相关的库使用。

大多数应用层基于coroutines的接口都发布在一个单独的库中:kotlinx.coroutines。这个库包含:

  • 平台无关的异步编程接口kotlinx-coorutines-core
    • 这个模块包含Go语言风格的支持select操作的管道和其他一些方便的基础元素
    • 这个库的详细使用说明在这里
  • 基于JDK 8 的CompletableFuture的接口:kotlinx-coroutines-jdk8
  • 基于JDK 7 的非阻塞IO(NIO)接口:kotlinx-coroutines-nio
  • 支持Swing(kotlinx-coroutins-swing)和JavaFx(`kotlinx-coroutines-javafx)
  • 支持RxJava:kotlinx-coroutines-rx

这些库既提供方便的API使得普通工作更加简单,并且还提供了端到端的如何构建基于coroutine库的例子


PS,我会坚持把这个系列写完,有问题可以留言交流,也关注专栏Kotlin for Android Kotlin安卓开发

作者:farmer_cc 发表于2017/7/21 9:24:31 原文链接
阅读:111 评论:0 查看评论

react-native城市列表组件

$
0
0

城市列表选择是很多app共有的功能,比如典型的美图app。那么对于React Native怎么实现呢?
这里写图片描述

要实现上面的效果,首先需要对界面的组成简单分析,界面的数据主要由当前城市,历史访问城市和热门城市组成,所以我们在提供Json数据的时候就需要将数据分为至少3部分。

const ALL_CITY_LIST = DATA_JSON.allCityList;
const HOT_CITY_LIST = DATA_JSON.hotCityList;
const LAST_VISIT_CITY_LIST = DATA_JSON.lastVisitCityList;

而要实现字母索引功能,我们需要自定义一个控件,实现和数据的绑定关系,自定义组件代码如下:
CityIndexListView.js

'use strict';
import React, {Component} from 'react';
import {
    StyleSheet,
    View,
    Text,
    TouchableOpacity,
    ListView,
    Dimensions,
} from 'react-native';

import Toast, {DURATION} from './ToastUtil'

const SECTIONHEIGHT = 30;
const ROWHEIGHT = 40;
const ROWHEIGHT_BOX = 40;
var totalheight = []; //每个字母对应的城市和字母的总高度

const {width, height} = Dimensions.get('window');

var that;

const key_now = '当前';
const key_last_visit = '最近';
const key_hot = '热门';

export default class CityIndexListView extends Component {

    constructor(props) {
        super(props);

        var getSectionData = (dataBlob, sectionID) => {
            return sectionID;
        };
        var getRowData = (dataBlob, sectionID, rowID) => {
            return dataBlob[sectionID][rowID];
        };

        let ALL_CITY_LIST = this.props.allCityList;
        let CURRENT_CITY_LIST = this.props.nowCityList;
        let LAST_VISIT_CITY_LIST = this.props.lastVisitCityList;
        let HOT_CITY_LIST = this.props.hotCityList;

        let letterList = this._getSortLetters(ALL_CITY_LIST);

        let dataBlob = {};
        dataBlob[key_now] = CURRENT_CITY_LIST;
        dataBlob[key_last_visit] = LAST_VISIT_CITY_LIST;
        dataBlob[key_hot] = HOT_CITY_LIST;

        ALL_CITY_LIST.map(cityJson => {
            let key = cityJson.sortLetters.toUpperCase();

            if (dataBlob[key]) {
                let subList = dataBlob[key];
                subList.push(cityJson);
            } else {
                let subList = [];
                subList.push(cityJson);
                dataBlob[key] = subList;
            }
        });

        let sectionIDs = Object.keys(dataBlob);
        let rowIDs = sectionIDs.map(sectionID => {
            let thisRow = [];
            let count = dataBlob[sectionID].length;
            for (let ii = 0; ii < count; ii++) {
                thisRow.push(ii);
            }

            let eachheight = SECTIONHEIGHT + ROWHEIGHT * thisRow.length;
            if (sectionID === key_hot || sectionID === key_now || sectionID === key_last_visit) {
                let rowNum = (thisRow.length % 3 === 0)
                    ? (thisRow.length / 3)
                    : parseInt(thisRow.length / 3) + 1;

                console.log('sectionIDs===>' + sectionIDs + ", rowNum=====>" + rowNum);

                eachheight = SECTIONHEIGHT + ROWHEIGHT_BOX * rowNum;
            }

            totalheight.push(eachheight);

            return thisRow;
        });


        let ds = new ListView.DataSource({
            getRowData: getRowData,
            getSectionHeaderData: getSectionData,
            rowHasChanged: (row1, row2) => row1 !== row2,
            sectionHeaderHasChanged: (s1, s2) => s1 !== s2
        });

        this.state = {
            dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs),
            letters: sectionIDs
        };

        that = this;
    }

    _getSortLetters(dataList) {
        let list = [];

        for (let j = 0; j < dataList.length; j++) {
            let sortLetters = dataList[j].sortLetters.toUpperCase();

            let exist = false;
            for (let xx = 0; xx < list.length; xx++) {
                if (list[xx] === sortLetters) {
                    exist = true;
                }
                if (exist) {
                    break;
                }
            }
            if (!exist) {
                list.push(sortLetters);
            }
        }

        return list;
    }

    _cityNameClick(cityJson) {
        // alert('选择了城市====》' + cityJson.id + '#####' + cityJson.name);
        this.props.onSelectCity(cityJson);
    }

    _scrollTo(index, letter) {
        this.refs.toast.close();
        let position = 0;
        for (let i = 0; i < index; i++) {
            position += totalheight[i]
        }
        this._listView.scrollTo({y: position});
        this.refs.toast.show(letter, DURATION.LENGTH_SHORT);
    }

    _renderRightLetters(letter, index) {
        return (
            <TouchableOpacity key={'letter_idx_' + index} activeOpacity={0.6} onPress={() => {
                this._scrollTo(index, letter)
            }}>
                <View style={styles.letter}>
                    <Text style={styles.letterText}>{letter}</Text>
                </View>
            </TouchableOpacity>
        );
    }

    _renderListBox(cityJson, rowId) {
        return (
            <TouchableOpacity key={'list_item_' + cityJson.id} style={styles.rowViewBox} onPress={() => {
                that._cityNameClick(cityJson)
            }}>
                <View style={styles.rowdataBox}>
                    <Text style={styles.rowDataTextBox}>{cityJson.name}</Text>
                </View>
            </TouchableOpacity>
        );
    }

    _renderListRow(cityJson, rowId) {
        console.log('rowId===>' + rowId + ", cityJson====>" + JSON.stringify(cityJson));
        if (rowId === key_now || rowId === key_hot || rowId === key_last_visit) {
            return that._renderListBox(cityJson, rowId);
        }

        return (
            <TouchableOpacity key={'list_item_' + cityJson.id} style={styles.rowView} onPress={() => {
                that._cityNameClick(cityJson)
            }}>
                <View style={styles.rowdata}>
                    <Text style={styles.rowdatatext}>{cityJson.name}</Text>
                </View>
            </TouchableOpacity>
        )
    }

    _renderListSectionHeader(sectionData, sectionID) {
        return (
            <View style={styles.sectionView}>
                <Text style={styles.sectionText}>
                    {sectionData}
                </Text>
            </View>
        );
    }

    render() {
        return (
            <View style={styles.container}>
                <View style={styles.listContainner}>
                    <ListView ref={listView => this._listView = listView}
                              contentContainerStyle={styles.contentContainer} dataSource={this.state.dataSource}
                              renderRow={this._renderListRow} renderSectionHeader={this._renderListSectionHeader}
                              enableEmptySections={true} initialListSize={500}/>
                    <View style={styles.letters}>
                        {this.state.letters.map((letter, index) => this._renderRightLetters(letter, index))}
                    </View>
                </View>
                <Toast ref="toast" position='top' positionValue={200} fadeInDuration={750} fadeOutDuration={1000}
                       opacity={0.8}/>
            </View>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        // paddingTop: 50,
        flex: 1,
        flexDirection: 'column',
        backgroundColor: '#F4F4F4',
    },
    listContainner: {
        height: Dimensions.get('window').height,
        marginBottom: 10
    },
    contentContainer: {
        flexDirection: 'row',
        width: width,
        backgroundColor: 'white',
        justifyContent: 'flex-start',
        flexWrap: 'wrap'
    },
    letters: {
        position: 'absolute',
        height: height,
        top: 0,
        bottom: 0,
        right: 10,
        backgroundColor: 'transparent',
        // justifyContent: 'flex-start',
        // alignItems: 'flex-start'
        alignItems: 'center',
        justifyContent: 'center'
    },
    letter: {
        height: height * 4 / 100,
        width: width * 4 / 50,
        justifyContent: 'center',
        alignItems: 'center'
    },
    letterText: {
        textAlign: 'center',
        fontSize: height * 1.1 / 50,
        color: '#e75404'
    },
    sectionView: {
        paddingTop: 5,
        paddingBottom: 5,
        height: 30,
        paddingLeft: 10,
        width: width,
        backgroundColor: '#F4F4F4'
    },
    sectionText: {
        color: '#e75404',
        fontWeight: 'bold'
    },
    rowView: {
        height: ROWHEIGHT,
        paddingLeft: 10,
        paddingRight: 10,
        borderBottomColor: '#F4F4F4',
        borderBottomWidth: 0.5
    },
    rowdata: {
        paddingTop: 10,
        paddingBottom: 2
    },

    rowdatatext: {
        color: 'gray',
        width: width
    },

    rowViewBox: {
        height: ROWHEIGHT_BOX,
        width: (width - 30) / 3,
        flexDirection: 'row',
        backgroundColor: '#ffffff'
    },
    rowdataBox: {
        borderWidth: 1,
        borderColor: '#DBDBDB',
        marginTop: 5,
        marginBottom: 5,
        paddingBottom: 2,
        marginLeft: 10,
        marginRight: 10,
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center'
    },
    rowDataTextBox: {
        marginTop: 5,
        flex: 1,
        height: 20
    }

});

然后在头部还需要实现一个搜索框。
SearchBox.js

'use strict';
import React, {Component} from 'react';
import {
    View,
    TextInput,
    StyleSheet,
    Platform,
} from 'react-native';

export default class SearchBox extends Component {
    constructor(props) {
        super(props);
        this.state = {
            value: ''
        };

    }

    onEndEditingKeyword(vv) {
        console.log(vv);
    }

    onChanegeTextKeyword(vv) {
        console.log('onChanegeTextKeyword', vv);

        this.setState({value: vv});
        this.props.onChanegeTextKeyword(vv);
    }

    render() {
        return (
            <View style={styles.container}>
                <View style={styles.inputBox}>
                    <View style={styles.inputIcon}>
                    </View>
                    <TextInput ref="keyword" autoCapitalize="none" value={this.props.keyword}
                               onChangeText={this.onChanegeTextKeyword.bind(this)} returnKeyType="search" maxLength={20}
                               style={styles.inputText} underlineColorAndroid="transparent"
                               placeholder={'输入城市名或拼音查询'}/>
                </View>
            </View>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        marginTop: 5,
        marginBottom: 5,
        backgroundColor: '#ffffff',
        flexDirection: 'row',
        height: Platform.OS === 'ios'
            ? 35
            : 45,
        borderBottomWidth: StyleSheet.hairlineWidth,
        borderBottomColor: '#cdcdcd',
        paddingBottom: 5
    },
    inputBox: {
        height: Platform.OS === 'ios'
            ? 30
            : 40,
        marginLeft: 5,
        marginRight: 5,
        flex: 1,
        flexDirection: 'row',
        backgroundColor: '#E6E7E8'
    },
    inputIcon: {
        margin: Platform.OS === 'ios'
            ? 5
            : 10
    },
    inputText: {
        alignSelf: 'flex-end',
        marginTop: Platform.OS === 'ios'
            ? 0
            : 0,
        flex: 1,
        height: Platform.OS === 'ios'
            ? 30
            : 40,
        marginLeft: 2,
        marginRight: 5,
        fontSize: 12,
        lineHeight: 30,
        textAlignVertical: 'bottom',
        textDecorationLine: 'none'
    }
});

最终效果:
这里写图片描述
这里写图片描述
最后是界面的绘制,这里就不多说了,大家可以下载源码自行查看。源码地址:http://download.csdn.net/detail/xiangzhihong8/9905924

作者:xiangzhihong8 发表于2017/7/21 9:37:12 原文链接
阅读:188 评论:0 查看评论

Swift3.0基础之详细讲解Closure闭包结构的使用

$
0
0

源码Demo写的很详细,这里不再赘述,如下:

//创建一个全局的Closure,这是最后应该看的知识点
    //方式一:定义一个闭包变量其实就是定义一个特定函数类型的变量,方式如下。因为Closure变量没有赋初始值,所以我们把其声明为可选类型的变量。在使用时,用!强制打开即可。
    var globalCloure1:((Int, Int) -> Int)?
    
    //方式二:除了上面的方式外,我们还用另一种常用的声明闭包变量的方式。那就是使用关键字typealias定义一个特定函数类型,我们就可以拿着这个类型去声明一个Closure变量了,如下所示
    //定义闭包类型 (就是一个函数类型)
    typealias globalCloure2 = (Int, Int) -> Int
    var myCloure:globalCloure2?

    //全局方法中的Closure
    var globalCloure3:((Int, Int) -> Void)?
    var num11 = Int()
    var num22 = Int()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        //Swift3.0详细的闭包结构Closure的使用
        /**
         定义格式:
         Closure格式:let/var closure名称:((空/属性,空/属性) -> (空/属性))? 简式:let/var 名称:(() -> ())
         如果没有参数,没有返回值,可以直接写成:()/Void,如果有返回值,可以不加(),即:let/var 名称:(() -> Int),但是实现的时候最好和定义的格式一样,如closure3
         **/
        
        //111------创建一个无参数、无返回值的Closure
        let closure1:(() -> ())?
//        closure1 = { () -> () in
//            print("这是一个无参数、无返回值的closure")
//        }
        //为空的时候可不写:() -> () in 如下:
        closure1 = {
            print("这是一个无参数、无返回值的closure")
        }
        //调用
        closure1!()
        
        //222------创建一个有参数、无返回值的Closure
        let closure2:((Int,NSString) -> ())?
//        closure2 = { (a:Int,name:NSString) -> () in
//            print("姓名:\(name),  年龄:\(a)")
//        }
        //为空的时候可不写:-> () 可以忽略 如下:
        closure2 = { (a:Int,name:NSString) in
            print("姓名:\(name),  年龄:\(a)")
        }
        closure2!(20,"张三李四")
        
        //333------创建一个有参数、有返回值的Closure
        //如果有返回值,不加括号也可以,实现的时候建议和定义的形式一致
//        let closure3:((Int,Int) -> Int)?
//        closure3 = { (a:Int,b:Int) -> Int in
//            return a+b
//        }
//        let sum:Int = closure3!(10,5)
//        print("输出此时Closure3的求和结果:\(sum)")
        //上面两段代码都可以合成一段,1,2同理,但是执行closure的时候不需要再加!强制打开使用
        let closure3:((Int,Int) -> Int) = { (a:Int,b:Int) -> Int in
            return a+b
        }
        let sum:Int = closure3(10,5)
        print("输出此时Closure3的求和结果:\(sum)")
        
        //444------创建一个无参数、有返回值的Closure
        let closure4:(() -> Int) = { () -> Int in
            return 10
        }
        let val:Int = closure4()
        print("输出此时Closure4的返回值:\(val)")
        
        //555------创建和调用 一个全局的Closure
        //方式一:
        globalCloure1 = { (a:Int,b:Int) -> Int in
            return a+b
        }
        print("方式一。。。输出此时全局Closure的和:\(globalCloure1!(2,3))")
        //方式二:
        myCloure = { (a:Int,b:Int) -> Int in
            return a+b
        }
        print("方式二。。。输出此时全局Closure的和:\(myCloure!(2,3))")
        
        //666------创建和调用 带有Closure的方法
        //只有Closure结构一个参数的方法
        self.oneClosure { (a, b) in
            print("只有Closure结构一个参数的方法:后执行方法的调用111。。。a+b的和:\(a+b)")
        }
        //有参数,有一个Closure结构的方法
        self.oneClosure(num1: 2, num2: 3) { (a, b) in
            print("有参数,有一个Closure结构的方法:后执行方法的调用222。。。a+b的和:\(a+b)")
        }
        //有参数,有多个个Closure结构的方法
        self.oneClosure(num1: 2, num2: 3, aClosure: { (a, b) in
            print("有参数,有多个个Closure结构的方法:后执行方法的调用333。。。a+b的和:\(a+b)")
        }) { (c, d) in
            print("有参数,有多个个Closure结构的方法:后执行方法的调用333。。。a*b的积:\(c*d)")
        }
        
        //777------方法中有和全局一样的Closure,如globalCloure3,需要传递方法中的Closure,添加@escaping,即方法执行完以后执行Closure
        self.twoClosure(num1: 2, num2: 3) { (a, b) in
            print("通过全局的Closure传递值:后执行方法的调用777。。。a+b的和:\(a+b)")
        }
        //创建一个UIButton
        let myBtn = UIButton.init(frame: CGRect.init(x: 100, y: 100, width: 100, height: 100))
        myBtn.setTitle("Click me", for: .normal)
        myBtn.backgroundColor = UIColor.cyan
        myBtn.setTitleColor(UIColor.red, for: .normal)
        myBtn.addTarget(self, action: #selector(myBtnClick(btn:)), for: .touchUpInside)
        self.view.addSubview(myBtn)
        
        //888------使用@autoclosure描述的Closure
        self.autoclosure(a: 2, b: 3, num: (3>2))
        
    }
    //MARK:666------创建和调用 带有Closure的方法
    //只有Closure结构一个参数的方法
    func oneClosure(aClosure:((Int,Int) -> ())) {
        print("只有Closure结构一个参数的方法:先执行这个方法。。。。111")
        aClosure(2,3)
    }
    //有参数,有一个Closure结构的方法
    func oneClosure(num1:Int,num2:Int,aClosure:((Int,Int) -> ())) {
        print("有参数,有一个Closure结构的方法:先执行这个方法。。。。。222")
        aClosure(num1,num2)
    }
    //有参数,有多个个Closure结构的方法
    func oneClosure(num1:Int,num2:Int,aClosure:((Int,Int) -> ()),bClosure:((Int,Int) -> Void)) {
        print("有参数,有多个个Closure结构的方法:先执行这个方法。。。。。333")
        aClosure(num1,num2)
        bClosure(num1,num2)
    }
    
    //MARK:777-----方法中有和全局一样的Closure,如globalCloure3,需要传递方法中的Closure
    /**
     **注意:
     如果需要赋值给全局Closure时需要加@escaping修饰方法中的闭包参数,如果不加系统会提示你加上,因为这种情况默认你是在方法结束后执行的Closure
     解释如下:
     在以前版本闭包的使用时不用加@escaping的。当前版本,如果闭包没有回调Closure参数返回值,是不需要@escaping的。但是如果闭包传递了Closure参数,就会出现一种假设。那就是closure的内容会在函数执行返回后才完成。
     简单的说就是如果这个闭包是在这个函数结束前被调用,就是@noescape。
     闭包在函数执行完成后才调用,调用的地方超过了函数的范围,就是@escaping逃逸闭包。
     网络请求后结束的回调就是逃逸的。因为发起请求后过一段时间闭包执行。
     在swift3.0中所有闭包都是默认非逃逸的,无需@noescape。如果是逃逸的就@escaping表示。
     延迟操作,网络加载等都需要@escaping。
     **/
    func twoClosure(num1:Int,num2:Int,aClosure:@escaping ((Int,Int) -> ())) {
        print("有全局Closure结构的方法:先执行这个方法。。。。。777")
        num11 = num1
        num22 = num2
        globalCloure3 = aClosure
    }
    //按钮点击方法
    func myBtnClick(btn:UIButton) {
        if (globalCloure3 != nil) {
            globalCloure3!(num11,num22)
        }
    }
    
    //MARK:888------Closure中的@autoclosure其实就是省略了最外围的(),把后面的语句自动的封装成一个闭包:
    //只适用于无参数,有返回值的格式
    //num:(() -> Bool) -----> num: @autoclosure () -> Bool
    func autoclosure(a:Int,b:Int, num: @autoclosure () -> Bool){
        if num() {
            print("true")
        }else{
            print("false")
        }
    }
结果如图:



源码下载:https://github.com/hbblzjy/SwiftAllClosureDemo




作者:hbblzjy 发表于2017/7/21 10:59:10 原文链接
阅读:85 评论:0 查看评论

Android App 瘦身总结 第三章 代码混淆及优化

$
0
0

在前两章我们分别从图片资源和jni动态库这两个方面来分析apk瘦身的优化点

Android App 瘦身总结 第一章 图片资源的优化处理

Android App 瘦身总结 第二章 jni动态库及cpu兼容

本章我们从代码角度来继续进行分析。

代码是一个app的核心,但是实际上一款应用真正自有的代码在空间占有率并不多(当然像淘宝微信这样的航母级自有代码也一定十分庞大),更多的是各种依赖引用的框架、lib和第三方sdk等。所以这部分的优化可能效果没有那么显著,但是十分必要,可以通过优化发现代码习惯问题、深入了解业务。

代码优化主要有一下几点:


一、代码混淆proguard

老生常谈的问题,代码混淆主要目的是增加反编译解读源码的难度,提高应用安全性。但是它同时的确带来了代码量的减少,虽然减少的可能不是特别显著。

代码混淆是android apk的常态,建议大家在应用中使用。关于混淆怎么用网上也有太多的文章了,就不列举了大家可以自行搜索。

但是要注意几点:

(1)引入第三方库时,一定要按照官方文档添加混淆,否则很容易出错

(2)引入jar包时(没有官方文档或者官方未给出),尽量不进行混淆。一个是大部分正规的jar包其实已经混淆过了;而是混淆后容易引起问题。

(3)在不完全了解proguard语法时,不要盲目复制粘贴网上提供一个proguard样本,尽量弄清除每一行语法的作用在使用。

(4)在每次发布版本后,一定要保存好对应的mapping文件,可以用于错误统计分析时将堆栈信息反解析回源码。


二、调整第三方库

这个于动态库类似,很多时候我们为了实现一些功能,会利用已有的轮子——第三方库。但是实际上大部分人对轮子没有全面了解,这就造成了一种情况的出现:举例来说就是我们只是想用一个简单计算功能,但我们引入的却是一台超级电脑。

拿我们的App来说,前期疯狂迭代期没有太多时间去考虑这些,当我们回过头来看的时候发现居然用到了二十多个第三方库。其中很多库都是巨无霸级别,是一套完整健全的某个功能的解决方案,但是我们可能只用其中一个组件、一套工具类等等,这就造成了大量的浪费。

所以这时候我们要注意几点:

(1)以最小的代价实现功能:尽量去寻找那些精准的贴合你的需求的第三方库;如果引入的是一个庞大的库而只用极少的功能,可以考虑将使用的功能部分源码直接拿出来使用。

(2)尽量使用同一家平台的服务:比如友盟、百度等,因为很多服务sdk会依赖一些基础jar包,一般同一家平台的都会用同一套基础jar包。如果不同的功能用不同平台的服务,很容易造成同一种基础功能存在多中解决方案的jar包。比如网络请求就存在volley、okhttp等多种解决方案,如果在一个app中使用了太多家平台的服务,就会出现在应用中同时存在volley、okhttp...,功能重复而且浪费。

(3)自己动手丰衣足食:有时候可能我们需要的是一个没有特别复杂的功能,但是可能由于时间紧张等情况使用了第三方库;另外一种情况是前期需要一个复杂的功能,经过几轮迭代后功能简化了。这时候我们就可以考虑抛开第三方库自己来实现,这样也会提高自身的能力。

(4)不要盲目跟风:现今还有一种现象,部分开发者很喜欢跟风新的框架、新的解决方案。关注新技术固然无错,但是要考虑实际应用场景。我面试过一家公司,两个开发者将时下最新最潮的全都塞入了他们的app,但其实那只是一个有十几个页面功能单一的应用。甚至有个框架只用在了一处地方,最大的功效是减少几行代码而已。而apk大小却接近航母级应用了。

这部分需要我们重新审视应用,更好的办法是严格把关第三方库的引入,虽然这样会比较麻烦,但是以后会受益匪浅。


三、环境差异依赖

有时候我们会为应用引入一些监控、校验等帮助测试的第三方库,但是这些其实正式包中没有必要存在。

这时可以考虑只在debug版本的时候依赖即可, 如:

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'

但是就会出现一个问题,在我们的代码里需要有一些初始化之类的代码。如果只是debugCompile,编译release版本时就会出错,因为找不到对应的类。

解决方法很简单,android studio允许我们创建不同环境的包,如图


我们在main目录的同级别上创建类release和debug两个环境的目录,在包下各有一个Application。这样编译不同环境的时候,编译器会自动将对应环境的目录中的java文件和mian目录下的java文件一起编译。

这样我们就可以在不同的Application中编写不同的代码,比如在debug目录下的Application中添加leakcancanary的初始化代码,而在release目录下的则没有。这样就不会出现release版本编译问题了。

要注意几点:

(1)在main目录下不能有同样的java文件(如上面的Application)

(2)Application只是个例子,在实际开发中,有差异的java代码尽量保证最少,最好只将不一致的部分封装到不同的环境目录中。比如上面的例子,如果Application代码较多,可以保留在main下,在debug和release下分别创建个LeakManager类,Applicaiton来调用LeakManager的方法即可。

通过这些操作就可以在打release版本是不加入这些与debug有关的第三方库,减少一定的应用大小。


四、代码习惯

另外要养成良好的代码习惯,尽量去写一些高复用的代码,减少应用中的重复代码。定期对代码进行review,小规模的进行代码重构,封装一些频繁使用的工具类等。养成用最少代码完成功能的习惯和能力。这样不仅会让代码结构清晰,也会让代码量大大的减少。


五、插件化

瘦身不是插件化的主要目的,但是一款app发展到航母级别的时候就必须考虑插件化了。关于插件化有很多开源框架,而且IT大佬们也陆续开源了自己的插件化框架,这个框架各有优缺,根据自身应用的特点去选择。


六、总结

本章主要从代码角度分析优化点,其中代码这方面对app瘦身影响没有那么大,因为代码编译打包时都会经过压缩,本身占用空间不会太大。

但是追求极致的目的不是极致,而是在追求极致的过程中提升自己的能力和思维高度。

经过这三章的讲解,关于app瘦身这部分就告一段落了,我相信自己会有理解不到位的情况,而且还有很多未了解未探索的部分,希望大家多提宝贵意见!!谢谢!!


作者:chzphoenix 发表于2017/7/20 15:43:10 原文链接
阅读:12 评论:0 查看评论

Android 打包apk无敌报错

$
0
0

前言:
这个问题从昨天上午一直到现在,请教了很多大佬,都没有给出确定的解决方案,可能他们也没碰到过可能,不过还是挺感谢他们的建议,一直到今天中午午休,我一直都在想这个问题,就是下面的这个,看了国内很多博客虽然我自己也写博客,说实话真的垃圾,全是废话,解决问题的特别少!我之前一直以为是ndk的问题,后来发现不是的,我的jniLibs没有那些依赖文件,后面看了网上一些方法删掉了ndk,还是没什么卵用,后面我还是不要脸的去群里问,问题出现了,肯定要解决,之前一直编译可以,但是运行就是报下面的异常。。

这里写图片描述

https://stackoverflow.com/questions/26966843/java-util-zip-zipexception-duplicate-entry-during-packagealldebugclassesformult

解决方案:

这里写图片描述

在你的app build.gradle中移除它 就可以了不懂英文也没事,这里卖个关子。//某个文件夹下面全部依赖 听不懂没事,需要的加群号一起讨论!576077608

    compile fileTree(dir: 'libs', include: '*.jar')

结束语:
转载请注明出处,另外在这里感谢下狗蛋,是他给了我传送门我才能起飞的,谢谢!

作者:qq_15950325 发表于2017/7/21 14:49:33 原文链接
阅读:26 评论:0 查看评论

Swift基础之封装ActionSheet控件

$
0
0

前端时间封装了OC版本http://blog.csdn.net/hbblzjy/article/details/75127359,今天把写好的Swift版本共享出来,希望对大家有用;

源码下载请点击Star,star是继续写的动力,谢谢~

自定义ActionSheet的关键点,就是UI的样式修改和设计调整,还有就是点击单元格时进行的后续操作,再一个就是界面显示的平滑度。

首先界面设计:

创建一个半透明的背景视图;

然后一个表格,表格分成两个区,设置标题头、区尾和单元格边角

//创建相关控件
    var maskV = UIView()//半透明背景
    var tableView = UITableView()//表格
    var cellArray = NSArray()//表格数组
    var cancelTitle = NSString()//取消标题设置
    var headView = UIView()//标题头视图
    var selectedClosure:((Int) -> ())?//选中单元格closure
    var cancelClosure:(() -> ())?//取消单元格closure
    
    //初始化方法:参数一:头视图,参数二:表格数组,参数三:取消的标题设置,参数四:选择单元格closure,参数五:取消closure
    
    init(aHeadView:UIView,aCellArray:NSArray,aCancelTitle:NSString,aSelectedClosure:@escaping ((Int) -> ()),aCancelClosure:@escaping (() -> ())) {
        super.init(frame: headView.frame)
        
        headView = aHeadView
        cellArray = aCellArray
        cancelTitle = aCancelTitle
        selectedClosure = aSelectedClosure
        cancelClosure = aCancelClosure
        
        //创建UI视图
        self.createUI()
        
    }
    //MARK: ------------ 创建UI视图
    func createUI() {
        self.frame = UIScreen.main.bounds
        //半透明背景
        maskV = UIView.init(frame: UIScreen.main.bounds)
        maskV.backgroundColor = UIColor.black
        maskV.alpha = 0.5
        maskV.isUserInteractionEnabled = true
        self.addSubview(maskV)
        
        //表格
        tableView = UITableView.init(frame: CGRect.zero, style: .plain)
        tableView.delegate = self
        tableView.dataSource = self
        tableView.layer.cornerRadius = 10.0
        tableView.clipsToBounds = true
        tableView.rowHeight = 44.0
        tableView.bounces = false
        tableView.backgroundColor = UIColor.clear
        tableView.tableHeaderView = self.headView
        tableView.separatorInset = UIEdgeInsets.init(top: 0, left: -50, bottom: 0, right: 0)
        
        //注册单元格:+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "oneCell")
        
        self.addSubview(tableView)
    }
    //MARK:------------ UITableViewDelegate,UITableViewDataSource
    func numberOfSections(in tableView: UITableView) -> Int {
        return 2
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return (section == 0) ? cellArray.count:1
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "oneCell")
        if indexPath.section == 0 {
            cell?.textLabel?.text = cellArray[indexPath.row] as? String
            if indexPath.row == cellArray.count-1 {
                //添加贝塞尔曲线,设计边角样式UIBezierPath与CAShapeLayer
                //注意:Swift中的“或”加了一个rawValue:UIRectCorner(rawValue: UIRectCorner.bottomLeft.rawValue | UIRectCorner.bottomRight.rawValue)
                let maskPath = UIBezierPath.init(roundedRect: CGRect.init(x: 0, y: 0, width: Screen_Width - Space_Line*2, height: tableView.rowHeight), byRoundingCorners: UIRectCorner(rawValue: UIRectCorner.bottomLeft.rawValue | UIRectCorner.bottomRight.rawValue), cornerRadii: CGSize.init(width: 10, height: 10))
                let maskLayer = CAShapeLayer.init()
                maskLayer.frame = (cell?.contentView.bounds)!
                maskLayer.path = maskPath.cgPath
                cell?.layer.mask = maskLayer
            }
        }else{
            cell?.textLabel?.text = cancelTitle as String
            cell?.layer.cornerRadius = 10
        }
        
        cell?.textLabel?.textAlignment = .center
        cell?.selectionStyle = .none
        
        return cell!
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if indexPath.section == 0 {
            if (selectedClosure != nil) {
                selectedClosure!(Int(indexPath.row))
            }
        }else{
            if (cancelClosure != nil) {
                cancelClosure!()
            }
        }
        
        self.dismiss()
    }
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return Space_Line
    }
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let footerView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: tableView.bounds.size.width, height: Space_Line))
        footerView.backgroundColor = UIColor.clear
        return footerView
    }

界面设计完成,需要考虑的就是弹出、消失的问题

//MARK:------绘制视图
    override func layoutSubviews() {
        super.layoutSubviews()
        self.show()
    }
    //滑动弹出
    func show() {
        tableView.frame = CGRect.init(x: Space_Line, y: Screen_Height, width: Screen_Width-Space_Line * 2, height: tableView.rowHeight * CGFloat(cellArray.count + 1) + headView.bounds.size.height + Space_Line * 2)
        UIView.animate(withDuration: 0.5) { 
            var rect:CGRect = self.tableView.frame
            rect.origin.y -= self.tableView.bounds.size.height
            self.tableView.frame = rect
        }
    }
    //滑动消失
    func dismiss() {
        UIView.animate(withDuration: 0.5, animations: { 
            var rect:CGRect = self.tableView.frame
            rect.origin.y += self.tableView.bounds.size.height
            self.tableView.frame = rect
        }) { (finished) in
            self.removeFromSuperview()
        }
    }
    //MARK:------触摸屏幕其他位置谈下
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.dismiss()
    }

最后是对自定义的视图的调用:

//MARK:------ 弹出按钮
    func actionSheetBtnClick() {
        //弹出actionSheet
        weak var weakSelf = self
        let jasonSheetView = JasonActionSheetView.init(aHeadView: (weakSelf?.headView())!, aCellArray: (weakSelf?.dataArray())!, aCancelTitle: "取消", aSelectedClosure: { (index) in
            //点击单元格后的操作
            if index == 0 {
                weakSelf?.view.backgroundColor = UIColor.red
            }else if index == 1 {
                weakSelf?.view.backgroundColor = UIColor.yellow
            }else{
                weakSelf?.view.backgroundColor = UIColor.lightGray
            }
            
        }) { 
            weakSelf?.view.backgroundColor = UIColor.white
        }
        weakSelf?.view.addSubview(jasonSheetView)
        
    }

效果图:


如果喜欢请点击Star,谢谢~

源码下载:https://github.com/hbblzjy/SwiftActionSheetDemo




作者:hbblzjy 发表于2017/7/21 17:18:09 原文链接
阅读:84 评论:0 查看评论

Android 开发 Tip 17 -- 为什么getBackground().setAlpha(); 会影响别的控件?

$
0
0

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


http://www.jb51.net/article/110035.htm

https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653578233&idx=1&sn=aea773c1e815fdef910fba28d765940b&chksm=84b3b1feb3c438e8372850a36bdcb87fdfb1ca793793a7c9598bcc792aabbb0f417b7a32c989&mpshare=1&scene=1&srcid=1117pJi4bqTWG94d2q3Dl65f#wechat_redirect

/**
     * Make this drawable mutable. This operation cannot be reversed. A mutable
     * drawable is guaranteed to not share its state with any other drawable.
     * This is especially useful when you need to modify properties of drawables
     * loaded from resources. By default, all drawables instances loaded from
     * the same resource share a common state; if you modify the state of one
     * instance, all the other instances will receive the same modification.
     *
     * Calling this method on a mutable Drawable will have no effect.
     *
     * @return This drawable.
     * @see ConstantState
     * @see #getConstantState()
     */
    public @NonNull Drawable mutate() {
        return this;
    }
作者:crazy1235 发表于2017/7/21 18:25:00 原文链接
阅读:54 评论:0 查看评论

Android NDK: From Elementary to Expert Episode 20

$
0
0

Debug Session Setup
The ndk-gdb script that takes care of setting up the debug session on behalf of the developer but knows the sequence of events happening during the debug session setup, which is very beneficial to understanding the caveats of debugging native code on Android. The complete sequence of events during the debug session setup is shown in Figure.
这里写图片描述
The ndk-gdb script launches the target application by using the application manager through ADB. The application manager simply relays the request to Zygote process.
Zygote, also known as the “app process,” is one of the core processes started when the Android system boots. Its role within the Android platform is to start the Dalvik virtual machine and initialize all core Android services. As a mobile operating system, Android needs to keep the startup time of applications as small as possible in order to provide a highly responsive user experience. In order to achieve that, instead of starting a new process from scratch for the applications, Zygote simply relies on forking. In computing, forking is the operation to clone an existing process. The new process has an exact copy of all memory segments of the parent process, although both processes execute independently.
At this point in time, the application is started and is executing code. As you may have noticed, the debug session is not established yet at this point.

作者:myfather103 发表于2017/7/21 18:30:24 原文链接
阅读:48 评论:0 查看评论

防侧漏之弱引用的使用

$
0
0

本文依然基于github上的开源框架为基础,看过之前发的 最新Retrofit + RxJava + MVP 那篇blog的讲述,应该明白框架里面的大概,一步步兑现之前的承诺,会写上十篇左右的帖子来讲解里面的要点和难点,今天主要讲述的是baseActivity里面的WeakReference< BaseActivity >。

最初入行的时候,使用handler一般都是如下方式:

private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            /**
             * 各种操作
             */
        }
    };

先说下此处的问题,首先,系统会有警告,This Handler class should be static or leaks might occur,大致意思就是说:Handler类应该定义成静态类,否则可能导致内存泄露,因为非静态内部类隐式自动持有外部类的强引用,而静态内部类不会引用外部类对象,目前只需要记住这句话,说到根本原因就牵扯到jvm,这块日后会单独拿出来写一个模块。

再者,这样写的话,如果有一个超生命周期的逻辑,则会出现内存泄漏,百说不如贴上代码:

    private void test() {
        handler.sendMessageDelayed(Message.obtain(), 60000);
        finish();
    }

当Android应用启动的时候,会先创建一个UI主线程的Looper对象,同时会创建一个MessageQueue,Looper循环遍历,把MessageQueue中的message一个一个取出来处理,处理结束后并不会销毁(这和java中消息机制不同,java中处理完便会自动销毁,等待回收),而是等待后续传来的message,只要Handler发送的Message尚未被处理,则该Message及发送它的Handler对象将被线程MessageQueue一直持有,上述代码是activity在finish一分钟后才收到信息,此时的activity中的handler还在被强引用,当这个activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的message持有handler实例的引用,handler又持有activity的引用,所以导致该activity的内存资源无法及时回收,引发内存泄漏,关于handler这块,之前一篇android之handler的刨根问底 进行了详细叙述,有兴趣可以看下。

为何使用弱引用

对于强、软、弱、虚四大引用,之前在简述图片加载框架有过讲述并有代码实例,此处不重复贴代码,对于弱引用,指的是当垃圾回收器扫描到此处垃圾,无论内存是否充足,都会回收,功能上和软引用如出一辙,和软引用最大的不同就是软引用是在内存不足的时候,gc扫描到才会回收此处内存。此处使用弱引用比软引用更加合理,虽然软引用同样可以解决因为强引用导致内存无法回收的问题,但有个前提条件就是需要内存不足的时候才可以,这就没有弱引用来得实在。

改进后的代码如下:

/**
 * Created by Zero on 2017/7/20.
 */
public abstract class BaseActivity<Pre extends BasePresenter> extends AppCompatActivity implements OnClickListener {

    private static final String DIALOG_LOADING = "DialogLoading";
    private boolean mVisible;
    private LoadingDialogFragment waitDialog = null;

    protected Pre presenter;
    protected final Handler mHandler = new MyHandler(this);
    private BroadcastReceiver receiver;
    private IntentFilter filter;

    private static class MyHandler extends Handler {

        private final WeakReference<BaseActivity> mActivity;

        private MyHandler(BaseActivity activity) {
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            if (mActivity.get() != null) {
                requestOver(msg);
            }
        }
    }

    /*****************省略一些不相关代码*****************/

    @Override
    protected void onDestroy() {
        super.onDestroy();
        /**
         * 移除mHandler
         */
        mHandler.removeCallbacksAndMessages(null);
        if (receiver != null) {
            LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
        }
    }
}

为了避免一些handler拖泥带水,onDestroy方法中对mHandler进行removeCallbacksAndMessages(null)处理,便于mHandler和activity及时被回收。

项目已上传,戳此进入github。

作者:pangpang123654 发表于2017/7/21 18:54:49 原文链接
阅读:163 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


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