月度归档: 2021 年 6 月

iOS APP内测邀请之TestFlight

用了一早上,从一头雾水,到实际操作,理出个头绪,防遗忘&分享。
TestFlight:iOS APP发布后一般要通过0~2天的审核,测试*后阶段要用线上的APP去验证一下有没有什么问题,就可以用TestFlight给指定用户发送邀请码,下载*新版APP(Beta测试版)。TestFlight是APP Store的一个APP,一个非常有用的APP。

添加TestFlight的测试员的大概的流程,照着做就好了

 

在 iTunes Connect 中发送 TestFlight 内测邀请的步骤

无法对 iOS App 进行升级测试,怎么办?答案是使用 TestFlight。

TestFlight 是一款在 2014 年被苹果收购的 iOS 测试工具。虽然首次使用比较麻烦,但是熟悉了整个流程之后,用起来还是很方便的,值得推荐。

因为使用 TestFlight 进行测试有一定的门槛,所以就有了这个教程。


一.打包 ipa 并上传到 App Store

此处省略,如果连这个都不会,那就不用看了。


二.添加 TestFlight 测试信息

  • 2.1 在浏览器中打开 【iTunes Connect】,使用开发者账号登录,先选择【我的App】,再选择你要测试的 App :
  • 2.2 选择【TestFlight】-【测试信息】,填写【Beta 版 App 描述】、【反馈电子邮件】、【营销URL】、【隐私政策网址】、【Beta 版 App 审核信息】:
  • 2.3 填写完毕之后,点击右上角的【存储】按钮。

三.添加用户和职能

  • 3.1 点击【我的App】-【用户和职能】:
  • 3.2 点击【加号】按钮:
  • 3.3 根据弹窗来添加【用户】即可。
  • 3.4 被添加的用户注意查收邮件,根据邮件链接接收邀请。

四.提交审核

  • 4.1 等到 IPA 处理完毕之后,点击【提交以供审核】按钮:

如果未提交审核,则无法进行第六步操作。


五.添加测试员

  • 5.1 选择【TestFlight】 – 【iTunes Connect用户】,点击【加号】按钮:
  • 5.2 在弹窗中选择已接受邀请的【iTunes Connect用户】,点击【添加】按钮:
  • 5.3 被添加的用户会【再次】收到邮件,根据邮件中的提示操作即可(邮件中包含 TestFlight iOS 端的操作流程)。

六.添加外部测试员

  • 6.1 选择【添加外部测试员】:
  • 6.2 在弹窗中创建群组:
  • 6.3 选择【测试员】,点击【加号】按钮:
  • 6.4 在弹窗中添加测试员:

七.构建版本

  • 7.1 选择【构建版本】,点击【加号】按钮:
  • 7.2 选择要测试的构建版本:
  • 7.3 根据自己的情况填写【测试信息】即可:

此教程至此结束。


如果写得不好,请大家指正!!!

IOS进阶 – CALayer的子类

CAShapeLayer

CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类。你指定诸如颜色和线宽等属性,用CGPath来定义想要绘制的图形,*后CAShapeLayer就自动渲染出来了。当然,你也可以用Core Graphics直接向原始的CALyer的内容中绘制一个路径,相比直下,使用CAShapeLayer有以下一些优点:

  • 渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Graphics快很多。
  • 高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
  • 不会被图层边界剪裁掉。一个CAShapeLayer可以在边界之外绘制。你的图层路径不会像在使用Core Graphics的普通CALayer一样被剪裁掉。
  • 不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。

主要属性

  1. // CAShapeLayer 绘制的路径
  2. @property(nullable) CGPathRef path;
  3. //路径中的填充颜色
  4. @property(nullable) CGColorRef fillColor;
  5. //填充规则
  6. //路径中的填充颜色
  7. @property(nullable) CGFloat fillColor;
  8. @property(copy) NSString *fillRule;
  9. //画笔颜色(路径的颜色,边框颜色)
  10. @property(nullable) CGColorRef strokeColor;
  11. //这是一组范围值,路径绘制开始和结束的范围(0 -> 1)
  12. @property CGFloat strokeStart;
  13. @property CGFloat strokeEnd;
  14. //设置虚线显示的起点距离,设置为8,则显示长度8之后的线
  15. @property CGFloat lineDashPhase;
  16. //设置虚线线段的长度和空格的长度,@[@20,@30,@40,@50],画20空30画40空50
  17. @property(nullable, copy) NSArray

创建一个CGPath

CAShapeLayer可以用来绘制所有能够通过CGPath来表示的形状。这个形状不一定要闭合,图层路径也不一定要不可破,事实上你可以在一个图层上绘制好几个不同的形状。你可以控制一些属性比如lineWith(线宽,用点表示单位),lineCap(线条结尾的样子),和lineJoin(线条之间的结合点的样子);但是在图层层面你只有一次机会设置这些属性。如果你想用不同颜色或风格来绘制多个形状,就不得不为每个形状准备一个图层了。

CAShapeLayer属性是CGPathRef类型,但是我们用UIBezierPath帮助类创建了图层路径,这样我们就不用考虑人工释放CGPath了。

示例:

  1. #import “DrawingView.h”
  2. #import <QuartzCore/QuartzCore.h>
  3. @interface ViewController ()
  4. @property (nonatomic, weak) IBOutlet UIView *containerView;
  5. @end
  6. @implementation ViewController
  7. – (void)viewDidLoad{
  8. [super viewDidLoad];
  9. //create path
  10. UIBezierPath *path = [[UIBezierPath alloc] init];
  11. [path moveToPoint:CGPointMake(175, 100)];
  12. [path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES];
  13. [path moveToPoint:CGPointMake(150, 125)];
  14. [path addLineToPoint:CGPointMake(150, 175)];
  15. [path addLineToPoint:CGPointMake(125, 225)];
  16. [path moveToPoint:CGPointMake(150, 175)];
  17. [path addLineToPoint:CGPointMake(175, 225)];
  18. [path moveToPoint:CGPointMake(100, 150)];
  19. [path addLineToPoint:CGPointMake(200, 150)];
  20. //create shape layer
  21. CAShapeLayer *shapeLayer = [CAShapeLayer layer];
  22. shapeLayer.strokeColor = [UIColor redColor].CGColor;
  23. shapeLayer.fillColor = [UIColor clearColor].CGColor;
  24. shapeLayer.lineWidth = 5;
  25. shapeLayer.lineJoin = kCALineJoinRound;
  26. shapeLayer.lineCap = kCALineCapRound;
  27. shapeLayer.path = path.CGPath;
  28. //add it to our view
  29. [self.containerView.layer addSublayer:shapeLayer];
  30. }
  31. @end

图6.1

圆角

相比于CALayer的cornerRadius属性, CAShapeLayer为创建圆角视图提供了一个方法,可以单独指定每个角。虽然使用CAShapeLayer类需要更多的工作,但是它有一个优势就是可以单独指定每个角。

我们创建圆角矩形其实就是人工绘制单独的直线和弧度,但是事实上UIBezierPath有自动绘制圆角矩形的构造方法,下面这段代码绘制了一个有三个圆角一个直角的矩形:

//define path parameters
CGRect rect = CGRectMake(50, 50, 100, 100);
CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
//create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];

我们可以通过这个图层路径绘制一个既有直角又有圆角的视图。如果我们想依照此图形来剪裁视图内容,我们可以把CAShapeLayer作为视图的宿主图层,而不是添加一个子视图。

CATextLayer

用户界面是无法从一个单独的图片里面构建的。一个设计良好的图标能够很好地表现一个按钮或控件的意图,不过你迟早都要需要一个不错的老式风格的文本标签。

如果你想在一个图层里面显示文字,完全可以借助图层代理直接将字符串使用Core Graphics写入图层的内容(这就是UILabel的精髓)。如果越过寄宿于图层的视图,直接在图层上操作,那其实相当繁琐。你要为每一个显示文字的图层创建一个能像图层代理一样工作的类,还要逻辑上判断哪个图层需要显示哪个字符串,更别提还要记录不同的字体,颜色等一系列乱七八糟的东西。

万幸的是这些都是不必要的,Core Animation提供了一个CALayer的子类CATextLayer,它以图层的形式包含了UILabel几乎所有的绘制特性,并且额外提供了一些新的特性。

同样,CATextLayer也要比UILabel渲染得快得多。很少有人知道在iOS 6及之前的版本,UILabel其实是通过WebKit来实现绘制的,这样就造成了当有很多文字的时候就会有*大的性能压力。而CATextLayer使用了Core text,并且渲染得非常快。

让我们来尝试用CATextLayer来显示一些文字。清单6.2的代码实现了这一功能,结果如图6.2所示。

清单6.2 用CATextLayer来实现一个UILabel

 

  1. @interface ViewController ()
  2. @property (nonatomic, weak) IBOutlet UIView *labelView;
  3. @end
  4. @implementation ViewController
  5. – (void)viewDidLoad{
  6. [super viewDidLoad];
  7. //create a text layer
  8. CATextLayer *textLayer = [CATextLayer layer];
  9. textLayer.frame = self.labelView.bounds;
  10. [self.labelView.layer addSublayer:textLayer];
  11. //set text attributes
  12. textLayer.foregroundColor = [UIColor blackColor].CGColor;
  13. textLayer.alignmentMode = kCAAlignmentJustified;
  14. textLayer.wrapped = YES;
  15. //choose a font
  16. UIFont *font = [UIFont systemFontOfSize:15];
  17. //set layer font
  18. CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  19. CGFontRef fontRef = CGFontCreateWithFontName(fontName);
  20. textLayer.font = fontRef;
  21. textLayer.fontSize = font.pointSize;
  22. CGFontRelease(fontRef);
  23. //choose some text
  24. NSString *text = @”Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis”;
  25. //set layer text
  26. textLayer.string = text;
  27. }
  28. @end

图6.2

图6.2 用CATextLayer来显示一个纯文本标签

如果你仔细看这个文本,你会发现一个奇怪的地方:这些文本有一些像素化了。这是因为并没有以Retina的方式渲染,第二章提到了这个contentScale属性,用来决定图层内容应该以怎样的分辨率来渲染。contentsScale并不关心屏幕的拉伸因素而总是默认为1.0。如果我们想以Retina的质量来显示文字,我们就得手动地设置CATextLayercontentsScale属性,如下:

textLayer.contentsScale = [UIScreen mainScreen].scale;

 

这样就解决了这个问题(如图6.3)

图6.3

图6.3 设置contentsScale来匹配屏幕

CATextLayerfont属性不是一个UIFont类型,而是一个CFTypeRef类型。这样可以根据你的具体需要来决定字体属性应该是用CGFontRef类型还是CTFontRef类型(Core Text字体)。同时字体大小也是用fontSize属性单独设置的,因为CTFontRefCGFontRef并不像UIFont一样包含点大小。这个例子会告诉你如何将UIFont转换成CGFontRef

另外,CATextLayerstring属性并不是你想象的NSString类型,而是id类型。这样你既可以用NSString也可以用NSAttributedString来指定文本了(注意,NSAttributedString并不是NSString的子类)。属性化字符串是iOS用来渲染字体风格的机制,它以特定的方式来决定指定范围内的字符串的原始信息,比如字体,颜色,字重,斜体等。

富文本

iOS 6中,Apple给UILabel和其他UIKit文本视图添加了直接的属性化字符串的支持,应该说这是一个很方便的特性。不过事实上从iOS3.2开始CATextLayer就已经支持属性化字符串了。这样的话,如果你想要支持更低版本的iOS系统,CATextLayer无疑是你向界面中增加富文本的好办法,而且也不用去跟复杂的Core Text打交道,也省了用UIWebView的麻烦。

让我们编辑一下示例使用到NSAttributedString(见清单6.3).iOS 6及以上我们可以用新的NSTextAttributeName实例来设置我们的字符串属性,但是练习的目的是为了演示在iOS 5及以下,所以我们用了Core Text,也就是说你需要把Core Text framework添加到你的项目中。否则,编译器是无法识别属性常量的。

图6.4是代码运行结果(注意那个红色的下划线文本)

清单6.3 用NSAttributedString实现一个富文本标签。

 

  1. #import “DrawingView.h”
  2. #import <QuartzCore/QuartzCore.h>
  3. #import <CoreText/CoreText.h>
  4. @interface ViewController ()
  5. @property (nonatomic, weak) IBOutlet UIView *labelView;
  6. @end
  7. @implementation ViewController
  8. – (void)viewDidLoad{
  9. [super viewDidLoad];
  10. //create a text layer
  11. CATextLayer *textLayer = [CATextLayer layer];
  12. textLayer.frame = self.labelView.bounds;
  13. textLayer.contentsScale = [UIScreen mainScreen].scale;
  14. [self.labelView.layer addSublayer:textLayer];
  15. //set text attributes
  16. textLayer.alignmentMode = kCAAlignmentJustified;
  17. textLayer.wrapped = YES;
  18. //choose a font
  19. UIFont *font = [UIFont systemFontOfSize:15];
  20. //choose some text
  21. NSString *text = @”Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc \ elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis”;
  22. //create attributed string
  23. NSMutableAttributedString *string = nil;
  24. string = [[NSMutableAttributedString alloc] initWithString:text];
  25. //convert UIFont to a CTFont
  26. CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  27. CGFloat fontSize = font.pointSize;
  28. CTFontRef fontRef = CTFontCreateWithName(fontName, fontSize, NULL);
  29. //set text attributes
  30. NSDictionary *attribs = @{
  31. (__bridge id)kCTForegroundColorAttributeName:(__bridge id)[UIColor blackColor].CGColor,
  32. (__bridge id)kCTFontAttributeName: (__bridge id)fontRef
  33. };
  34. [string setAttributes:attribs range:NSMakeRange(0, [text length])];
  35. attribs = @{
  36. (__bridge id)kCTForegroundColorAttributeName: (__bridge id)[UIColor redColor].CGColor,
  37. (__bridge id)kCTUnderlineStyleAttributeName: @(kCTUnderlineStyleSingle),
  38. (__bridge id)kCTFontAttributeName: (__bridge id)fontRef
  39. };
  40. [string setAttributes:attribs range:NSMakeRange(6, 5)];
  41. //release the CTFont we created earlier
  42. CFRelease(fontRef);
  43. //set layer text
  44. textLayer.string = string;
  45. }
  46. @end

图6.4

图6.4 用CATextLayer实现一个富文本标签。

行距和字距

有必要提一下的是,由于绘制的实现机制不同(Core Text和WebKit),用CATextLayer渲染和用UILabel渲染出的文本行距和字距也不是不尽相同的。

二者的差异程度(由使用的字体和字符决定)总的来说挺小,但是如果你想正确的显示普通便签和CATextLayer就一定要记住这一点。

UILabel的替代品

我们已经证实了CATextLayerUILabel有着更好的性能表现,同时还有额外的布局选项并且在iOS 5上支持富文本。但是与一般的标签比较而言会更加繁琐一些。如果我们真的在需求一个UILabel的可用替代品,*好是能够在Interface Builder上创建我们的标签,而且尽可能地像一般的视图一样正常工作。

我们应该继承UILabel,然后添加一个子图层CATextLayer并重写显示文本的方法。但是仍然会有由UILabel-drawRect:方法创建的空寄宿图。而且由于CALayer不支持自动缩放和自动布局,子视图并不是主动跟踪视图边界的大小,所以每次视图大小被更改,我们不得不手动更新子图层的边界。

我们真正想要的是一个用CATextLayer作为宿主图层的UILabel子类,这样就可以随着视图自动调整大小而且也没有冗余的寄宿图啦。

就像我们在*章『图层树』讨论的一样,每一个UIView都是寄宿在一个CALayer的示例上。这个图层是由视图自动创建和管理的,那我们可以用别的图层类型替代它么?一旦被创建,我们就无法代替这个图层了。但是如果我们继承了UIView,那我们就可以重写+layerClass方法使得在创建的时候能返回一个不同的图层子类。UIView会在初始化的时候调用+layerClass方法,然后用它的返回类型来创建宿主图层。

清单6.4 演示了一个UILabel子类LayerLabelCATextLayer绘制它的问题,而不是调用一般的UILabel使用的较慢的-drawRect:方法。LayerLabel示例既可以用代码实现,也可以在Interface Builder实现,只要把普通的标签拖入视图之中,然后设置它的类是LayerLabel就可以了。

清单6.4 使用CATextLayerUILabel子类:LayerLabel

 

  1. #import “LayerLabel.h”
  2. #import <QuartzCore/QuartzCore.h>
  3. @implementation LayerLabel
  4. + (Class)layerClass{
  5. //this makes our label create a CATextLayer //instead of a regular CALayer for its backing layer
  6. return [CATextLayer class];
  7. }
  8. – (CATextLayer *)textLayer{
  9. return (CATextLayer *)self.layer;
  10. }
  11. – (void)setUp{
  12. //set defaults from UILabel settings
  13. self.text = self.text;
  14. self.textColor = self.textColor;
  15. self.font = self.font;
  16. //we should really derive these from the UILabel settings too
  17. //but that’s complicated, so for now we’ll just hard-code them
  18. [self textLayer].alignmentMode = kCAAlignmentJustified;
  19. [self textLayer].wrapped = YES;
  20. [self.layer display];
  21. }
  22. – (id)initWithFrame:(CGRect)frame{
  23. //called when creating label programmatically
  24. if (self = [super initWithFrame:frame]) {
  25. [self setUp];
  26. }
  27. return self;
  28. }
  29. – (void)awakeFromNib{
  30. //called when creating label using Interface Builder
  31. [self setUp];
  32. }
  33. – (void)setText:(NSString *)text{
  34. super.text = text;
  35. //set layer text
  36. [self textLayer].string = text;
  37. }
  38. – (void)setTextColor:(UIColor *)textColor{
  39. super.textColor = textColor;
  40. //set layer text color
  41. [self textLayer].foregroundColor = textColor.CGColor;
  42. }
  43. – (void)setFont:(UIFont *)font{
  44. super.font = font;
  45. //set layer font
  46. CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  47. CGFontRef fontRef = CGFontCreateWithFontName(fontName);
  48. [self textLayer].font = fontRef;
  49. [self textLayer].fontSize = font.pointSize;
  50. CGFontRelease(fontRef);
  51. }
  52. @end

如果你运行代码,你会发现文本并没有像素化,而我们也没有设置contentsScale属性。把CATextLayer作为宿主图层的另一好处就是视图自动设置了contentsScale属性。

在这个简单的例子中,我们只是实现了UILabel的一部分风格和布局属性,不过稍微再改进一下我们就可以创建一个支持UILabel所有功能甚至更多功能的LayerLabel类(你可以在一些线上的开源项目中找到)。

如果你打算支持iOS 6及以上,基于CATextLayer的标签可能就有有些局限性。但是总得来说,如果想在app里面充分利用CALayer子类,用+layerClass来创建基于不同图层的视图是一个简单可复用的方法。

CATransformLayer

当我们在构造复杂的3D事物的时候,如果能够组织独立元素就太方便了。比如说,你想创造一个孩子的手臂:你就需要确定哪一部分是孩子的手腕,哪一部分是孩子的前臂,哪一部分是孩子的肘,哪一部分是孩子的上臂,哪一部分是孩子的肩膀等等。

当然是允许独立地移动每个区域的啦。以肘为指点会移动前臂和手,而不是肩膀。Core Animation图层很容易就可以让你在2D环境下做出这样的层级体系下的变换,但是3D情况下就不太可能,因为所有的图层都把他的孩子都平面化到一个场景中(第五章『变换』有提到)。

CATransformLayer解决了这个问题,CATransformLayer不同于普通的CALayer,因为它不能显示它自己的内容。只有当存在了一个能作用域子图层的变换它才真正存在。CATransformLayer并不平面化它的子图层,所以它能够用于构造一个层级的3D结构,比如我的手臂示例。

用代码创建一个手臂需要相当多的代码,所以我就演示得更简单一些吧:在第五章的立方体示例,我们将通过旋转camara来解决图层平面化问题而不是像立方体示例代码中用的sublayerTransform。这是一个非常不错的技巧,但是只能作用域单个对象上,如果你的场景包含两个立方体,那我们就不能用这个技巧单独旋转他们了。

那么,就让我们来试一试CATransformLayer吧,*个问题就来了:在第五章,我们是用多个视图来构造了我们的立方体,而不是单独的图层。我们不能在不打乱已有的视图层次的前提下在一个本身不是有寄宿图的图层中放置一个寄宿图图层。我们可以创建一个新的UIView子类寄宿在CATransformLayer(用+layerClass方法)之上。但是,为了简化案例,我们仅仅重建了一个单独的图层,而不是使用视图。这意味着我们不能像第五章一样在立方体表面显示按钮和标签,不过我们现在也用不到这个特性。

清单6.5就是代码。我们以我们在第五章使用过的相同基本逻辑放置立方体。但是并不像以前那样直接将立方面添加到容器视图的宿主图层,我们将他们放置到一个CATransformLayer中创建一个独立的立方体对象,然后将两个这样的立方体放进容器中。我们随机地给立方面染色以将他们区分开来,这样就不用靠标签或是光亮来区分他们。图6.5是运行结果。

清单6.5 用CATransformLayer装配一个3D图层体系

  1. @interface ViewController ()
  2. @property (nonatomic, weak) IBOutlet UIView *containerView;
  3. @end
  4. @implementation ViewController
  5. – (CALayer *)faceWithTransform:(CATransform3D)transform{
  6. //create cube face layer
  7. CALayer *face = [CALayer layer];
  8. face.frame = CGRectMake(-50, -50, 100, 100);
  9. //apply a random color
  10. CGFloat red = (rand() / (double)INT_MAX);
  11. CGFloat green = (rand() / (double)INT_MAX);
  12. CGFloat blue = (rand() / (double)INT_MAX);
  13. face.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
  14. //apply the transform and return
  15. face.transform = transform;
  16. return face;
  17. }
  18. – (CALayer *)cubeWithTransform:(CATransform3D)transform{
  19. //create cube layer
  20. CATransformLayer *cube = [CATransformLayer layer];
  21. //add cube face 1
  22. CATransform3D ct = CATransform3DMakeTranslation(0, 0, 50);
  23. [cube addSublayer:[self faceWithTransform:ct]];
  24. //add cube face 2
  25. ct = CATransform3DMakeTranslation(50, 0, 0);
  26. ct = CATransform3DRotate(ct, M_PI_2, 0, 1, 0);
  27. [cube addSublayer:[self faceWithTransform:ct]];
  28. //add cube face 3
  29. ct = CATransform3DMakeTranslation(0, -50, 0);
  30. ct = CATransform3DRotate(ct, M_PI_2, 1, 0, 0);
  31. [cube addSublayer:[self faceWithTransform:ct]];
  32. //add cube face 4
  33. ct = CATransform3DMakeTranslation(0, 50, 0);
  34. ct = CATransform3DRotate(ct, -M_PI_2, 1, 0, 0);
  35. [cube addSublayer:[self faceWithTransform:ct]];
  36. //add cube face 5
  37. ct = CATransform3DMakeTranslation(-50, 0, 0);
  38. ct = CATransform3DRotate(ct, -M_PI_2, 0, 1, 0);
  39. [cube addSublayer:[self faceWithTransform:ct]];
  40. //add cube face 6
  41. ct = CATransform3DMakeTranslation(0, 0, -50);
  42. ct = CATransform3DRotate(ct, M_PI, 0, 1, 0);
  43. [cube addSublayer:[self faceWithTransform:ct]];
  44. //center the cube layer within the container
  45. CGSize containerSize = self.containerView.bounds.size;
  46. cube.position = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
  47. //apply the transform and return
  48. cube.transform = transform;
  49. return cube;
  50. }
  51. – (void)viewDidLoad{
  52. [super viewDidLoad];
  53. //set up the perspective transform
  54. CATransform3D pt = CATransform3DIdentity;
  55. pt.m34 = -1.0 / 500.0;
  56. self.containerView.layer.sublayerTransform = pt;
  57. //set up the transform for cube 1 and add it
  58. CATransform3D c1t = CATransform3DIdentity;
  59. c1t = CATransform3DTranslate(c1t, -100, 0, 0);
  60. CALayer *cube1 = [self cubeWithTransform:c1t];
  61. [self.containerView.layer addSublayer:cube1];
  62. //set up the transform for cube 2 and add it
  63. CATransform3D c2t = CATransform3DIdentity;
  64. c2t = CATransform3DTranslate(c2t, 100, 0, 0);
  65. c2t = CATransform3DRotate(c2t, -M_PI_4, 1, 0, 0);
  66. c2t = CATransform3DRotate(c2t, -M_PI_4, 0, 1, 0);
  67. CALayer *cube2 = [self cubeWithTransform:c2t];
  68. [self.containerView.layer addSublayer:cube2];
  69. }
  70. @end

图6.5

图6.5 同一视角下的俩不同变换的立方体

CAGradientLayer

CAGradientLayer是用来生成两种或更多颜色平滑渐变的。用Core Graphics复制一个CAGradientLayer并将内容绘制到一个普通图层的寄宿图也是有可能的,但是CAGradientLayer的真正好处在于绘制使用了硬件加速。

基础渐变

我们将从一个简单的红变蓝的对角线渐变开始(见清单6.6).这些渐变色彩放在一个数组中,并赋给colors属性。这个数组成员接受CGColorRef类型的值(并不是从NSObject派生而来),所以我们要用通过bridge转换以确保编译正常。

CAGradientLayer也有startPointendPoint属性,他们决定了渐变的方向。这两个参数是以单位坐标系进行的定义,所以左上角坐标是{0, 0},右下角坐标是{1, 1}。代码运行结果如图6.6

清单6.6 简单的两种颜色的对角线渐变

  1. @interface ViewController ()
  2. @property (nonatomic, weak) IBOutlet UIView *containerView;
  3. @end
  4. @implementation ViewController
  5. – (void)viewDidLoad{
  6. [super viewDidLoad];
  7. //create gradient layer and add it to our container view
  8. CAGradientLayer *gradientLayer = [CAGradientLayer layer];
  9. gradientLayer.frame = self.containerView.bounds;
  10. [self.containerView.layer addSublayer:gradientLayer];
  11. //set gradient colors
  12. gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor blueColor].CGColor];
  13. //set gradient start and end points
  14. gradientLayer.startPoint = CGPointMake(0, 0);
  15. gradientLayer.endPoint = CGPointMake(1, 1);
  16. }
  17. @end

图6.6

图6.6 用CAGradientLayer实现简单的两种颜色的对角线渐变

多重渐变

如果你愿意,colors属性可以包含很多颜色,所以创建一个彩虹一样的多重渐变也是很简单的。默认情况下,这些颜色在空间上均匀地被渲染,但是我们可以用locations属性来调整空间。locations属性是一个浮点数值的数组(以NSNumber包装)。这些浮点数定义了colors属性中每个不同颜色的位置,同样的,也是以单位坐标系进行标定。0.0代表着渐变的开始,1.0代表着结束。

locations数组并不是强制要求的,但是如果你给它赋值了就一定要确保locations的数组大小和colors数组大小一定要相同,否则你将会得到一个空白的渐变。

清单6.7展示了一个基于清单6.6的对角线渐变的代码改造。现在变成了从红到黄*后到绿色的渐变。locations数组指定了0.0,0.25和0.5三个数值,这样这三个渐变就有点像挤在了左上角。(如图6.7).

清单6.7 在渐变上使用locations

  1. – (void)viewDidLoad {
  2. [super viewDidLoad];
  3. //create gradient layer and add it to our container view
  4. CAGradientLayer *gradientLayer = [CAGradientLayer layer];
  5. gradientLayer.frame = self.containerView.bounds;
  6. [self.containerView.layer addSublayer:gradientLayer];
  7. //set gradient colors
  8. gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id) [UIColor yellowColor].CGColor, (__bridge id)[UIColor greenColor].CGColor];
  9. //set locations
  10. gradientLayer.locations = @[@0.0, @0.25, @0.5];
  11. //set gradient start and end points
  12. gradientLayer.startPoint = CGPointMake(0, 0);
  13. gradientLayer.endPoint = CGPointMake(1, 1);
  14. }

图6.7

图6.7 用locations构造偏移至左上角的三色渐变

CAReplicatorLayer

CAReplicatorLayer的目的是为了高效生成许多相似的图层。它会绘制一个或多个图层的子图层,并在每个复制体上应用不同的变换。看上去演示能够更加解释这些,我们来写个例子吧。

重复图层(Repeating Layers)

清单6.8中,我们在屏幕的中间创建了一个小白色方块图层,然后用CAReplicatorLayer生成十个图层组成一个圆圈。instanceCount属性指定了图层需要重复多少次。instanceTransform指定了一个CATransform3D3D变换(这种情况下,下一图层的位移和旋转将会移动到圆圈的下一个点)。

变换是逐步增加的,每个实例都是相对于前一实例布局。这就是为什么这些复制体*终不会出现在同意位置上,图6.8是代码运行结果。

清单6.8 用CAReplicatorLayer重复图层

  1. @interface ViewController ()
  2. @property (nonatomic, weak) IBOutlet UIView *containerView;
  3. @end
  4. @implementation ViewController
  5. – (void)viewDidLoad{
  6. [super viewDidLoad];
  7. //create a replicator layer and add it to our view
  8. CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
  9. replicator.frame = self.containerView.bounds;
  10. [self.containerView.layer addSublayer:replicator];
  11. //configure the replicator
  12. replicator.instanceCount = 10;
  13. //apply a transform for each instance
  14. CATransform3D transform = CATransform3DIdentity;
  15. transform = CATransform3DTranslate(transform, 0, 200, 0);
  16. transform = CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1);
  17. transform = CATransform3DTranslate(transform, 0, -200, 0);
  18. replicator.instanceTransform = transform;
  19. //apply a color shift for each instance
  20. replicator.instanceBlueOffset = -0.1;
  21. replicator.instanceGreenOffset = -0.1;
  22. //create a sublayer and place it inside the replicator
  23. CALayer *layer = [CALayer layer];
  24. layer.frame = CGRectMake(100.0f, 100.0f, 100.0f, 100.0f);
  25. layer.backgroundColor = [UIColor whiteColor].CGColor;
  26. [replicator addSublayer:layer];
  27. }
  28. @end<span style=“font-family: verdana, ‘ms song’, 宋体, Arial, 微软雅黑, Helvetica, sans-serif; background-color: rgb(255, 255, 255);”> </span>

图6.8

图6.8 用CAReplicatorLayer创建一圈图层

注意到当图层在重复的时候,他们的颜色也在变化:这是用instanceBlueOffsetinstanceGreenOffset属性实现的。通过逐步减少蓝色和绿色通道,我们逐渐将图层颜色转换成了红色。这个复制效果看起来很酷,但是CAReplicatorLayer真正应用到实际程序上的场景比如:一个游戏中导弹的轨迹云,或者粒子爆炸(尽管iOS 5已经引入了CAEmitterLayer,它更适合创建任意的粒子效果)。除此之外,还有一个实际应用是:反射。

反射

使用CAReplicatorLayer并应用一个负比例变换于一个复制图层,你就可以创建指定视图(或整个视图层次)内容的镜像图片,这样就创建了一个实时的『反射』效果。让我们来尝试实现这个创意:指定一个继承于UIViewReflectionView,它会自动产生内容的反射效果。实现这个效果的代码很简单(见清单6.9),实际上用ReflectionView实现这个效果会更简单,我们只需要把ReflectionView的实例放置于Interface Builder(见图6.9),它就会实时生成子视图的反射,而不需要别的代码(见图6.10).

清单6.9 用CAReplicatorLayer自动绘制反射

  1. #import “ReflectionView.h”
  2. #import <QuartzCore/QuartzCore.h>
  3. @implementation ReflectionView
  4. + (Class)layerClass{
  5. return [CAReplicatorLayer class];
  6. }
  7. – (void)setUp{
  8. //configure replicator
  9. CAReplicatorLayer *layer = (CAReplicatorLayer *)self.layer;
  10. layer.instanceCount = 2;
  11. //move reflection instance below original and flip vertically
  12. CATransform3D transform = CATransform3DIdentity;
  13. CGFloat verticalOffset = self.bounds.size.height + 2;
  14. transform = CATransform3DTranslate(transform, 0, verticalOffset, 0);
  15. transform = CATransform3DScale(transform, 1, -1, 0);
  16. layer.instanceTransform = transform;
  17. //reduce alpha of reflection layer
  18. layer.instanceAlphaOffset = -0.6;
  19. }
  20. – (id)initWithFrame:(CGRect)frame{
  21. //this is called when view is created in code
  22. if ((self = [super initWithFrame:frame])) {
  23. [self setUp];
  24. }
  25. return self;
  26. }
  27. – (void)awakeFromNib{
  28. //this is called when view is created from a nib
  29. [self setUp];
  30. }
  31. @end

图6.9

图6.9 在Interface Builder中使用ReflectionView

图6.10

图6.10 ReflectionView自动实时产生反射效果。

开源代码ReflectionView完成了一个自适应的渐变淡出效果(用CAGradientLayer和图层蒙板实现),代码见https://github.com/nicklockwood/ReflectionView

CAScrollLayer

对于一个未转换的图层,它的bounds和它的frame是一样的,frame属性是由bounds属性自动计算而出的,所以更改任意一个值都会更新其他值。

但是如果你只想显示一个大图层里面的一小部分呢。比如说,你可能有一个很大的图片,你希望用户能够随意滑动,或者是一个数据或文本的长列表。在一个典型的iOS应用中,你可能会用到UITableView或是UIScrollView,但是对于独立的图层来说,什么会等价于刚刚提到的UITableViewUIScrollView呢?

在第二章中,我们探索了图层的contentsRect属性的用法,它的确是能够解决在图层中小地方显示大图片的解决方法。但是如果你的图层包含子图层那它就不是一个非常好的解决方案,因为,这样做的话每次你想『滑动』可视区域的时候,你就需要手工重新计算并更新所有的子图层位置。

这个时候就需要CAScrollLayer了。CAScrollLayer有一个-scrollToPoint:方法,它自动适应bounds的原点以便图层内容出现在滑动的地方。注意,这就是它做的所有事情。前面提到过,Core Animation并不处理用户输入,所以CAScrollLayer并不负责将触摸事件转换为滑动事件,既不渲染滚动条,也不实现任何iOS指定行为例如滑动反弹(当视图滑动超多了它的边界的将会反弹回正确的地方)。

让我们来用CAScrollLayer来常见一个基本的UIScrollView替代品。我们将会用CAScrollLayer作为视图的宿主图层,并创建一个自定义的UIView,然后用UIPanGestureRecognizer实现触摸事件响应。这段代码见清单6.10. 图6.11是运行效果:ScrollView显示了一个大于它的frameUIImageView

清单6.10 用CAScrollLayer实现滑动视图

 

  1. #import “ScrollView.h”
  2. #import <QuartzCore/QuartzCore.h> @implementation ScrollView
  3. + (Class)layerClass{
  4. return [CAScrollLayer class];
  5. }
  6. – (void)setUp{
  7. //enable clipping
  8. self.layer.masksToBounds = YES;
  9. //attach pan gesture recognizer
  10. UIPanGestureRecognizer *recognizer = nil;
  11. recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
  12. [self addGestureRecognizer:recognizer];
  13. }
  14. – (id)initWithFrame:(CGRect)frame{
  15. //this is called when view is created in code
  16. if ((self = [super initWithFrame:frame])) {
  17. [self setUp];
  18. }
  19. return self;
  20. }
  21. – (void)awakeFromNib {
  22. //this is called when view is created from a nib
  23. [self setUp];
  24. }
  25. – (void)pan:(UIPanGestureRecognizer *)recognizer{
  26. //get the offset by subtracting the pan gesture
  27. //translation from the current bounds origin
  28. CGPoint offset = self.bounds.origin;
  29. offset.x -= [recognizer translationInView:self].x;
  30. offset.y -= [recognizer translationInView:self].y;
  31. //scroll the layer
  32. [(CAScrollLayer *)self.layer scrollToPoint:offset];
  33. //reset the pan gesture translation
  34. [recognizer setTranslation:CGPointZero inView:self];
  35. }
  36. @end

图6.11 用UIScrollView创建一个凑合的滑动视图

不同于UIScrollView,我们定制的滑动视图类并没有实现任何形式的边界检查(bounds checking)。图层内容*有可能滑出视图的边界并无限滑下去。CAScrollLayer并没有等同于UIScrollViewcontentSize的属性,所以当CAScrollLayer滑动的时候完全没有一个全局的可滑动区域的概念,也无法自适应它的边界原点至你指定的值。它之所以不能自适应边界大小是因为它不需要,内容完全可以超过边界。

那你一定会奇怪用CAScrollLayer的意义到底何在,因为你可以简单地用一个普通的CALayer然后手动适应边界原点啊。真相其实并不复杂,UIScrollView并没有用CAScrollLayer,事实上,就是简单的通过直接操作图层边界来实现滑动。

CAScrollLayer有一个潜在的有用特性。如果你查看CAScrollLayer的头文件,你就会注意到有一个扩展分类实现了一些方法和属性:

- (void)scrollPoint:(CGPoint)p;
- (void)scrollRectToVisible:(CGRect)r;
@property(readonly) CGRect visibleRect;

 

看到这些方法和属性名,你也许会以为这些方法给每个CALayer实例增加了滑动功能。但是事实上他们只是放置在CAScrollLayer中的图层的实用方法。scrollPoint:方法从图层树中查找并找到*个可用的CAScrollLayer,然后滑动它使得指定点成为可视的。scrollRectToVisible:方法实现了同样的事情只不过是作用在一个矩形上的。visibleRect属性决定图层(如果存在的话)的哪部分是当前的可视区域。如果你自己实现这些方法就会相对容易明白一点,但是CAScrollLayer帮你省了这些麻烦,所以当涉及到实现图层滑动的时候就可以用上了。

CATiledLayer

有些时候你可能需要绘制一个很大的图片,常见的例子就是一个高像素的照片或者是地球表面的详细地图。iOS应用通畅运行在内存受限的设备上,所以读取整个图片到内存中是不明智的。载入大图可能会相当地慢,那些对你看上去比较方便的做法(在主线程调用UIImage-imageNamed:方法或者-imageWithContentsOfFile:方法)将会阻塞你的用户界面,至少会引起动画卡顿现象。

能高效绘制在iOS上的图片也有一个大小限制。所有显示在屏幕上的图片*终都会被转化为OpenGL纹理,同时OpenGL有一个*大的纹理尺寸(通常是2048*2048,或4096*4096,这个取决于设备型号)。如果你想在单个纹理中显示一个比这大的图,即便图片已经存在于内存中了,你仍然会遇到很大的性能问题,因为Core Animation强制用CPU处理图片而不是更快的GPU(见第12章『速度的曲调』,和第13章『高效绘图』,它更加详细地解释了软件绘制和硬件绘制)。

CATiledLayer为载入大图造成的性能问题提供了一个解决方案:将大图分解成小片然后将他们单独按需载入。让我们用实验来证明一下。

小片裁剪

这个示例中,我们将会从一个2048*2048分辨率的雪人图片入手。为了能够从CATiledLayer中获益,我们需要把这个图片裁切成许多小一些的图片。你可以通过代码来完成这件事情,但是如果你在运行时读入整个图片并裁切,那CATiledLayer这些所有的性能优点就损失殆尽了。理想情况下来说,*好能够逐个步骤来实现。

清单6.11 演示了一个简单的Mac OS命令行程序,它用CATiledLayer将一个图片裁剪成小图并存储到不同的文件中。

清单6.11 裁剪图片成小图的终端程序

 

  1. #import <AppKit/AppKit.h>
  2. int main(int argc, const char * argv[]){
  3. @autoreleasepool{
  4. //handle incorrect arguments
  5. if (argc < 2) {
  6. NSLog(@”TileCutter arguments: inputfile”);
  7. return 0;
  8. }
  9. //input file
  10. NSString *inputFile = [NSString stringWithCString:argv[1] encoding:NSUTF8StringEncoding];
  11. //tile size
  12. CGFloat tileSize = 256; //output path
  13. NSString *outputPath = [inputFile stringByDeletingPathExtension];
  14. //load image
  15. NSImage *image = [[NSImage alloc] initWithContentsOfFile:inputFile];
  16. NSSize size = [image size];
  17. NSArray *representations = [image representations];
  18. if ([representations count]){
  19. NSBitmapImageRep *representation = representations[0];
  20. size.width = [representation pixelsWide];
  21. size.height = [representation pixelsHigh];
  22. }
  23. NSRect rect = NSMakeRect(0.0, 0.0, size.width, size.height);
  24. CGImageRef imageRef = [image CGImageForProposedRect:&rect context:NULL hints:nil];
  25. //calculate rows and columns
  26. NSInteger rows = ceil(size.height / tileSize);
  27. NSInteger cols = ceil(size.width / tileSize);
  28. //generate tiles
  29. for (int y = 0; y < rows; ++y) {
  30. for (int x = 0; x < cols; ++x) {
  31. //extract tile image
  32. CGRect tileRect = CGRectMake(x*tileSize, y*tileSize, tileSize, tileSize);
  33. CGImageRef tileImage = CGImageCreateWithImageInRect(imageRef, tileRect);
  34. //convert to jpeg data
  35. NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:tileImage];
  36. NSData *data = [imageRep representationUsingType: NSJPEGFileType properties:nil];
  37. CGImageRelease(tileImage);
  38. //save file
  39. NSString *path = [outputPath stringByAppendingFormat: @”_%02i_%02i.jpg”, x, y];
  40. [data writeToFile:path atomically:NO];
  41. }
  42. }
  43. }
  44. return 0;
  45. }

这个程序将2048*2048分辨率的雪人图案裁剪成了64个不同的256*256的小图。(256*256是CATiledLayer的默认小图大小,默认大小可以通过tileSize属性更改)。程序接受一个图片路径作为命令行的*个参数。我们可以在编译的scheme将路径参数硬编码然后就可以在Xcode中运行了,但是以后作用在另一个图片上就不方便了。所以,我们编译了这个程序并把它保存到敏感的地方,然后从终端调用,如下面所示:

 path/to/TileCutterApp path/to/Snowman.jpg

The app is very basic, but could easily be extended to support additional arguments such as tile size, or to export images in formats other than JPEG. The result of running it is a sequence of 64 new images, named as follows:

这个程序相当基础,但是能够轻易地扩展支持额外的参数比如小图大小,或者导出格式等等。运行结果是64个新图的序列,如下面命名:

  1. Snowman_00_00.jpg
  2. Snowman_00_01.jpg
  3. Snowman_00_02.jpg
  4. Snowman_07_07.jpg

既然我们有了裁切后的小图,我们就要让iOS程序用到他们。CATiledLayer很好地和UIScrollView集成在一起。除了设置图层和滑动视图边界以适配整个图片大小,我们真正要做的就是实现-drawLayer:inContext:方法,当需要载入新的小图时,CATiledLayer就会调用到这个方法。

清单6.12演示了代码。图6.12是代码运行结果。

清单6.12 一个简单的滚动CATiledLayer实现

  1. #import “ViewController.h”
  2. #import <QuartzCore/QuartzCore.h>
  3. @interface ViewController ()
  4. @property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
  5. @end
  6. @implementation ViewController
  7. – (void)viewDidLoad{
  8. [super viewDidLoad];
  9. //add the tiled layer
  10. CATiledLayer *tileLayer = [CATiledLayer layer];
  11. tileLayer.frame = CGRectMake(0, 0, 2048, 2048);
  12. tileLayer.delegate = self; [self.scrollView.layer addSublayer:tileLayer];
  13. //configure the scroll view
  14. self.scrollView.contentSize = tileLayer.frame.size;
  15. //draw layer
  16. [tileLayer setNeedsDisplay];
  17. }
  18. – (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx{
  19. //determine tile coordinate
  20. CGRect bounds = CGContextGetClipBoundingBox(ctx);
  21. NSInteger x = floor(bounds.origin.x / layer.tileSize.width);
  22. NSInteger y = floor(bounds.origin.y / layer.tileSize.height);
  23. //load tile image
  24. NSString *imageName = [NSString stringWithFormat: @”Snowman_%02i_%02i”, x, y];
  25. NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@”jpg”];
  26. UIImage *tileImage = [UIImage imageWithContentsOfFile:imagePath];
  27. //draw tile
  28. UIGraphicsPushContext(ctx);
  29. [tileImage drawInRect:bounds];
  30. UIGraphicsPopContext();
  31. }

图6.12

图6.12 用UIScrollView滚动CATiledLayer

当你滑动这个图片,你会发现当CATiledLayer载入小图的时候,他们会淡入到界面中。这是CATiledLayer的默认行为。(你可能已经在iOS 6之前的苹果地图程序中见过这个效果)你可以用fadeDuration属性改变淡入时长或直接禁用掉。CATiledLayer(不同于大部分的UIKit和Core Animation方法)支持多线程绘制,-drawLayer:inContext:方法可以在多个线程中同时地并发调用,所以请小心谨慎地确保你在这个方法中实现的绘制代码是线程安全的。

Retina小图

你也许已经注意到了这些小图并不是以Retina的分辨率显示的。为了以屏幕的原生分辨率来渲染CATiledLayer,我们需要设置图层的contentsScale来匹配UIScreenscale属性:

tileLayer.contentsScale = [UIScreen mainScreen].scale;

有趣的是,tileSize是以像素为单位,而不是点,所以增大了contentsScale就自动有了默认的小图尺寸(现在它是128*128的点而不是256*256).所以,我们不需要手工更新小图的尺寸或是在Retina分辨率下指定一个不同的小图。我们需要做的是适应小图渲染代码以对应安排scale的变化,然而:

  1. //determine tile coordinate
  2. CGRect bounds = CGContextGetClipBoundingBox(ctx);
  3. CGFloat scale = [UIScreen mainScreen].scale;
  4. NSInteger x = floor(bounds.origin.x / layer.tileSize.width * scale);
  5. NSInteger y = floor(bounds.origin.y / layer.tileSize.height * scale);

通过这个方法纠正scale也意味着我们的雪人图将以一半的大小渲染在Retina设备上(总尺寸是1024*1024,而不是2048*2048)。这个通常都不会影响到用CATiledLayer正常显示的图片类型(比如照片和地图,他们在设计上就是要支持放大缩小,能够在不同的缩放条件下显示),但是也需要在心里明白。

CAEmitterLayer

在iOS 5中,苹果引入了一个新的CALayer子类叫做CAEmitterLayerCAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。

CAEmitterLayer看上去像是许多CAEmitterCell的容器,这些CAEmitierCell定义了一个例子效果。你将会为不同的例子效果定义一个或多个CAEmitterCell作为模版,同时CAEmitterLayer负责基于这些模版实例化一个粒子流。一个CAEmitterCell类似于一个CALayer:它有一个contents属性可以定义为一个CGImage,另外还有一些可设置属性控制着表现和行为。我们不会对这些属性逐一进行详细的描述,你们可以在CAEmitterCell类的头文件中找到。

我们来举个例子。我们将利用在一圆中发射不同速度和透明度的粒子创建一个火爆炸的效果。清单6.13包含了生成爆炸的代码。图6.13是运行结果

清单6.13 用CAEmitterLayer创建爆炸效果

  1. #import “ViewController.h”
  2. #import <QuartzCore/QuartzCore.h>
  3. @interface ViewController ()
  4. @property (nonatomic, weak) IBOutlet UIView *containerView;
  5. @end
  6. @implementation ViewController
  7. – (void)viewDidLoad{
  8. [super viewDidLoad];
  9. //create particle emitter layer
  10. CAEmitterLayer *emitter = [CAEmitterLayer layer];
  11. emitter.frame = self.containerView.bounds;
  12. [self.containerView.layer addSublayer:emitter];
  13. //configure emitter
  14. emitter.renderMode = kCAEmitterLayerAdditive;
  15. emitter.emitterPosition = CGPointMake(emitter.frame.size.width / 2.0, emitter.frame.size.height / 2.0);
  16. //create a particle template
  17. CAEmitterCell *cell = [[CAEmitterCell alloc] init];
  18. cell.contents = (__bridge id)[UIImage imageNamed:@”Spark.png”].CGImage;
  19. cell.birthRate = 150;
  20. cell.lifetime = 5.0;
  21. cell.color = [UIColor colorWithRed:1 green:0.5 blue:0.1 alpha:1.0].CGColor;
  22. cell.alphaSpeed = -0.4;
  23. cell.velocity = 50;
  24. cell.velocityRange = 50;
  25. cell.emissionRange = M_PI * 2.0;
  26. //add particle template to emitter
  27. emitter.emitterCells = @[cell];
  28. }
  29. @end

图6.13 火焰爆炸效果

CAEMitterCell的属性基本上可以分为三种:

  • 这种粒子的某一属性的初始值。比如,color属性指定了一个可以混合图片内容颜色的混合色。在示例中,我们将它设置为桔色。
  • 例子某一属性的变化范围。比如emissionRange属性的值是2π,这意味着例子可以从360度任意位置反射出来。如果指定一个小一些的值,就可以创造出一个圆锥形
  • 指定值在时间线上的变化。比如,在示例中,我们将alphaSpeed设置为-0.4,就是说例子的透明度每过一秒就是减少0.4,这样就有发射出去之后逐渐小时的效果。

CAEmitterLayer的属性它自己控制着整个例子系统的位置和形状。一些属性比如birthRatelifetimecelocity,这些属性在CAEmitterCell中也有。这些属性会以相乘的方式作用在一起,这样你就可以用一个值来加速或者扩大整个例子系统。其他值得提到的属性有以下这些:

  • preservesDepth,是否将3D例子系统平面化到一个图层(默认值)或者可以在3D空间中混合其他的图层
  • renderMode,控制着在视觉上粒子图片是如何混合的。你可能已经注意到了示例中我们把它设置为kCAEmitterLayerAdditive,它实现了这样一个效果:合并例子重叠部分的亮度使得看上去更亮。如果我们把它设置为默认的kCAEmitterLayerUnordered,效果就没那么好看了(见图6.14).

图6.14

图6.14 禁止混色之后的火焰粒子

CAEAGLLayer

当iOS要处理高性能图形绘制,必要时就是OpenGL。应该说它应该是*后的杀手锏,至少对于非游戏的应用来说是的。因为相比Core Animation和UIkit框架,它不可思议地复杂。

OpenGL提供了Core Animation的基础,它是底层的C接口,直接和iPhone,iPad的硬件通信,*少地抽象出来的方法。OpenGL没有对象或是图层的继承概念。它只是简单地处理三角形。OpenGL中所有东西都是3D空间中有颜色和纹理的三角形。用起来非常复杂和强大,但是用OpenGL绘制iOS用户界面就需要很多很多的工作了。

为了能够以高性能使用Core Animation,你需要判断你需要绘制哪种内容(矢量图形,例子,文本,等等),但后选择合适的图层去呈现这些内容,Core Animation中只有一些类型的内容是被高度优化的;所以如果你想绘制的东西并不能找到标准的图层类,想要得到高性能就比较费事情了。

因为OpenGL根本不会对你的内容进行假设,它能够绘制得相当快。利用OpenGL,你可以绘制任何你知道必要的集合信息和形状逻辑的内容。所以很多游戏都喜欢用OpenGL(这些情况下,Core Animation的限制就明显了:它优化过的内容类型并不一定能满足需求),但是这样依赖,方便的高度抽象接口就没了。

在iOS 5中,苹果引入了一个新的框架叫做GLKit,它去掉了一些设置OpenGL的复杂性,提供了一个叫做CLKViewUIView的子类,帮你处理大部分的设置和绘制工作。前提是各种各样的OpenGL绘图缓冲的底层可配置项仍然需要你用CAEAGLLayer完成,它是CALayer的一个子类,用来显示任意的OpenGL图形。

大部分情况下你都不需要手动设置CAEAGLLayer(假设用GLKView),过去的日子就不要再提了。特别的,我们将设置一个OpenGL ES 2.0的上下文,它是现代的iOS设备的标准做法。

尽管不需要GLKit也可以做到这一切,但是GLKit囊括了很多额外的工作,比如设置顶点和片段着色器,这些都以类C语言叫做GLSL自包含在程序中,同时在运行时载入到图形硬件中。编写GLSL代码和设置EAGLayer没有什么关系,所以我们将用GLKBaseEffect类将着色逻辑抽象出来。其他的事情,我们还是会有以往的方式。

在开始之前,你需要将GLKit和OpenGLES框架加入到你的项目中,然后就可以实现清单6.14中的代码,里面是设置一个GAEAGLLayer的*少工作,它使用了OpenGL ES 2.0 的绘图上下文,并渲染了一个有色三角(见图6.15).

清单6.14 用CAEAGLLayer绘制一个三角形

  1. #import “ViewController.h”
  2. #import <QuartzCore/QuartzCore.h>
  3. #import <GLKit/GLKit.h>
  4. @interface ViewController ()
  5. @property (nonatomic, weak) IBOutlet UIView *glView;
  6. @property (nonatomic, strong) EAGLContext *glContext;
  7. @property (nonatomic, strong) CAEAGLLayer *glLayer;
  8. @property (nonatomic, assign) GLuint framebuffer;
  9. @property (nonatomic, assign) GLuint colorRenderbuffer;
  10. @property (nonatomic, assign) GLint framebufferWidth;
  11. @property (nonatomic, assign) GLint framebufferHeight;
  12. @property (nonatomic, strong) GLKBaseEffect *effect;
  13. @end
  14. @implementation ViewController
  15. – (void)setUpBuffers{
  16. //set up frame buffer
  17. glGenFramebuffers(1, &_framebuffer);
  18. glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
  19. //set up color render buffer
  20. glGenRenderbuffers(1, &_colorRenderbuffer);
  21. glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
  22. glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer);
  23. [self.glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer];
  24. glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_framebufferWidth);
  25. glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_framebufferHeight);
  26. //check success
  27. if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
  28. NSLog(@”Failed to make complete framebuffer object: %i”, glCheckFramebufferStatus(GL_FRAMEBUFFER));
  29. }
  30. }
  31. – (void)tearDownBuffers{
  32. if (_framebuffer) {
  33. //delete framebuffer
  34. glDeleteFramebuffers(1, &_framebuffer);
  35. _framebuffer = 0;
  36. }
  37. if (_colorRenderbuffer) {
  38. //delete color render buffer
  39. glDeleteRenderbuffers(1, &_colorRenderbuffer);
  40. _colorRenderbuffer = 0;
  41. }
  42. }
  43. – (void)drawFrame {
  44. //bind framebuffer & set viewport
  45. glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
  46. glViewport(0, 0, _framebufferWidth, _framebufferHeight);
  47. //bind shader program
  48. [self.effect prepareToDraw];
  49. //clear the screen
  50. glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 1.0);
  51. //set up vertices
  52. GLfloat vertices[] = {
  53. -0.5f, -0.5f, -1.0f, 0.0f, 0.5f, -1.0f, 0.5f, -0.5f, -1.0f,
  54. };
  55. //set up colors
  56. GLfloat colors[] = {
  57. 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
  58. };
  59. //draw triangle
  60. glEnableVertexAttribArray(GLKVertexAttribPosition);
  61. glEnableVertexAttribArray(GLKVertexAttribColor);
  62. glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, vertices);
  63. glVertexAttribPointer(GLKVertexAttribColor,4, GL_FLOAT, GL_FALSE, 0, colors);
  64. glDrawArrays(GL_TRIANGLES, 0, 3);
  65. //present render buffer
  66. glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
  67. [self.glContext presentRenderbuffer:GL_RENDERBUFFER];
  68. }
  69. – (void)viewDidLoad{
  70. [super viewDidLoad];
  71. //set up context
  72. self.glContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
  73. [EAGLContext setCurrentContext:self.glContext];
  74. //set up layer
  75. self.glLayer = [CAEAGLLayer layer];
  76. self.glLayer.frame = self.glView.bounds;
  77. [self.glView.layer addSublayer:self.glLayer];
  78. self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking:@NO, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};
  79. //set up base effect
  80. self.effect = [[GLKBaseEffect alloc] init];
  81. //set up buffers
  82. [self setUpBuffers];
  83. //draw frame
  84. [self drawFrame];
  85. }
  86. – (void)viewDidUnload{
  87. [self tearDownBuffers];
  88. [super viewDidUnload];
  89. }
  90. – (void)dealloc{
  91. [self tearDownBuffers];
  92. [EAGLContext setCurrentContext:nil];
  93. }
  94. @end

图6.15

图6.15 用OpenGL渲染的CAEAGLLayer图层

在一个真正的OpenGL应用中,我们可能会用NSTimerCADisplayLink周期性地每秒钟调用-drawRrame方法60次,同时会将几何图形生成和绘制分开以便不会每次都重新生成三角形的顶点(这样也可以让我们绘制其他的一些东西而不是一个三角形而已),不过上面这个例子已经足够演示了绘图原则了。

AVPlayerLayer

*后一个图层类型是AVPlayerLayer。尽管它不是Core Animation框架的一部分(AV前缀看上去像),AVPlayerLayer是有别的框架(AVFoundation)提供的,它和Core Animation紧密地结合在一起,提供了一个CALayer子类来显示自定义的内容类型。

AVPlayerLayer是用来在iOS上播放视频的。他是高级接口例如MPMoivePlayer的底层实现,提供了显示视频的底层控制。AVPlayerLayer的使用相当简单:你可以用+playerLayerWithPlayer:方法创建一个已经绑定了视频播放器的图层,或者你可以先创建一个图层,然后用player属性绑定一个AVPlayer实例。

在我们开始之前,我们需要添加AVFoundation到我们的项目中。然后,清单6.15创建了一个简单的电影播放器,图6.16是代码运行结果。

清单6.15 用AVPlayerLayer播放视频

  1. #import “ViewController.h”
  2. #import <QuartzCore/QuartzCore.h>
  3. #import <AVFoundation/AVFoundation.h>
  4. @interface ViewController ()
  5. @property (nonatomic, weak) IBOutlet UIView *containerView; @end
  6. @implementation ViewController
  7. – (void)viewDidLoad
  8. {
  9. [super viewDidLoad];
  10. //get video URL
  11. NSURL *URL = [[NSBundle mainBundle] URLForResource:@”Ship” withExtension:@”mp4″];
  12. //create player and player layer
  13. AVPlayer *player = [AVPlayer playerWithURL:URL];
  14. AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
  15. //set player layer frame and attach it to our view
  16. playerLayer.frame = self.containerView.bounds;
  17. [self.containerView.layer addSublayer:playerLayer];
  18. //play the video
  19. [player play];
  20. }
  21. @end<span style=“font-family: verdana, ‘ms song’, 宋体, Arial, 微软雅黑, Helvetica, sans-serif; background-color: rgb(255, 255, 255);”> </span>

%title插图%num

图6.16 用AVPlayerLayer图层播放视频的截图

我们用代码创建了一个AVPlayerLayer,但是我们仍然把它添加到了一个容器视图中,而不是直接在controller中的主视图上添加。这样其实是为了可以使用自动布局限制使得图层在*中间;否则,一旦设备被旋转了我们就要手动重新放置位置,因为Core Animation并不支持自动大小和自动布局(见第三章『图层几何学』)。

当然,因为AVPlayerLayerCALayer的子类,它继承了父类的所有特性。我们并不会受限于要在一个矩形中播放视频;清单6.16演示了在3D,圆角,有色边框,蒙板,阴影等效果(见图6.17).

清单6.16 给视频增加变换,边框和圆角

  1. – (void)viewDidLoad{
  2. //set player layer frame and attach it to our view
  3. playerLayer.frame = self.containerView.bounds;
  4. [self.containerView.layer addSublayer:playerLayer];
  5. //transform layer
  6. CATransform3D transform = CATransform3DIdentity;
  7. transform.m34 = -1.0 / 500.0;
  8. transform = CATransform3DRotate(transform, M_PI_4, 1, 1, 0);
  9. playerLayer.transform = transform; 
  10. //add rounded corners and border
  11. playerLayer.masksToBounds = YES;
  12. playerLayer.cornerRadius = 20.0;
  13. playerLayer.borderColor = [UIColor redColor].CGColor;
  14. playerLayer.borderWidth = 5.0;
  15. //play the video
  16. [player play];
  17. }

%title插图%num

IOS的 testflight测试设置(内部测试)

一,概述
在日常的开发当中,当一个项目在开发过程中或者完成准备上线,都需要我们进行真机测试。 今天介绍的是使用TestFlight在ituns Connect中对构建的版本进行真机测试
二,管理员邀请参与者(内部测试)
1、登录开发者账号https://developer.apple.com/account
2、进入后,点击ituns connect

%title插图%num

3、点击进入用户和职能

%title插图%num

4、在用户栏点击添加按钮

%title插图%num

5、输入姓名、邮件

%title插图%num

6、勾选职能为“开发人员”,选择所要测试或参与研发的APP

%title插图%num

7、不用修改,直接点击存储
8、此时添加完成后,等测试人员加入项目(也就是步骤“二、参与者加入项目”)

9、待测试人员加入项目后,进入我的APP->要测试的APP->TestFlight->内部测试

%title插图%num

10、点击添加内部测试员,选择之前添加的测试人

11、点击选择版本进行测试,此时需要IOS研发人员先将APP提交到ituns,然后选择版本后进行保存,*后点击右上角的开始测试按钮。开始测试后,内部测试人员会收到邀请测试的邮件,则从“三、参与者参与测试”开始进行测试工作。

%title插图%num

参与者加入项目
1、接收到邮件后,点击如图链接,选择accept加入我们的IOS项目

%title插图%num

2、收到如图的测试邀请邮件后,进入appstore下载名为 TestFlight 的APP
%title插图%num
参与者参与测试
1、 打开APP后点击图中 redeem按钮
%title插图%num
2、在如图邀请输入invite code的界面,输入邮件中链接打开后显示的号码

3、invite code

4、输入邀请码进入后,安装APP

TestFlight 如何获取邀请码

1
首先登录苹果 iTunes Connect 官网,进入如下界面:

%title插图%num

点击添加测试员

%title插图%num

如果没有你的 APP ID 在列表里面,那就到 用户和职能 中添加:

%title插图%num

添加完后会发一个邮件到你填写的邮箱里面,然后点击登录一下这一步就过了,然后我们回到第2步把刚才添加的你的邮箱勾选添加。添加完后测试员列表会显示一个发邮件的按钮,点击按钮会发送两条信息到你填写的这个邮箱,然后第二个邮箱里面有你需要的 TestFlight 的邀请码,把这个邀请码输入到你 ios 设备的 TestFlight 就可以了。
1
**注意注意:APP中 TestFlight 的登录账号必须要和你添加的用户一致才能

iOS 屏蔽系统更新描述文件更新!快把烦人的系统更新提示关掉!

%title插图%num

我们以前分享过关于 iOS 系统屏蔽系统更新提示的方法,但是可能文章中提及的 App 过于敏感,文章被删除了!

刚好有人问 iOS 系统更新提示怎么去掉?而前段时间描述文件又更新了可以使用了,由于前段时间没有更新,今天我们分享一下详细方法,与以前的通用方法(被删除了的)相比,更加简单!

%title插图%num

虽说相比简单,但是不同人难易程度的标准可能不同,教程还得详细点,有些步骤会的可以直接跳过!

该方法相比的话,比较简单,但是仅适用于 iOS 13 及以上系统!

1、首先,我们需要下载描述文件。

这里,我们提供两个可以下载描述文件的网站“Beta Profiles”和“iBeta 尝鲜派”

%title插图%num

进入网站 Beta Profiles ,滑动网页找到“Block OTA Update”,再点击蓝色按钮“Download”下载描述文件。

如果是网站“尝鲜派”的话,我们左右滑动找到*后一项“屏蔽 OTA 更新”,再点击“立即安装”下载描述文件。

这时会重新打开一个网页和下载弹窗提示,我们选择“允许”而不是“忽略”,不用等待就会秒下载完成!下载完成以后,我们打开设置,就可以看到 Apple ID 头像下方有一个“已下载描述文件”

%title插图%num

我们点击“已下载描述文件”进入安装,再点击右上角“安装”,然后输入我们的锁屏密码,*后点击右上角“安装”完成描述文件的安装!

%title插图%num

*后,我们打开设置 – 通用 – 软件更新 查看一下是否成功!

*新版本是 iOS 13.7,而我的手机系统是 iOS 13.4,但是检查更新时已经是*新版本!

%title插图%num

同理,从网站“尝鲜派”下载安装方法也是一样的,这里就不再详细说明了!

那么,有一天你想要更新系统的时候怎么更新呢?

方法也很简单,直接删除已经安装好的描述文件再检查更新就可以啦!

具体步骤:打开设置 – 通用 – 描述文件与设备管理 – tvOS 13 Beta Software Profile ,然后点击底部的“移除描述文件”,再输入锁屏密码移除描述文件,移除完成以后,我们再去检查更新就可以啦!

苹果手机微信双开,微信多开有哪些可以推荐的?

前言
相信很多手机用户,需要用到一个以上微信。
安卓用户大多数机型都有内置的微信多开功能,iPhone用户对此需求比较强烈,
而且iphone本身是不支持微信多开的,这也是苹果iOS生态不允许的。
本文推荐下市面上的苹果手机微信多开软件,主要是推荐下靠谱可稳定使用的多开产品。

目前市面上有两种类型苹果手机微信多开软件,分别为企业证书(或超级证书)的签名ipa微信多开软件,和苹果官方商店TestFlight上架模式的微信多开软件。
下面分别介绍下两个不同类型软件的区别,分析下利弊。

企业证书&超级证书版微信多开
企业证书或超级证书签名版微信多开是市面上*常见的,百度搜苹果微信多开,会显示很多推广信息,价格在15元以上,因其开发成本比较小,所以开发商也是很多,所以也有很多品牌的多开软件,比如之前跑路的蒲公英,新传奇,万里马等。这种多开软件成本低,可大批量生产出售,开发商可短时间迅速赚一笔钱,所以买的时候说是可以使用一年的多开软件,平均使用1个月就跑路了,良心一点的开发商*多3个月就跑路,跑路后又换个名字继续开发然后反复的使用这种模式进行吸金。
而且这类多开软件的签名证书是会随时掉签(苹果开发者应用签名),可能你刚买用了一会都有可能掉签,然后花的钱打水漂了,所以买了这类的多开软件,不是软件掉签就是开发商跑路,用不了多长时间,用户体验*差而且聊天数据也会丢失,所以不建议大家买这种签名的微信多开软件,买的时候卖家都会说可以永久使用其实就是个幌子而已,永久?怎么可能是吧,一年都不可能,永久更不可能了。

上架版微信多开
TestFlight上架版微信多开的成本很高,直接过滤了小开发商,价格高开发商利润空间就比较大,所以开发商会专心维护做下去,再说下其模式,上架版是苹果官方内测商店上架的微信多开软件,因为是苹果官方认可的模式,所以使用稳定性比较好,基本上和原始微信一样,不会出现掉签,也就是你买了一年真的可以用一年,使用是不会出现问题的,上架版价格比较高,市场价在100元以上,如果你看到有商家说上架版但是价格在100元以下,那么你就要警惕了便宜很可能是假的,上架版价格虽然比较高但是真的好用,不用折腾不用担心无法使用的情况,是目前市面上*靠谱的微信多开软件了,可以说是完美使用,越狱和没越狱的手机都可以用,体验也是非常好,一点问题也不会出现,*力推荐。

总结
看完文章相信大家都了解了,签名版价格便宜但是随时可能无法使用,上架版价格虽然贵一些,但是你买了之后是真的可以使用一年的,还是要注意买的时候一定要问清楚,看清楚产品介绍是不是上架版的,别花了冤枉钱。

Angular 入门教程系列:使用webpack-bundle-analyzer对构建结果进行分析

webpack-bundle-analyzer是一个npm的package,可以用于构建结果的分析。在实际的使用中,由于Angular页面的特点,项目稍大一些之后,即使使用了prod等选项进行优化,也往往编译后会有数M之大。这时使用webpack-bundle-analyzer即可对结果进行分析,可以通过webpack-bundle-analyzer生成的分析结果对各个组成部分的大小进行非常方便的确认。

webpack-bundle-analyzer
URL链接:https://www.npmjs.com/package/webpack-bundle-analyzer
使用方法
步骤1: 安装webpack-bundle-analyzer
安装方法:npm install -g webpack-bundle-analyzer

步骤2: 构建打包并生成json文件
以下对ng-alain的demo应用进行结果分析,使用ng-alain的方法可参看:

https://liumiaocn.blog.csdn.net/article/details/102508783
使用如下命令对上述应用进行打包并生成结果的json文件

执行命令:ng build –prod –stats-json

执行日志如下所示

liumiaocn:alain-demo liumiao$ ng build –prod –stats-json
Generating ES5 bundles for differential loading…
ES5 bundle generation complete.

chunk {2} polyfills-es2015.0ef207fb7b4761464817.js (polyfills) 36.4 kB [initial] [rendered]
…省略

WARNING in budgets, maximum exceeded for initial. Budget 2 MB was exceeded by 2.51 MB.
liumiaocn:alain-demo liumiao$
liumiaocn:alain-demo liumiao$ ls dist/alain-demo/*.json
dist/alain-demo/stats-es2015.json
liumiaocn:alain-demo liumiao$

问题与对应方法
可以看到上述结果中出现了WARNING:

WARNING in budgets, maximum exceeded for initial. Budget 2 MB was exceeded by 2.51 MB.

原因是因为angular.json中maximumWarning的*小设定2MB过小,实际达到了4.51MB(超出了2.51MB)

57 “budgets”: [
58 {
59 “type”: “initial”,
60 “maximumWarning”: “2mb”,
61 “maximumError”: “5mb”
62 },

对应方法:将上述文件的maximumWarning从2mb修改为5mb即可

步骤3: 确认结果
正常的情况下,应该会在dist目录下生成一个名为stats.json的文件,然后使用此文件作为webpack-bundle-analyzer的输入即可确认构建之后各个部分的大小。这里使用的ng-alain缺省的情况下的目录为dist/alain-demo,这个原因是因为angular.json中的路径配置差生的,另外文件名也变成了stats-es2015.json,但是了解到这个是webpack-bundle-analyzer所分析的内容,直接作为参数传入即可。

命令:webpack-bundle-analyzer dist/alain-demo/stats-es2015.json
或者
npx webpack-bundle-analyzer dist/alain-demo/stats-es2015.json

注:如果一直提示端口8888被占用,有时可以考虑重启一下有可能这种奇怪的问题就会消失。

正常的情况下,会自动打开浏览器在本地的8888端口显示分析结果,执行日志信息如下所示:

liumiaocn:alain-demo liumiao$ webpack-bundle-analyzer dist/alain-demo/stats-es2015.json
Error parsing bundle asset “/Users/liumiao/alain-demo/dist/alain-demo/polyfills-es5-es2015.2b9ce34c123ac007a8ba.js”: no such file
Webpack Bundle Analyzer is started at http://127.0.0.1:8888
Use Ctrl+C to close it

本例的结果显示如下图所示:

%title插图%num

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

【Android】玩转命令行工具-apkanalyzer

文章目录

1 打印App ID、版本信息
2 打印apk的文件大小
3 打印apk的download大小
4 打印apk用到的feature
5 比较两个apk的大小
6 打印apk中的文件列表
7 打印apk中某个文件的内容(二进制内容会原样显示)
8 打印AndroidManifest.xml(文本格式)
9 打印App ID
10 打印版本名
11 打印版本号
12 打印*低的SDK版本
13 打印目标SDK版本
14 打印用到的权限列表
15 打印是否debug模式
16 打印apk中的dex文件列表
17 打印dex文件中引用的方法个数(默认打印所有的dex文件)
18 打印dex文件中的类信息
19 打印类的字节码或者方法的反编译格式
20 打印资源表中的包
21 打印某个资源类型的配置
22 打印某个资源的值
23 打印某种资源类型的所有名字
24 打印二进制xml文件(纯文本格式输出)

apkanalyzer,从名字就可以看出这是一个分析apk的工具,*简单的使用方法是在AndroidStudio中点击Build,然后选择Analyze APK…,本文介绍如何在命令行使用apkanalyzer。

apkanalyzer在Android SDK中,位置为android_sdk/tools/bin/apkanalyzer。
apkanalyzer的语法如下:

apkanalyzer [global-options] subject verb [options] apk-file [apk-file2]
1
global-options:可选项,- -human-readable,可缩写为-h,表示以可读性好的格式打印文件大小。
subject:必选项,包括apk、files、manifest、dex、resources。
verb:必选项,根据subject的不同而不同。
options:可选项,根据subject和verb的不同而不同。
apk-file:必选项,表示需要分析的apk。
apk-file2:仅在比较两个apk时才需要。
下面详细说明apkanalyzer的用法。

1 打印App ID、版本信息
apkanalyzer apk summary apk-file

$ ./apkanalyzer apk summary Animal.apk
a.b.animal 27 8.1.0

 

2 打印apk的文件大小
apkanalyzer apk file-size apk-file

$ ./apkanalyzer apk file-size Animal.apk
376139

3 打印apk的download大小
apkanalyzer apk download-size apk-file

$ ./apkanalyzer apk download-size Animal.apk
369340

4 打印apk用到的feature
apkanalyzer apk features [options] apk-file
options包括:–not-required

$ ./apkanalyzer apk features Animal.apk
android.hardware.faketouch implied: default feature for all apps

5 比较两个apk的大小
apkanalyzer apk compare [options] apk-file apk-file2
options包括:–different-only、–files-only、–patch-size

$ ./apkanalyzer apk compare Animal.apk Animal2.apk
376139 301359 -74780 /
8804 8828 24 /classes.dex
6192 6192 0 /META-INF/
2191 2191 0 /META-INF/MANIFEST.MF
1722 1722 0 /META-INF/CERT.RSA
2279 2279 0 /META-INF/CERT.SF
2764 2764 0 /AndroidManifest.xml
4112 4112 0 /resources.arsc
367539 292731 -74808 /res/
1104 1104 0 /res/mipmap-anydpi-v26/
552 552 0 /res/mipmap-anydpi-v26/ic_launcher_round.xml
552 552 0 /res/mipmap-anydpi-v26/ic_launcher.xml
1208 1208 0 /res/layout/
1208 1208 0 /res/layout/layout_main.xml
3416 3416 0 /res/drawable-v24/
760 760 0 /res/drawable-v24/ic_launcher_foreground_1.xml
2656 2656 0 /res/drawable-v24/ic_launcher_foreground.xml
25133 25133 0 /res/mipmap-xxxhdpi-v4/
16187 16187 0 /res/mipmap-xxxhdpi-v4/ic_launcher_round.png
8946 8946 0 /res/mipmap-xxxhdpi-v4/ic_launcher.png
18161 18161 0 /res/mipmap-xxhdpi-v4/
11607 11607 0 /res/mipmap-xxhdpi-v4/ic_launcher_round.png
6554 6554 0 /res/mipmap-xxhdpi-v4/ic_launcher.png
12680 12680 0 /res/mipmap-xhdpi-v4/
8004 8004 0 /res/mipmap-xhdpi-v4/ic_launcher_round.png
4676 4676 0 /res/mipmap-xhdpi-v4/ic_launcher.png
5816 5816 0 /res/mipmap-mdpi-v4/
3487 3487 0 /res/mipmap-mdpi-v4/ic_launcher_round.png
2329 2329 0 /res/mipmap-mdpi-v4/ic_launcher.png
9165 9165 0 /res/mipmap-hdpi-v4/
5843 5843 0 /res/mipmap-hdpi-v4/ic_launcher_round.png
3322 3322 0 /res/mipmap-hdpi-v4/ic_launcher.png
290856 216048 -74808 /res/drawable/
6280 6280 0 /res/drawable/ic_launcher_background.xml
284576 209768 -74808 /res/drawable/animal.jpg

 

6 打印apk中的文件列表
apkanalyzer files list apk-file

$ ./apkanalyzer files list Animal.apk
/
/META-INF/
/META-INF/MANIFEST.MF
/META-INF/CERT.RSA
/META-INF/CERT.SF
/classes.dex
/AndroidManifest.xml
/resources.arsc
/res/
/res/mipmap-anydpi-v26/
/res/mipmap-anydpi-v26/ic_launcher_round.xml
/res/mipmap-anydpi-v26/ic_launcher.xml
/res/layout/
/res/layout/layout_main.xml
/res/drawable-v24/
/res/drawable-v24/ic_launcher_foreground_1.xml
/res/drawable-v24/ic_launcher_foreground.xml
/res/mipmap-xxxhdpi-v4/
/res/mipmap-xxxhdpi-v4/ic_launcher_round.png
/res/mipmap-xxxhdpi-v4/ic_launcher.png
/res/mipmap-xxhdpi-v4/
/res/mipmap-xxhdpi-v4/ic_launcher_round.png
/res/mipmap-xxhdpi-v4/ic_launcher.png
/res/mipmap-xhdpi-v4/
/res/mipmap-xhdpi-v4/ic_launcher_round.png
/res/mipmap-xhdpi-v4/ic_launcher.png
/res/mipmap-mdpi-v4/
/res/mipmap-mdpi-v4/ic_launcher_round.png
/res/mipmap-mdpi-v4/ic_launcher.png
/res/mipmap-hdpi-v4/
/res/mipmap-hdpi-v4/ic_launcher_round.png
/res/mipmap-hdpi-v4/ic_launcher.png
/res/drawable/
/res/drawable/ic_launcher_background.xml
/res/drawable/animal.jpg

 

7 打印apk中某个文件的内容(二进制内容会原样显示)
apkanalyzer files cat –file path apk-file

$ ./apkanalyzer files cat –file /AndroidManifest.xml Animal.apk

�’�6Pn������,:L����”6Nv����l�8Pd�����
sharedUserId
versionCode
minSdkVersiontargetSdkVersionname versionName
allowBackuphardwareAcceleratediconlabel roundIcon
supportsRtlthemeandroid*http://schemas.android.com/apk/res/androidpackageplatformBuildVersionCodeplatformBuildVersionNammanifest
a.b.animalandroid.uid.system8.1.02uses-sdkuses-permission’android.permission.MODIFY_THEME_OVERLAY*android.permission.CHANGE_OVERLAY_PACKAGES(android.permission.INTERACT_ACROSS_USERS-android.permission.INTERACT_ACROSS_USERS_FULL
application
intent-filteractionandroid.intent.action.MAIcategoryandroid.intent.category.DEFAULT�<

 

8 打印AndroidManifest.xml(文本格式)
apkanalyzer manifest print apk-file

$ ./apkanalyzer manifest print Animal.apk
<?xml version=”1.0″ encoding=”utf-8″?>
<manifest
xmlns:android=”http://schemas.android.com/apk/res/android”
android:sharedUserId=”android.uid.system”
android:versionCode=”27″
android:versionName=”8.1.0″
package=”a.b.animal”
platformBuildVersionCode=”27″
platformBuildVersionName=”8.1.0″>

<uses-sdk
android:minSdkVersion=”27″
android:targetSdkVersion=”27″ />

<uses-permission
android:name=”android.permission.MODIFY_THEME_OVERLAY” />

<uses-permission
android:name=”android.permission.CHANGE_OVERLAY_PACKAGES” />

<uses-permission
android:name=”android.permission.INTERACT_ACROSS_USERS” />

<uses-permission
android:name=”android.permission.INTERACT_ACROSS_USERS_FULL” />

<application
android:theme=”@ref/0x7f070000″
android:label=”@ref/0x7f060000″
android:icon=”@ref/0x7f030000″
android:name=”.AnimalApp”
android:allowBackup=”true”
android:hardwareAccelerated=”true”
android:supportsRtl=”true”
android:roundIcon=”@ref/0x7f030001″>

<activity
android:name=”.MainActivity”>

<intent-filter>

<action
android:name=”android.intent.action.MAIN” />

<category
android:name=”android.intent.category.DEFAULT” />
</intent-filter>
</activity>
</application>
</manifest>

9 打印App ID
apkanalyzer manifest application-id apk-file

$ ./apkanalyzer manifest application-id Animal.apk
a.b.animal

10 打印版本名
apkanalyzer manifest version-name apk-file

$ ./apkanalyzer manifest version-name Animal.apk
8.1.0

11 打印版本号
apkanalyzer manifest version-code apk-file

$ ./apkanalyzer manifest version-code Animal.apk
27

12 打印*低的SDK版本
apkanalyzer manifest min-sdk apk-file

$ ./apkanalyzer manifest min-sdk Animal.apk
27

13 打印目标SDK版本
apkanalyzer manifest target-sdk apk-file

$ ./apkanalyzer manifest target-sdk Animal.apk
27

14 打印用到的权限列表
apkanalyzer manifest permissions apk-file

$ ./apkanalyzer manifest permissions Animal.apk
android.permission.CHANGE_OVERLAY_PACKAGES
android.permission.MODIFY_THEME_OVERLAY
android.permission.INTERACT_ACROSS_USERS_FULL
android.permission.INTERACT_ACROSS_USERS

15 打印是否debug模式
apkanalyzer manifest debuggable apk-file

$ ./apkanalyzer manifest debuggable Animal.apk
false

16 打印apk中的dex文件列表
apkanalyzer dex list apk-file

$ ./apkanalyzer dex list Animal.apk
classes.dex

17 打印dex文件中引用的方法个数(默认打印所有的dex文件)
apkanalyzer dex references [–files path] [–files path2] apk-file

$ ./apkanalyzer dex references Animal.apk
classes.dex 64

18 打印dex文件中的类信息
apkanalyzer dex packages [option1 option2 …] apk-file
options包括:–defined-only、–files、–proguard-folder file、–proguard-mappings file、–proguard-seeds file、–proguard-usages file、–show-removed

$ ./apkanalyzer dex packages Animal.apk
P d 28 64 5214 <TOTAL>
P d 28 31 4448 a
P d 28 31 4448 a.b
P d 28 31 4448 a.b.animal
C d 9 12 1136 a.b.animal.MainActivity
M d 1 1 58 a.b.animal.MainActivity int -get0(a.b.animal.MainActivity)
M d 1 1 58 a.b.animal.MainActivity android.content.om.IOverlayManager -get1(a.b.animal.MainActivity)
M d 1 1 55 a.b.animal.MainActivity <init>()
M d 1 1 453 a.b.animal.MainActivity void onCreate(android.os.Bundle)
M d 1 1 74 a.b.animal.MainActivity void onDestroy()
M d 1 1 73 a.b.animal.MainActivity void onPause()
M d 1 1 73 a.b.animal.MainActivity void onResume()
M d 1 1 73 a.b.animal.MainActivity void onStart()
M d 1 1 74 a.b.animal.MainActivity void onStop()
M r 0 1 26 a.b.animal.MainActivity android.view.View findViewById(int)
M r 0 1 20 a.b.animal.MainActivity android.content.res.Resources getResources()
M r 0 1 26 a.b.animal.MainActivity void setContentView(int)
F d 0 0 12 a.b.animal.MainActivity java.lang.String TAG
F d 0 0 10 a.b.animal.MainActivity int mCurrentUserId
F d 0 0 10 a.b.animal.MainActivity android.content.om.IOverlayManager mOverlayManager
C d 4 4 369 a.b.animal.AnimalApp
M d 1 1 75 a.b.animal.AnimalApp <init>()
M d 1 1 109 a.b.animal.AnimalApp void onConfigurationChanged(android.content.res.Configuration)
M d 1 1 66 a.b.animal.AnimalApp void onCreate()
M d 1 1 66 a.b.animal.AnimalApp void onTerminate()
F d 0 0 12 a.b.animal.AnimalApp java.lang.String TAG
C d 2 2 620 a.b.animal.MainActivity$1
M d 1 1 73 a.b.animal.MainActivity$1 <init>(a.b.animal.MainActivity)
M d 1 1 462 a.b.animal.MainActivity$1 void onClick(android.view.View)
F d 0 0 11 a.b.animal.MainActivity$1 a.b.animal.MainActivity this$0
C d 2 2 479 a.b.animal.MainActivity$2
M d 1 1 75 a.b.animal.MainActivity$2 <init>(a.b.animal.MainActivity)
M d 1 1 319 a.b.animal.MainActivity$2 void onClick(android.view.View)
F d 0 0 11 a.b.animal.MainActivity$2 a.b.animal.MainActivity this$0
C d 2 2 479 a.b.animal.MainActivity$3
M d 1 1 75 a.b.animal.MainActivity$3 <init>(a.b.animal.MainActivity)
M d 1 1 319 a.b.animal.MainActivity$3 void onClick(android.view.View)
F d 0 0 11 a.b.animal.MainActivity$3 a.b.animal.MainActivity this$0
C d 1 1 123 a.b.animal.R$attr
M d 1 1 55 a.b.animal.R$attr <init>()
C d 1 1 139 a.b.animal.R$color
M d 1 1 55 a.b.animal.R$color <init>()
F d 0 0 15 a.b.animal.R$color int color_default
C d 1 1 184 a.b.animal.R$drawable
M d 1 1 55 a.b.animal.R$drawable <init>()
F d 0 0 15 a.b.animal.R$drawable int animal
F d 0 0 15 a.b.animal.R$drawable int ic_launcher_background
F d 0 0 15 a.b.animal.R$drawable int ic_launcher_foreground
F d 0 0 15 a.b.animal.R$drawable int ic_launcher_foreground_1
C d 1 1 184 a.b.animal.R$id
M d 1 1 55 a.b.animal.R$id <init>()
F d 0 0 15 a.b.animal.R$id int image_animal
F d 0 0 15 a.b.animal.R$id int theme_default
F d 0 0 15 a.b.animal.R$id int theme_dog
F d 0 0 15 a.b.animal.R$id int theme_monkey
C d 1 1 139 a.b.animal.R$layout
M d 1 1 55 a.b.animal.R$layout <init>()
F d 0 0 15 a.b.animal.R$layout int layout_main
C d 1 1 154 a.b.animal.R$mipmap
M d 1 1 55 a.b.animal.R$mipmap <init>()
F d 0 0 15 a.b.animal.R$mipmap int ic_launcher
F d 0 0 15 a.b.animal.R$mipmap int ic_launcher_round
C d 1 1 184 a.b.animal.R$string
M d 1 1 55 a.b.animal.R$string <init>()
F d 0 0 15 a.b.animal.R$string int app_name
F d 0 0 15 a.b.animal.R$string int theme_default
F d 0 0 15 a.b.animal.R$string int theme_dog
F d 0 0 15 a.b.animal.R$string int theme_monkey
C d 1 1 139 a.b.animal.R$style
M d 1 1 55 a.b.animal.R$style <init>()
F d 0 0 15 a.b.animal.R$style int AppTheme
C d 1 1 119 a.b.animal.R
M d 1 1 55 a.b.animal.R <init>()
P r 0 18 430 android
P r 0 9 186 android.app
C r 0 7 146 android.app.Activity
M r 0 1 20 android.app.Activity <init>()
M r 0 1 26 android.app.Activity void onCreate(android.os.Bundle)
M r 0 1 20 android.app.Activity void onDestroy()
M r 0 1 20 android.app.Activity void onPause()
M r 0 1 20 android.app.Activity void onResume()
M r 0 1 20 android.app.Activity void onStart()
M r 0 1 20 android.app.Activity void onStop()
C r 0 1 20 android.app.ActivityManager
M r 0 1 20 android.app.ActivityManager int getCurrentUser()
C r 0 1 20 android.app.Application
M r 0 1 20 android.app.Application <init>()
P r 0 4 108 android.content
P r 0 3 82 android.content.om
C r 0 2 56 android.content.om.IOverlayManager
M r 0 1 26 android.content.om.IOverlayManager java.util.Map getAllOverlays(int)
M r 0 1 30 android.content.om.IOverlayManager boolean setEnabledExclusive(java.lang.String,boolean,int)
C r 0 1 26 android.content.om.IOverlayManager$Stub
M r 0 1 26 android.content.om.IOverlayManager$Stub android.content.om.IOverlayManager asInterface(android.os.IBinder)
P r 0 1 26 android.content.res
C r 0 1 26 android.content.res.Resources
M r 0 1 26 android.content.res.Resources int getColor(int)
P r 0 2 58 android.util
C r 0 2 58 android.util.Log
M r 0 1 28 android.util.Log int d(java.lang.String,java.lang.String)
M r 0 1 30 android.util.Log int w(java.lang.String,java.lang.String,java.lang.Throwable)
P r 0 2 52 android.widget
C r 0 2 52 android.widget.Button
M r 0 1 26 android.widget.Button void setBackgroundColor(int)
M r 0 1 26 android.widget.Button void setOnClickListener(android.view.View$OnClickListener)
P r 0 1 26 android.os
C r 0 1 26 android.os.ServiceManager
M r 0 1 26 android.os.ServiceManager android.os.IBinder getService(java.lang.String)
P r 0 15 336 java
P r 0 8 184 java.lang
C r 0 6 144 java.lang.StringBuilder
M r 0 1 20 java.lang.StringBuilder <init>()
M r 0 1 26 java.lang.StringBuilder java.lang.StringBuilder append(int)
M r 0 1 26 java.lang.StringBuilder java.lang.StringBuilder append(java.lang.Object)
M r 0 1 26 java.lang.StringBuilder java.lang.StringBuilder append(java.lang.String)
M r 0 1 26 java.lang.StringBuilder java.lang.StringBuilder append(boolean)
M r 0 1 20 java.lang.StringBuilder java.lang.String toString()
C r 0 1 20 java.lang.Object
M r 0 1 20 java.lang.Object <init>()
C r 0 1 20 java.lang.Iterable
M r 0 1 20 java.lang.Iterable java.util.Iterator iterator()
P r 0 7 152 java.util
C r 0 3 66 java.util.Map
M r 0 1 26 java.util.Map java.lang.Object get(java.lang.Object)
M r 0 1 20 java.util.Map boolean isEmpty()
M r 0 1 20 java.util.Map java.util.Set keySet()
C r 0 2 40 java.util.Iterator
M r 0 1 20 java.util.Iterator boolean hasNext()
M r 0 1 20 java.util.Iterator java.lang.Object next()
C r 0 2 46 java.util.List
M r 0 1 26 java.util.List java.lang.Object get(int)
M r 0 1 20 java.util.List int size()

19 打印类的字节码或者方法的反编译格式
apkanalyzer dex code –class class [–method method]

$ ./apkanalyzer dex code –class a.b.animal.MainActivity Animal.apk
.class public La/b/animal/MainActivity;
.super Landroid/app/Activity;
.source “MainActivity.java”

# static fields
.field private static final TAG:Ljava/lang/String; = “Animal”

# instance fields
.field private mCurrentUserId:I

.field private mOverlayManager:Landroid/content/om/IOverlayManager;

# direct methods
.method static synthetic -get0(La/b/animal/MainActivity;)I
.registers 2
.param p0, “-this” # La/b/animal/MainActivity;

.prologue
iget v0, p0, La/b/animal/MainActivity;->mCurrentUserId:I

return v0
.end method

.method static synthetic -get1(La/b/animal/MainActivity;)Landroid/content/om/IOverlayManager;
.registers 2
.param p0, “-this” # La/b/animal/MainActivity;

.prologue
iget-object v0, p0, La/b/animal/MainActivity;->mOverlayManager:Landroid/content/om/IOverlayManager;

return-object v0
.end method

.method public constructor <init>()V
.registers 1

.prologue
.line 19
invoke-direct {p0}, Landroid/app/Activity;-><init>()V

return-void
.end method

# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
.registers 9
.param p1, “savedInstanceState” # Landroid/os/Bundle;

.prologue
const/high16 v6, 0x7f050000

.line 27
const-string/jumbo v3, “Animal”

const-string/jumbo v4, “onCreate”

invoke-static {v3, v4}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

.line 29
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

.line 31
const/high16 v3, 0x7f040000

invoke-virtual {p0, v3}, La/b/animal/MainActivity;->setContentView(I)V

.line 33
invoke-static {}, Landroid/app/ActivityManager;->getCurrentUser()I

move-result v3

iput v3, p0, La/b/animal/MainActivity;->mCurrentUserId:I

.line 34
const-string/jumbo v3, “Animal”

new-instance v4, Ljava/lang/StringBuilder;

invoke-direct {v4}, Ljava/lang/StringBuilder;-><init>()V

const-string/jumbo v5, “CurrentUser ”

invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v4

iget v5, p0, La/b/animal/MainActivity;->mCurrentUserId:I

invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

move-result-object v4

invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v4

invoke-static {v3, v4}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

.line 37
const-string/jumbo v3, “overlay”

invoke-static {v3}, Landroid/os/ServiceManager;->getService(Ljava/lang/String;)Landroid/os/IBinder;

move-result-object v3

.line 36
invoke-static {v3}, Landroid/content/om/IOverlayManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/om/IOverlayManager;

move-result-object v3

iput-object v3, p0, La/b/animal/MainActivity;->mOverlayManager:Landroid/content/om/IOverlayManager;

.line 38
const-string/jumbo v3, “Animal”

new-instance v4, Ljava/lang/StringBuilder;

invoke-direct {v4}, Ljava/lang/StringBuilder;-><init>()V

const-string/jumbo v5, “OverlayManager ”

invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v4

iget-object v5, p0, La/b/animal/MainActivity;->mOverlayManager:Landroid/content/om/IOverlayManager;

invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;

move-result-object v4

invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v4

invoke-static {v3, v4}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

.line 40
const v3, 0x7f080001

invoke-virtual {p0, v3}, La/b/animal/MainActivity;->findViewById(I)Landroid/view/View;

move-result-object v0

check-cast v0, Landroid/widget/Button;

.line 41
.local v0, “themeDefault”:Landroid/widget/Button;
invoke-virtual {p0}, La/b/animal/MainActivity;->getResources()Landroid/content/res/Resources;

move-result-object v3

invoke-virtual {v3, v6}, Landroid/content/res/Resources;->getColor(I)I

move-result v3

invoke-virtual {v0, v3}, Landroid/widget/Button;->setBackgroundColor(I)V

.line 42
new-instance v3, La/b/animal/MainActivity$1;

invoke-direct {v3, p0}, La/b/animal/MainActivity$1;-><init>(La/b/animal/MainActivity;)V

invoke-virtual {v0, v3}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

.line 69
const v3, 0x7f080002

invoke-virtual {p0, v3}, La/b/animal/MainActivity;->findViewById(I)Landroid/view/View;

move-result-object v1

check-cast v1, Landroid/widget/Button;

.line 70
.local v1, “themeDog”:Landroid/widget/Button;
invoke-virtual {p0}, La/b/animal/MainActivity;->getResources()Landroid/content/res/Resources;

move-result-object v3

invoke-virtual {v3, v6}, Landroid/content/res/Resources;->getColor(I)I

move-result v3

invoke-virtual {v1, v3}, Landroid/widget/Button;->setBackgroundColor(I)V

.line 71
new-instance v3, La/b/animal/MainActivity$2;

invoke-direct {v3, p0}, La/b/animal/MainActivity$2;-><init>(La/b/animal/MainActivity;)V

invoke-virtual {v1, v3}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

.line 88
const v3, 0x7f080003

invoke-virtual {p0, v3}, La/b/animal/MainActivity;->findViewById(I)Landroid/view/View;

move-result-object v2

check-cast v2, Landroid/widget/Button;

.line 89
.local v2, “themeMonkey”:Landroid/widget/Button;
invoke-virtual {p0}, La/b/animal/MainActivity;->getResources()Landroid/content/res/Resources;

move-result-object v3

invoke-virtual {v3, v6}, Landroid/content/res/Resources;->getColor(I)I

move-result v3

invoke-virtual {v2, v3}, Landroid/widget/Button;->setBackgroundColor(I)V

.line 90
new-instance v3, La/b/animal/MainActivity$3;

invoke-direct {v3, p0}, La/b/animal/MainActivity$3;-><init>(La/b/animal/MainActivity;)V

invoke-virtual {v2, v3}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

.line 106
return-void
.end method

.method protected onDestroy()V
.registers 3

.prologue
.line 134
const-string/jumbo v0, “Animal”

const-string/jumbo v1, “onDestroy”

invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

.line 135
invoke-super {p0}, Landroid/app/Activity;->onDestroy()V

.line 136
return-void
.end method

.method protected onPause()V
.registers 3

.prologue
.line 122
const-string/jumbo v0, “Animal”

const-string/jumbo v1, “onPause”

invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

.line 123
invoke-super {p0}, Landroid/app/Activity;->onPause()V

.line 124
return-void
.end method

.method protected onResume()V
.registers 3

.prologue
.line 116
const-string/jumbo v0, “Animal”

const-string/jumbo v1, “onResume”

invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

.line 117
invoke-super {p0}, Landroid/app/Activity;->onResume()V

.line 118
return-void
.end method

.method protected onStart()V
.registers 3

.prologue
.line 110
const-string/jumbo v0, “Animal”

const-string/jumbo v1, “onStart”

invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

.line 111
invoke-super {p0}, Landroid/app/Activity;->onStart()V

.line 112
return-void
.end method

.method protected onStop()V
.registers 3

.prologue
.line 128
const-string/jumbo v0, “Animal”

const-string/jumbo v1, “onStop”

invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

.line 129
invoke-super {p0}, Landroid/app/Activity;->onStop()V

.line 130
return-void
.end method

20 打印资源表中的包
apkanalyzer resources packages apk-file

$ ./apkanalyzer resources packages Animal.apk
a.b.animal

21 打印某个资源类型的配置
apkanalyzer resources configs –type type [–package package] apk-file

$ ./apkanalyzer resources configs –type string Animal.apk
default
en-rXA
ar-rXB

22 打印某个资源的值
apkanalyzer resources value –config config –name name –type type [–package package] apk-file

$ ./apkanalyzer resources value –config default –name app_name –type string Animal.apk
Animal

23 打印某种资源类型的所有名字
apkanalyzer resources names –config config –type type [–package package] apk-file

$ ./apkanalyzer resources names –config default –type string Animal.apk
app_name
theme_default
theme_dog
theme_monkey

24 打印二进制xml文件(纯文本格式输出)
apkanalyzer resources xml –file path apk-file
$ ./apkanalyzer resources xml –file /AndroidManifest.xml Animal.apk
<?xml version=”1.0″ encoding=”utf-8″?>
<manifest
xmlns:android=”http://schemas.android.com/apk/res/android”
android:sharedUserId=”android.uid.system”
android:versionCode=”27″
android:versionName=”8.1.0″
package=”a.b.animal”
platformBuildVersionCode=”27″
platformBuildVersionName=”8.1.0″>

<uses-sdk
android:minSdkVersion=”27″
android:targetSdkVersion=”27″ />

<uses-permission
android:name=”android.permission.MODIFY_THEME_OVERLAY” />

<uses-permission
android:name=”android.permission.CHANGE_OVERLAY_PACKAGES” />

<uses-permission
android:name=”android.permission.INTERACT_ACROSS_USERS” />

<uses-permission
android:name=”android.permission.INTERACT_ACROSS_USERS_FULL” />

<application
android:theme=”@ref/0x7f070000″
android:label=”@ref/0x7f060000″
android:icon=”@ref/0x7f030000″
android:name=”.AnimalApp”
android:allowBackup=”true”
android:hardwareAccelerated=”true”
android:supportsRtl=”true”
android:roundIcon=”@ref/0x7f030001″>

<activity
android:name=”.MainActivity”>

<intent-filter>

<action
android:name=”android.intent.action.MAIN” />

<category
android:name=”android.intent.category.DEFAULT” />
</intent-filter>
</activity>
</application>
</manifest>

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

【Android 声音处理】MediaPlayer和SoundPool

1、new SoundPool(4, AudioManager.STREAM_MUSIC, 100);
*个参数是允许有多少个声音流同时播放,第2个参数是声音类型,第三个参数是声音的品质。

2、使用MediaPlayer来播放音频文件存在一些不足:
如:资源占用量较高、延迟时间较长、不支持多个音频同时播放等。
这些缺点决定了MediaPlayer在某些场合的使用情况不会很理想,例如在对时间精准度要求相对较高的游戏开发中。
虽然MediaPlayer有提供自带的setLooping(true/false);这个方法,但在循环的间歇会有明显的停顿,应该是做了重新加载或者某些初始化的操作造成的吧,后来我决定不用它自带的循环函数来做循环,而是使用它的另一个函数:getCurrentPosition(),用它可以检测声音播放的偏移,我让它刚好放完或者快要放完的时候手动seekTo(position);这样就可以规避掉中间停顿的问题,代码如下:

if (mMediaPlayer.getCurrentPosition() >= 15800)
{
mMediaPlayer.seekTo(50);
}

3、 相对于使用SoundPool存在的一些问题:

(1) SoundPool*大只能申请1M的内存空间,这就意味着我们只能使用一些很短的声音片段,而不是用它来播放歌曲或者游戏背景音乐(背景音乐可以考虑使用JetPlayer来播放)。

(2) SoundPool提供了pause和stop方法,但这些方法建议*好不要轻易使用,因为有些时候它们可能会使你的程序莫名其妙的终止。还有些朋友反映它们不会立即中止播放声音,而是把缓冲区里的数据播放完才会停下来,也许会多播放一秒钟。

(3)音频格式建议使用OGG格式。使用WAV格式的音频文件存放游戏音效,经过反复测试,在音效播放间隔较短的情况下会出现异常关闭的情况(有说法是SoundPool目前只对16bit的WAV文件有较好的支持)。后来将文件转成OGG格式,问题得到了解决。

(4)在使用SoundPool播放音频的时候,如果在初始化中就调用播放函数进行播放音乐那么根本没有声音,不是因为没有执行,而是SoundPool需要一准备时间!当然这个准备时间也很短,不会影响使用,只是程序一运行就播放会没有声音罢了。

4、SoundPool*大只能申请1M的内存空间:(还有种说法是不能超过5.6秒)
SoundPool.load()方法返回音频id,而且这个id会因音频文件的大小而变大变小,那么一旦我们的音频文件超过int*大值,那么就会报内存错误的异常。所以为什么用SoundPool只能播放一些简短的音频这就是其原因了。

5、怎么才知道一首歌曲播放完了,那么这里给说下:
PlaybackCompleted状态:文件正常播放完毕,而又没有设置循环播放的话就进入该状态,并会触发OnCompletionListener的onCompletion()方法。此时可以调用start()方法重新从头播放文件,也可以stop()停止MediaPlayer,或者也可以seekTo()来重新定位播放位置。

注意:
(1)别忘记绑定操作! mp.setOnCompletionListener(this);
(2)如果你设置了循环播放 mp.setLooping(true); 的话,那么永远都不会监听到播放完成的状态!!!!这里一定要注意!

6、调节音量:

//
// Android OS中,如果你去按手机上的调节音量的按钮,会分两种情况,
// 一种是调整手机本身的铃声音量,一种是调整游戏,软件,音乐播放的音量
// 当我们在游戏中的时候 ,总是想调整游戏的音量而不是手机的铃声音量,
// 可是烦人的问题又来了,我在开发中发现,只有游戏中有声音在播放的时候
// ,你才能去调整游戏的音量,否则就是手机的音量,有没有办法让手机只要是
// 在运行游戏的状态就只调整游戏的音量呢?试试下面这段代码吧!
//
// 设定调整音量为媒体音量,当暂停播放的时候调整音量就不会再默认调整铃声音量了
MainActivity.instance.setVolumeControlStream(AudioManager.STREAM_MUSIC);

AudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)volume, AudioManager.FLAG_PLAY_SOUND);
是设置指定类型的所有流的音量;
MediaPlayer.setVolume(leftVolume, rightVolume);
是设置左右声道的音量, 但只设置当前player。

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

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速