iOS开发-登录界面开发(4)AFNetworking的引入-Swfit4.1_Xcode9.3.1

1.AFNetworking是个啥?

  网络请求开源框架, iOS和Mac OS都能用。

2.SwiftyJSON是个啥?

  另一个开源框架,处理JSON数据(解析数据、生成数据)。

3.让我们来引入AFNetworking、SwiftyJSON:

3.1.找到你项目的地址:(顺便介绍一个快捷键,option+command+c,复制文件或者文件夹的路径)

/Users/jimi/Documents/iOS/Demo/Myy

3.2.打开终端,输入,回车:

cd /Users/jimi/Documents/iOS/Demo/Myy

3.3.创建一个 Podfile:

touch Podfile

3.4.你可以选择使用命令行编辑,比如:“vim Podfile”回车,开始编辑,按“i”,进行插入,插入下面的语句,按Esc,按:,按wq+回车,进行保存退出。

或者你可以,直接把下面的语句,复制到 Podfile 文件里:

# 设置支持*低平台
platform :ios, '8.0'
target 'Myy' do
# 如果是Swift项目,需添加"use_frameworks!"
use_frameworks!
# 如果要使用固定版本 可以像下面这么写
# pod 'AFNetworking', '~> 3.2.1'
pod "AFNetworking"
pod "SwiftyJSON"
end

3.5.开始下载引入:

pod install

4.可以打开引入第三方框架的工程了,打开这个文件,就可以了:

/Users/jimi/Documents/iOS/Demo/Myy/Myy.xcworkspace

5.这样,我们就同时引入2个开源框架了。引入的是*新版本,如果你特殊需求,可以像注释写得一样,引入指定版本。

工作记录8:iOS 传值问题总结(7种传值完美介绍)

1、属性传值

前向后传值。

记住:

/*

1: 属性传值*步需要用到什么类型就定义什么样的属性

2: 从上一个页面到一个页面的选中方法里面将要传的值传到来(上一个页面)备注:这种方法只适用于上一个页面推到下一个页面

*/

MainViewController与SecondViewController两个视图 控制器 ,点击MainViewController中的按钮将跳转到SecondViewController视图,同时想要传递一个值过去。这时可以利用属性传值。

首先SecondViewController视图中需要有一个属性用来 存储 传递过来的值:

@property(nonatomic,retain) NSString *firstValue ;//属性传值

然后MainViewController视图需要引用SecondViewController视图的头文件,在视图中的按钮点击事件中,通过SecondViewController的对象将需要传递的值存在firstValue中:

(void)buttonAction:(UIButton *)button
{
SecondViewController *second = 
[[SecondViewController alloc]init];//用下一个视图的属性接受想要传过去的值,属性传值
second.firstValue = _txtFiled.text;
[self.navigationController pushViewController:second animated:YES];
}

页面跳转之后,就能在SecondViewController视图中,通过存值的属性,取用刚才传递过来的值:

//显示传过来的值[_txtFiled setText:_firstValue];//firstValue保存传过来的值

2、方法传值:

需求同一中的 属性传值 一样,但是要通过使用方法传值,可以直接将方法与初始化方法合并,此时当触发MainViewController的按钮点击事件并跳转到 SecondViewController时,在按钮点击事件中可以直接通过SecondViewController的初始化,将值保存在 firstValue中:

初始化方法如下:                首先SecondViewController视图中需要有一个属性用来 存储 传递过来的值:

@property(nonatomic,retain) NSString *firstValue ;//传值用

 

//重写初始化方法,用于传值
- (id)initWithValue:(NSString *)value
{
if(self = [super initWithNibName:nil bundle:nil]) {
                 self.firstValue = value;
            }
        return self;
}

方法传值:

 

- (void)buttonAction:(UIButton *)button
{//将方法传值与初始化写到一起
SecondViewController *second = [[SecondViewController alloc]
initWithValue:_txtFiled.text];//此时已经将值存在firstValue中
[self.navigationController pushViewController:second animated:YES];
}

这样就可以直接通过firstValue属性获得传递过来的值:

 

//显示传过来的值[_txtFiled setText:_firstValue];//firstValue保存传过来的值

3、协议传值   代替协议代理传值,主要时间点问题。

上面 中说明了如何从A传值到B,这次要讲的是如何从A进入B,在B输入值后回传给A,这类似于Android中的利用 Activity的onActivityResult回调方法实现两个Activity之间的值传递,那么在IOS中如何实现这个功能呢,答案是使用 Delegate(委托协议)。

工程结构如下:

1346122481_1896.png

其中有两个ViewController分别对应两个界面,一个协议PassValueDelegate用来实现传值协议,UserEntity是传递数据的对象。
以下是实现的效果:点击Open进入Second界面,输入完毕点击OK后回到First界面并显示结果

1346122704_8626.png   1346122709_1735.png

1346122728_4358.png

协议中声明的方法:

copy
  1. #import <Foundation/Foundation.h>
  2. @ class  UserEntity;
  3. @protocol PassValueDelegate <NSObject>
  4. -( void )passValue:(UserEntity *)value;
  5. @end
  6. 在*个窗口实现协议:
  1. #import <UIKit/UIKit.h>
  2. #import “PassValueDelegate.h”
  3. //*个窗口遵守PassValueDelegate
  4. @interface ViewController : UIViewController<PassValueDelegate>
  5. @property (retain, nonatomic) IBOutlet UILabel *nameLabel;
  6. @property (retain, nonatomic) IBOutlet UILabel *ageLabel;
  7. @property (retain, nonatomic) IBOutlet UILabel *gendarLabel;
  8. – (IBAction)openBtnClicked:(id)sender;
  9. @end

.m文件中实现协议的方法:

[cpp]view plaincopy

  1. //实现协议,在*个窗口显示在第二个窗口输入的值方法
  2. -( void )passValue:(UserEntity *)value
  3. {
  4.     self.nameLabel.text = value.userName;
  5.     self.ageLabel.text = [NSString stringWithFormat:@ “%d” ,value.age];
  6.     self.gendarLabel.text = value.gendar;
  7. }

点击Open按钮所触发的事件:

[cpp]view plaincopy

  1. //点击进入第二个窗口的方法
  2. – (IBAction)openBtnClicked:(id)sender {
  3.     SecondViewController *secondView = [[SecondViewController alloc] initWithNibName:@ “SecondViewController”  bundle:[NSBundle mainBundle]];
  4.      //设置第二个窗口中的delegate为*个窗口的self
  5.     secondView.delegate = self;
  6.     [self.navigationController pushViewController:secondView animated:YES];
  7. }

第二个窗口中声明一个NSObject对象,该对象遵守PassValueDelegate协议:

[cpp]view plaincopy

  1. #import <UIKit/UIKit.h>
  2. #import “PassValueDelegate.h”
  3. @interface SecondViewController : UIViewController
  4. @property (retain, nonatomic) IBOutlet UITextField *nameTextField;
  5. @property (retain, nonatomic) IBOutlet UITextField *ageTextFiled;
  6. @property (retain, nonatomic) IBOutlet UITextField *gendarTextField;
  7. //这里用assign而不用retain是为了防止引起循环引用。
  8. @property(nonatomic,assign) NSObject<PassValueDelegate> *delegate;
  9. – (IBAction)okBtnClicked:(id)sender;
  10. – (IBAction)closeKeyboard:(id)sender;
  11. @end

输入完毕后,点击OK按钮所触发的事件:

[cpp]view plaincopy

  1. – (IBAction)okBtnClicked:(id)sender {
  2.     UserEntity *userEntity = [[UserEntity alloc] init];
  3.     userEntity.userName = self.nameTextField.text;
  4.     userEntity.gendar = self.gendarTextField.text;
  5.     userEntity.age = [self.ageTextFiled.text intValue];
  6.      //通过委托协议传值
  7.     [self.delegate passValue:userEntity];
  8.      //退回到*个窗口
  9.     [self.navigationController popViewControllerAnimated:YES];
  10.     [userEntity release];
  11. }

以上就实现了使用Delegate在两个ViewController之间传值,这种场景一般应用在进入子界面输入信息,完后要把输入的信息回传给前一个界面的情况,比如修改用户个人信息,点击修改进入修改界面,修改完后到显示界面显示修改后的结果。

4、Block传值                                               //参考 http://liuyafang.blog.51cto.com/8837978/1551399

1.*页中 声明一个 block, 需要传入一个颜色 , 让当前的 view 变色

// 声明一个 block, 需要传入一个颜色 , 让当前的 view 变色

void (^changeColor)( UIColor *color) = ^( UIColor *color){

self . view . backgroundColor = color;

};

2 .  *页中 //block 传值 ——— 将 block 给第二个页面

SecondViewController *secondVC = [[ SecondViewController   alloc ]  init ];

//block 传值 ——— 将 block 给第二个页面

secondVC. block = changeColor;

3.第二页中定义 — 当 block 变量作为一个类的属性 , 必须要使用 copy 修饰

//block 传值 ——— 将 block 给第二个页面

//block 传值 — 当 block 变量作为一个类的属性 , 必须要使用 copy 修饰

@property ( nonatomic  ,  copy ) void (^block)( UIColor  *color);

4.在第二页中给block传值

//block 传值 ——— 将传值给 block

NSArray  *array = [ NSArray   arrayWithObjects :[ UIColor   yellowColor ], [ UIColor   cyanColor ], [ UIColor   greenColor ], [ UIColor   brownColor ],  nil ];

self . block ([array  objectAtIndex : rand () %  4 ]);

类和文件

AppDelegate.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

#import "AppDelegate.h"

#import "MainViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

// Override point for customization after application launch.

self.window.backgroundColor = [UIColor whiteColor];

[self.window makeKeyAndVisible];

 

MainViewController *mainVC = [[MainViewController alloc] init];

UINavigationController *navVc = [[UINavigationController alloc] initWithRootViewController:mainVC];

self.window.rootViewController = navVc;

//模糊效果

navVc.navigationBar.translucent = YES;

[navVc release];

[mainVC release];

 

 

 

 

[_window release];

return YES;

}

- (void)dealloc

{

[_window release];

[ super dealloc];

}

- (void)applicationWillResignActive:(UIApplication *)application

{

// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.

// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.

}

- (void)applicationDidEnterBackground:(UIApplication *)application

{

// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.

// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

}

- (void)applicationWillEnterForeground:(UIApplication *)application

{

// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.

}

- (void)applicationDidBecomeActive:(UIApplication *)application

{

// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

}

- (void)applicationWillTerminate:(UIApplication *)application

{

// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.

}

@end

MainViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

#import "MainViewController.h"

#import "SecondViewController.h"

@interface MainViewController ()

@end

@implementation MainViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

{

self = [ super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

if (self) {

// Custom initialization

}

return self;

}

- (void)viewDidLoad

{

[ super viewDidLoad];

// Do any additional setup after loading the view.

self.title = @ "block传值" ;

UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];

button.frame = CGRectMake(120, 100, 80, 30);

button.backgroundColor = [UIColor magentaColor];

[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];

[button setTitle:@ "按钮" forState:UIControlStateNormal];

button.layer.cornerRadius = 5;

[button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];

[self.view addSubview:button];

}

- (void)buttonClicked:(UIButton *)button

{

//block语法

//返回值类型 (^block参数名) (参数类型 参数名) = ^返回值类型 (参数类型 参数名) {

//具体实现;

//};

 

float b = 0;

 

//1.无参数无返回值

void(^block1)(void) = ^(void){

NSLog(@ "可口可乐" );

};

//block语法调用

block1();

 

 

//2.有参数,无返回值

void(^block2)(NSString *str1, NSString *str2) = ^void(NSString *str1, NSString *str2){

NSString *a = [str1 stringByAppendingString:str2];

NSLog(@ "%@" , a);

};

block2(@ "abc" ,@ "def" );

 

 

//3.有返回值,无参数

NSString *(^block3)(void) = ^NSString *(void){

return @ "咿呀咿呀呦" ;

};

NSLog(@ "%@" ,block3());

//4.有参数,有返回值

NSString *(^block4)(NSString *str1) =^NSString *(NSString *str1){

return [str1 stringByAppendingString:@ "真棒!!!!" ];

};

NSLog(@ "%@" , block4(@ "苹果电脑" ));

 

//声明一个block,需要传入一个颜色,让当前的view变色

void(^changeColor)(UIColor *color) = ^(UIColor *color){

self.view.backgroundColor = color;

};

//block传值------------声明一个

void(^changeValue)(UITextField *textField) = ^void(UITextField *textField){

[button setTitle:textField.text forState:UIControlStateNormal];

};

NSLog(@ "%@" , block1); //block的地址在全局区

NSLog(@ "%@" , changeColor); //如果在block的代码中,使用了block外部的变量,系统会把block指针转移到栈区

 

SecondViewController *secondVC = [[SecondViewController alloc] init];

//block传值---------将block给第二个页面

secondVC.block = changeColor;

secondVC.blockofValue = changeValue;

secondVC.name = button.currentTitle;

NSLog(@ "%@" , button.currentTitle);

NSLog(@ "%@" , secondVC.block); //使用copy后block会被系统转移到堆区

[self.navigationController pushViewController:secondVC animated:YES];

[secondVC release];

}

- (void)didReceiveMemoryWarning

{

[ super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

/*

#pragma mark - Navigation

{

// Get the new view controller using [segue destinationViewController].

// Pass the selected object to the new view controller.

}

*/

@end

SecondViewController.h

1
2
3
4
5
6
7
8
9

#import <UIKit/UIKit.h>

@interface SecondViewController : UIViewController

@property (nonatomic , copy)void(^block)(UIColor *color);

@property (nonatomic , copy)void(^blockofValue)(UITextField *textField);

//

@property (nonatomic , copy)NSString *name;

@end

SecondViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

import "SecondViewController.h"

@interface SecondViewController ()

@property (nonatomic , retain)UITextField *textField;

@end

@implementation SecondViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

{

self = [ super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

if (self) {

// Custom initialization

}

return self;

}

- (void)viewDidLoad

{

[ super viewDidLoad];

// Do any additional setup after loading the view.

self.view.backgroundColor = [UIColor whiteColor];

self.textField = [[UITextField alloc] initWithFrame:CGRectMake(50, 100, 220, 30)];

self.textField.borderStyle = UITextBorderStyleRoundedRect;

//

self.textField.text = self.name;

NSLog(@ "%@" ,self.name);

self.textField.clearButtonMode = UITextFieldViewModeAlways;

[self.view addSubview:self.textField];

[_textField release];

 

 

UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 180, 120, 30)];

button.backgroundColor = [UIColor cyanColor];

[button setTitle:@ "点击" forState:UIControlStateNormal];

button.layer.cornerRadius = 5;

[button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];

[self.view addSubview:button];

}

- (void)buttonClicked:(UIButton *)button

{

//block传值---------将传值给block

NSArray *array = [NSArray arrayWithObjects:[UIColor yellowColor], [UIColor cyanColor], [UIColor greenColor], [UIColor brownColor], nil];

self.block([array objectAtIndex:rand() % 4]);

//block传值---------将传值给block

self.blockofValue(self.textField);

[self.navigationController popToRootViewControllerAnimated:YES];

}

- (void)didReceiveMemoryWarning

{

[ super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

/*

#pragma mark - Navigation

{

// Get the new view controller using [segue destinationViewController].

// Pass the selected object to the new view controller.

}

*/

@end

5 单例 传值

单例只会对某个类实例化一次/单例类,对单例这个类实例化一次有且仅有一个对象

你单例初始化,只能初始化一次,然后你指向的对象,其实都是指向一个内存地址,也就是同一块内存,所以都是一样的/

那么,只能有一个对象,就是实例化的那个

(1)定义单例类singleton      .h文件

#import <Foundation/Foundation.h>

@interface singleton : NSObject  //步骤一

//@property (strong,nonatomic) UITextField *value;//*开始的时候把这个value定义为UITextField了,然后在init里面又没有初始化它,就取不到值。任何对象都要初始化它才能使用。

@property (strong, nonatomic) NSString *value;

//+(id)shareData:

+(singleton *)shareData;  //步骤二

@end

//.m文件

#import “singleton.h”

@implementation singleton

 

static singleton *singletonData = nil;  //步骤三

+(singleton *)shareData {  //步骤四

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

singletonData = [[singleton alloc] init];

});

return singletonData;

}

-(id)init {  //步骤五

if (self = [super init]) {

//        self.value = [[UITextField alloc]init];

}

return self;

}

@end

//以上是一个完整单例子

(2)ViewController

#import <UIKit/UIKit.h>

#import “OneViewController.h”

#import “singleton.h”   //引用单例

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextField *qqTextfield;

– (IBAction)go:(id)sender;

@end

– (IBAction)go:(id)sender {

//单例的使用

singleton *oneS = [singleton shareData];

//    oneS.value.text = self.qqTextfield.text;

oneS.value = self.qqTextfield.text;

OneViewController *oneVC = [[OneViewController alloc]init];

[self presentViewController:oneVC animated:YES completion:nil];

}

(3)OneViewController

#import <UIKit/UIKit.h>

#import “singleton.h”

@interface OneViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextField *oneTextField;

@end

– (void)viewDidLoad

{

[super viewDidLoad];

// Do any additional setup after loading the view from its nib.

self.oneTextField.text = [singleton shareData].value;

}

6:数据共享。

OS app之间要共享数据不是那么容易,因为每个app都是sandbox。然后,有时候又不得不跟其它app之间共享数据,那应该怎么办呢?

下面是一些常用方法的总结:

  •  

    UIDocumentInteractionController

    Availability: iOS 3.2+

    具体用法参见: http://mobile.tutsplus.com/tutorials/iphone/previewing-and-opening-documents-with-uidocumentinteractioncontroller/

  • UIActivityViewController
    Availability: iOS 6.0+
  • Shared Keychain Access
    这个要求app之间用的是同样的证书
  • Custom URL Scheme
    通过构造URL,把数据作为参数传递过去。 本地测试过,传递10000个字符都可以,不过不要太长,内存可能吃不消。
  • Web Service 通过dropbox或者其他第三方的服务来共享数据。
  • UIPasteboard + URL Scheme 通过URL scheme传递UIPasteboard的名称,然后通过UIPasteboard共享数据。
    微信iOS SDK应该采用的就是这种方式。
    不过在iOS 7上,这种方法会存在问题,如果采用这种方案,得赶紧想办法解决。

1)  http://enharmonichq.com/sharing-data-locally-between-ios-apps/

2)  http://stackoverflow.com/questions/17080074/ios7-beta-doesnt-allow-inter-app-communication-by-uipasteboard

 

7:通知传值

通知中心
NSNotificationCenter提供了一种更加解耦的方式。*典型的应用就是任何对象对可以发送通知到中心,同时任何对象可以监听中心的通知。
发送通知的代码如下:

[[NSNotificationCenter defaultCenter] postNotificationName:@”myNotificationName” object:broadcasterObject];
注册接收通知的代码如下:

[[NSNotificationCenter defaultCenter] addObserver:listenerObject selector:@selector(receivingMethodOnListener:) name:@”myNotificationName” object:nil];
注册通知的时候可以指定一个具体的广播者对象,但这不是必须的。你可能注意到了defaultCenter 。实际上这是你在应用中会使用到的唯一的中心。通知会向整个应用开放,因此只有一个中心。
同时还有一个NSDistributedNotificationCenter。这是用来应用间通信的。在整个计算机上只有一个该类型的中心。
优点: 通知的发送者和接受者都不需要知道对方。可以指定接收通知的具体方法。通知名可以是任何字符串。
缺点: 较键值观察需要多点代码。在删掉前必须移除监听者。 不能传大量数值,只能让谁去做什么事。

ios 取消键盘响应

在FormViewController中,则不需要在实现UITextFieldDelegate,来对处于编辑状态的textField进行跟踪,也不必担心将来会添加N个UITextView,只要是在FormViewController下,我们只要调用  [self.view endEditing:YES];就可以了

http://my.oschina.net/hmj/blog/100020

iOS中的几种定时器详解

在软件开发过程中,我们常常需要在某个时间后执行某个方法,或者是按照某个周期一直执行某个方法。在这个时候,我们就需要用到定时器。

然而,在iOS中有很多方法完成以上的任务,经过查阅资料,大概有三种方法:NSTimer、CADisplayLink、GCD。接下来我就一一介绍它们的用法。

一、NSTimer

1.创建方法

  1. /**
  2. * 类方法创建定时器对象
  3. *
  4. * @property ti 之行之前等待的时间。比如设置为1.0,代表1秒后之行方法
  5. * @property aTarget 需要执行方法的对象
  6. * @property aSelector 需要执行的方法
  7. * @property userInfo 保存定时器使用者的一些信息
  8. * @property yesOrNo 是否需要循环
  9. *
  10. @return 返回定时器对象
  11. */
  12. + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti
  13. target:(id)target
  14. selector:(SEL)aSelector
  15. userInfo:(id)userInfo
  16. repeats:(BOOL)repeats;
  17. class func scheduledTimerWithTimeInterval(_ ti: NSTimeInterval,
  18. target aTarget: AnyObject,
  19. selector aSelector: Selector,
  20. userInfo userInfo: AnyObject?,
  21. repeats yesOrNo: Bool) -> NSTimer

2.释放方法

  1. – (void)invalidate;
  2. func invalidate()
  • 注意:
    调用创建方法后,target对象的计数器会加1,直到执行完毕,自动减1。如果是循环执行的话,就必须手动关闭,否则可以不执行释放方法。

3.特性

  • 存在延迟:
    不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoop和RunLoop Mode有关,如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行。
  • 必须加入Runloop:
    使用上面的创建方式,会自动把timer加入MainRunloop的NSDefaultRunLoopMode中。如果使用以下方式创建定时器,就必须手动加入Runloop:
  1. NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
  2. [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  • UIScrollView 拖动时执行的是 UITrackingRunLoopMode,会导致暂停定时器,等恢复为 NSDefaultRunLoopMode 时才恢复定时器。所以如果需要定时器在 UIScrollView 拖动时也不影响的话,建议添加到 UITrackingRunLoopMode 或 NSRunLoopCommonModes 中:

1.创建方法

  1. /**
  2. * 类方法创建显示连接对象
  3. *
  4. * @property target 执行方法的对象
  5. * @property sel 需要执行的方法
  6. *
  7. @return 返回显示连接对象
  8. */
  9. + (CADisplayLink *)displayLinkWithTarget:(id)target
  10. selector:(SEL)sel;
  11. /**
  12. * 调度显示连接器去发送通知
  13. *
  14. * @property runloop 运行循环
  15. * @property mode 运行循环的模式
  16. *
  17. @return
  18. */
  19. – (void)addToRunLoop:(NSRunLoop *)runloop
  20. forMode:(NSString *)mode;
  21. init(target target: AnyObject,
  22. selector sel: Selector)
  23. func addToRunLoop(_ runloop: NSRunLoop,
  24. forMode mode: String)

2.停止方法

  1. /* 当把CADisplayLink对象add到runloop中后,selector就能被周期性调用,
  2. 类似于重复的NSTimer被启动了;执行invalidate操作时,CADisplayLink对
  3. 象就会从runloop中移除,selector调用也随即停止,类似于NSTimer的invalidate
  4. 方法。*/
  5. – (void)invalidate;
  6. func invalidate()

3.特性

  • 屏幕刷新时调用:
    CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的selector就会被调用一次。所以通常情况下,按照iOS设备屏幕的刷新率60次/秒
  • 延迟:
    iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。但如果调用的方法比较耗时,超过了屏幕刷新周期,就会导致跳过若干次回调调用机会。如果CPU过于繁忙,无法保证屏幕60次/秒的刷新率,就会导致跳过若干次调用回调方法的机会,跳过次数取决CPU的忙碌程度。
  • 使用场景:
    从原理上可以看出,CADisplayLink适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。

4.重要属性

  • frameInterval:
    NSInteger类型的值,用来设置间隔多少帧调用一次selector方法,默认值是1,即每帧都调用一次。
  • duration:
    readOnly的CFTimeInterval值,表示两次屏幕刷新之间的时间间隔。需要注意的是,该属性在target的selector被首次调用以后才会被赋值。selector的调用间隔时间计算方式是:调用间隔时间 = duration × frameInterval。

三、GCD方式

执行一次

  1. double delayInSeconds = 2.0;
  2. dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
  3. dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
  4. //执行事件
  5. });

重复执行

  1. NSTimeInterval period = 1.0; // 设置时间间隔
  2. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  3. dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
  4. dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒执行
  5. dispatch_source_set_event_handler(_timer, ^{
  6. //在这里执行事件
  7. });
  8. dispatch_resume(_timer);

 

[iOS] iPad与iPhone上各种标准控件的大小

iPhone和iPad下各种常见控件的宽度和标准是一样的,所以这里就用iPhone说明。

 

Sizes of iPhone UI Elements

%title插图%num

Element Size (in points)
Window (including status bar) 320 x 480 pts
Status Bar
(How to hide the status bar)
20 pts
View inside window
(visible status bar)
320 x 460
Navigation Bar 44 pts
Nav Bar Image /
Toolbar Image
up to 20 x 20 pts (transparent PNG)
Tab Bar 49 pts
Tab Bar Icon up to 30 x 30 pts (transparent PNGs)
Text Field 31 pts
Height of a view inside
a navigation bar
416 pts
Height of a view inside
a tab bar
411 pts
Height of a view inside
a navbar and a tab bar
367 pts
Portrait Keyboard height 216 pts
Landscape Keyboard height 140 pts

Points vs. Pixels
The iPhone 4 introduced a high resolution display with twice the pixels of previous iPhones. However you don’t have to modify your  code to support high-res displays; the coordinate system goes by points rather than pixels, and the dimensi***** in points of the screen and all UI elements remain the same.
iOS 4 supports high resolution displays (like the iPhone 4 display) via the  scale property on UIScreen, UIView, UIImage, and CALayer classes. If the object is displaying high-res content, its scale property is set to 2.0. Otherwise it defaults to 1.0.
All you need to do to support high-res displays is to provide @2x versi***** of the images in your project. See the  checklist for updating to iOS4 or  Apple documentation for  Supporting High Resolution Screens for more info.
Adjusting Sizes
Click here to see how to adjust  View Frames and Bounds.
Additional References
Apple Documentation: Points vs. Pixels

Apple Documentation: UIBarButtonItem Class Reference says “Typically, the size of a toolbar and navigation bar image is 20 x 20 points.”

Apple Documentation: UITabBarItem Class Reference says “The size of an tab bar image is typically 30 x 30 points.”

iOS 11开发教程(十四)iOS11应用代码添加视图

如果开发者想要使用代码为主视图添加视图,该怎么办呢。以下将为开发者解决这一问题。要使用代码为主视图添加视图需要实现3个步骤。

(1)实例化视图对象

每一个视图都是一个特定的类。在Swift中,经常会说,类是一个抽象的概念,而非具体的事物,所以要将类进行实例化。实例化一个视图对象的具体语法如下:

let/var 对象名=视图类()

以我们接触的*个视图View为例,它的实例化对象如下:

let newView=UIView()

其中,UIView是空白视图的类,newView是UIView类实例化出来的一个对象。

(2)设置视图的位置和大小

每一个视图都是一个区域,所以需要为此区域设置位置和大小。设置位置和大小的属性为frame,其语法形式如下:

对象名.frame=CGRect(x ,y ,width,height)

其中,x和y表示视图在主视图中的位置,width和height表示视图的大小。以下为实例化的对象newView设置位置和大小:

newView.frame=CGRect(x: 67, y: 264, width: 240, height: 128)

其中,67和264表示此视图的主视图中的位置,240和128表示此视图的大小。

注意:步骤1和步骤2也可以进行合并。例如,以下的代码是将UIView类的实例化对象和设置位置大小进行了合并:

let newView=UIView(frame: CGRect(x: 67, y: 264, width: 240, height: 128))

(3)将视图添加到当前的视图中

*后,也是*为关键的一步,就是将实例化的对象添加到主视图中。这样才可以进行显示。此时需要使用到AddSubview()方法,其语法形式如下:

this.view.addSubview (视图对象名)

以下将实例化的对象newView添加到当前的主视图中,代码如下:

self.view.addSubview(newView)

【示例1-2】以下将使用代码为主视图添加一个View空白视图。代码如下:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.

        let newView=UIView(frame: CGRect(x: 67, y: 264, width: 240, height: 128))

        self.view.addSubview(newView)

    }

……

}

此时运行程序,会看到如图1.50所示的效果。在此运行效果中也是看不到添加的视图的。这是因为添加的视图默认是白色的背景,如果想要看到视图,需要设置它的背景。例如以下的代码,将背景颜色设置为了灰色:

newView.backgroundColor=UIColor.gray

此时运行程序,会看到如图1.51所示的效果。

%title插图%num%title插图%num

苹果App Store简介

App store即application store,通常理解为应用商店。App store是苹果公司基于iPhone的软件应用商店,向iPhone的用户提供第三方的应用软件服务,这是苹果开创的一个让网络与手机相融合的新型经营模式。
2008年3月6日,苹果对外发布了针对iPhone的应用开发包(SDK),供免费下载,以便第三方应用开发人员开发针对iPhone及Touch的应用软件。不到一周时间,3月12日,苹果宣布已获得超过100,000次的下载,三个月后,这一数字上升至250,000次。苹果公司一直以来推出的产品在技术上都保持一定的封闭性,比如当年的Mac,此次推出SDK可以说是前所未有的开放之举。继 SDK推出之后,同年7月11日,苹果APP Store正式上线。7月14日,APP Store中可供下载的应用已达800个,下载量达到1千万次。2009年1月16日,数字刷新为,逾1.5万个应用,超过5亿次下载。APP Store平台上大部分应用价格低于10美元,并且有约20%的应用是供免费下载的。用户购买应用所支付的费用由苹果与应用开发商3:7分成。

App store模式的意义在于为第三方软件的提供者提供了方便而又高效的一个软件销售平台,使得第三方软件的提供者参与其中的积*性空前高涨,适应了手机用户们对个性化软件的需求,从而使得手机软件业开始进入了一个高速、良性发展的轨道,是苹果公司把App store这样的一个商业行为升华到了一个让人效仿的经营模式,苹果公司的App store开创了手机软件业发展的新篇章,App store无疑将会成为手机软件业发展史上的一个重要的里程碑,其意义已远远超越了“iPhone的软件应用商店”的本身。

Android Handler处理机制 ( 三 )

Android Handler处理机制 ( 三 ) ——Handler,Message,Looper,MessageQueue

  在android中提供了一种异步回调机制Handler,使用它,我们可以在完成一个很长时间的任务后做出相应的通知

handler基本使用:

在主线程中,使用handler很简单,new一个Handler对象实现其handleMessage方法,在handleMessage中
提供收到消息后相应的处理方法即可,这里不对handler使用进行详细说明,在看本博文前,读者应该先掌握handler的基本使用,我这里主要深入描述handler的内部机制

.现在我们首先就有一个问题,我们使用myThreadHandler.sendEmptyMessage(0);发送一个message对象,那么Handler是如何接收该message对象并处理的呢?我先画一个数据结构图:

%title插图%num

从这个图中我们很清楚可以看到调用sendEmptyMessage后,会把 Message对象放入一个MessageQueue队列,该队列属于某个Looper对象,每个Looper对象通过 ThreadLocal.set(new Looper())跟一个Thread绑定了,Looper对象所属的线程在Looper.Loop方法中循环执行从MessageQueue队列读取 Message对象,并把Message对象交由Handler处理,调用Handler的dispatchMessage方法。

现在我们再来看一下使用Handler的基本实现代码:

// 主线程中新建一个handler
normalHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
btnSendMsg2NormalHandler.setText(“normalHandler”);
Log.d(Constant.TAG, MessageFormat.format(“Thread[{0}]–normalHandler handleMessage run…”, Thread.currentThread()
.getName()));
}
};


//发送消息到hanlder
myThreadHandler.sendEmptyMessage(0);

你现在已经很清楚了sendEmptyMessage到handleMessage的 过程,途中经过Looper.MessageQueue队列,转由Looper所在的线程去处理了,这是一个异步的过程,当然Looper所在的线程也可 以是sendEmptyMessage所在的线程。

看了上面你也许还是迷惑不解,那么什么要Looper了,跟我们要用的Handler又有啥鸟关系呢?

我在前面一直强调在主线程中使用handler,为什么要这么说呢,因为你在自己new一个新线程中去像我前面那样简单建立一个Handler,程序执行是会报错的:

java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at com.cao.android.demos.handles.HandleTestActivity$MyThread$1.<init>(HandleTestActivity.java:86)
at com.cao.android.demos.handles.HandleTestActivity$MyThread.run(HandleTestActivity.java:86)

为什么在主线程中不会报错,而在自己新见的线程中就会报这个错误呢?很简单,因为主线程它已经建立了Looper,你可以打开ActivityThread的源码看一下:

public static final void main(String[] args) {
SamplingProfilerIntegration.start();

Process.setArgV0(“<pre-initialized>”);

Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();
thread.attach(false);

Looper.loop();

if (Process.supportsProcesses()) {
throw new RuntimeException(“Main thread loop unexpectedly exited”);
}

thread.detach();
String name = (thread.mInitialApplication != null)
? thread.mInitialApplication.getPackageName()
: “<unknown>”;
Slog.i(TAG, “Main thread of ” + name + ” is now exiting”);
}

在main函数中它已经做了这个事情了,为什么要调用 Looper.prepareMainLooper(); Looper.loop();我们可以进去看一下,在prepareMainLooper方法中新建了一个looper对象,并与当前进程进行了绑定,而 在Looper.loop方法中,线程建立消息循环机制,循环从MessageQueue获取Message对象,调用  msg.target.dispatchMessage(msg);进行处理msg.target在 myThreadHandler.sendEmptyMessage(0)设置进去的,因为一个Thead中可以建立多个Hander,通过 msg.target保证MessageQueue中的每个msg交由发送message的handler进行处理,那么Handler又是怎样与 Looper建立联系的呢,在Handler构造函数中有这样一段代码:

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
“Can’t create handler inside thread that has not called Looper.prepare()”);
}
mQueue = mLooper.mQueue;

在新建Handler时需要设置mLooper成员,Looper.myLooper是从当前线程中获取绑定的Looper对象:

public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}

若Looper对象没有创建,就会抛异常”Can’t create handler inside thread that has not called Looper.prepare()”
这跟我前面讲的是一致的。所以我们在一个新线程中要创建一个Handler就需要这样写:

class MyThread extends Thread {

public void run() {
Log.d(Constant.TAG, MessageFormat.format(“Thread[{0}]– run…”, Thread
.currentThread().getName()));
// 其它线程中新建一个handler
Looper.prepare();// 创建该线程的Looper对象,用于接收消息,在非主线程中是没有looper的所以在创建handler前一定要使用prepare()创建一个Looper
myThreadHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
Log.d(Constant.TAG, MessageFormat.format(“Thread[{0}]–myThreadHandler handleMessage run…”, Thread
.currentThread().getName()));
}
};
Looper.myLooper().loop();//建立一个消息循环,该线程不会退出
}
}

现在,你应该对Handler的机制有所了解了吧,若有什么疑问,欢迎在评论中提出

在其它线程中Handler使用主线程的Looper

前面我说了在新线程中要新建一个Handler需要调用Looper.prepare();也有另一种方法就是使用主线程中的Looper,那就不必新建Looper对象了:

threadMainLoopHandler =new Handler(Looper.getMainLooper()){
public void handleMessage(android.os.Message msg) {
Log.d(Constant.TAG, MessageFormat.format(“Thread[{0}]–threadMainLoopHandler handleMessage run…”, Thread
.currentThread().getName()));
}
//该handleMessage方法将在mainthread中执行
};

这时候注意不要在handleMessage做太多的操作,因为它在主线程中执行,会影响主线程执行ui更新操作。

使用Message.callback回调

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
从dispatchMessage定义可以看出,如果Message对象自带callback对象,handler不会执行handleMessage方 法而是执行message.callback中定义的run方法,当然callback还是在handler关联的looper所绑定的线程中执行的。实 际上Handler.post(Runnable r)方法就是把r添加到一个msg.callback的,也就是说,下面两种写法,没有什么区别:

1.使用Message.callback

 

  1. Message msg = Message.obtain(myThreadHandler,new Runnable() {  
  2.     @Override  
  3.     public void run() {  
  4.         Log.d(Constant.TAG, MessageFormat.format(“Thread[{0}]–myThreadHandler.Message.callback.run”,  
  5.                 Thread.currentThread().getName()));
  6.     }
  7. });
  8. myThreadHandler.sendMessage(msg);

2.使用Handler.post

 

  1. myThreadHandler.post(new Runnable() {  
  2.                     @Override  
  3.                     public void run() {  
  4.                         Log.d(Constant.TAG, MessageFormat.format(“Thread[{0}]–myThreadHandler.Message.callback.run”,  
  5.                                 Thread.currentThread().getName()));
  6.                     }
  7.                 });

对于Handler机制相关测试,我写了一个测试类

3.Handler对Activity finish影响。

在开发的过程中碰到一个棘手的问题,调用Activity.finish函数 Acitivity没有执行生命周期的ondestory函数,后面查找半天是因为有一个handler成员,因为它有一个delay消息没有处理,调用 Activity.finish,Activity不会马上destory,所以记得在Ativity finish前清理一下handle中的未处理的消息,这样Activity才会顺利的destory

Android Handler处理机制 ( 二 )

Android Handler处理机制 ——Handler,Message,Looper,MessageQueue

Android是消息驱动的,实现消息驱动有几个要素:

  1. 消息的表示:Message
  2. 消息队列:MessageQueue
  3. 消息循环,用于循环取出消息进行处理:Looper
  4. 消息处理,消息循环从消息队列中取出消息后要对消息进行处理:Handler

平时我们*常使用的就是Message与Handler了,如果使用过HandlerThread或者自己实现类似HandlerThread的东 西可能还会接触到Looper,而MessageQueue是Looper内部使用的,对于标准的SDK,我们是无法实例化并使用的(构造函数是包可见 性)。

我们平时接触到的Looper、Message、Handler都是用JAVA实现的,Android做为基于Linux的系统,底层用C、C++ 实现的,而且还有NDK的存在,消息驱动的模型怎么可能只存在于JAVA层,实际上,在Native层存在与Java层对应的类如Looper、 MessageQueue等。

 初始化消息队列

首先来看一下如果一个线程想实现消息循环应该怎么做,以HandlerThread为例:

复制代码
复制代码
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}
复制代码
复制代码

主要是红色标明的两句,首先调用prepare初始化MessageQueue与Looper,然后调用loop进入消息循环。先看一下Looper.prepare。

复制代码
复制代码
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
复制代码
复制代码

重载函数,quitAllowed默认为true,从名字可以看出来就是消息循环是否可以退出,默认是可退出的,Main线程(UI线程)初始化消 息循环时会调用prepareMainLooper,传进去的是false。使用了ThreadLocal,每个线程可以初始化一个Looper。

再来看一下Looper在初始化时都做了什么:

复制代码
复制代码
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mRun = true;
    mThread = Thread.currentThread();
}

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    nativeInit();
}
复制代码
复制代码

在Looper初始化时,新建了一个MessageQueue的对象保存了在成员mQueue中。MessageQueue的构造函数是包可见性,所以我们是无法直接使用的,在MessageQueue初始化的时候调用了nativeInit,这是一个Native方法:

复制代码
复制代码
static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return;
    }

    nativeMessageQueue->incStrong(env);
    android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
}

static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,
        NativeMessageQueue* nativeMessageQueue) {
    env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr,
             reinterpret_cast<jint>(nativeMessageQueue));
}
复制代码
复制代码

在nativeInit中,new了一个Native层的MessageQueue的对象,并将其地址保存在了Java层MessageQueue 的成员mPtr中,Android中有好多这样的实现,一个类在Java层与Native层都有实现,通过JNI的GetFieldID与 SetIntField把Native层的类的实例地址保存到Java层类的实例的mPtr成员中,比如Parcel。

再看NativeMessageQueue的实现:

复制代码
复制代码
NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}
复制代码
复制代码

在NativeMessageQueue的构造函数中获得了一个Native层的Looper对象,Native层的Looper也使用了线程本地存储,注意new Looper时传入了参数false。

复制代码
复制代码
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    int wakeFds[2];
    int result = pipe(wakeFds);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);

    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];

    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
            errno);

    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
            errno);

    // Allocate the epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);

    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
            errno);
}
复制代码
复制代码

Native层的Looper使用了epoll。初始化了一个管道,用mWakeWritePipeFd与mWakeReadPipeFd分别保存 了管道的写端与读端,并监听了读端的EPOLLIN事件。注意下初始化列表的值,mAllowNonCallbacks的值为false。

mAllowNonCallback是做什么的?使用epoll仅为了监听mWakeReadPipeFd的事件?其实Native Looper不仅可以监听这一个描述符,Looper还提供了addFd方法:

int addFd(int fd, int ident, int events, ALooper_callbackFunc callback, void* data);
int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data);

fd表示要监听的描述符。ident表示要监听的事件的标识,值必须>=0或者为 ALOOPER_POLL_CALLBACK(-2),event表示要监听的事件,callback是事件发生时的回调函 数,mAllowNonCallbacks的作用就在于此,当mAllowNonCallbacks为true时允许callback为NULL,在 pollOnce中ident作为结果返回,否则不允许callback为空,当callback不为NULL时,ident的值会被忽略。还是直接看代 码方便理解:

复制代码
复制代码
int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
#if DEBUG_CALLBACKS
    ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident,
            events, callback.get(), data);
#endif
    if (!callback.get()) {
        if (! mAllowNonCallbacks) {
            ALOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
            return -1;
        }
        if (ident < 0) {
            ALOGE("Invalid attempt to set NULL callback with ident < 0.");
            return -1;
        }
    } else {
        ident = ALOOPER_POLL_CALLBACK;
    }

    int epollEvents = 0;
    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
    if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;

    { // acquire lock
        AutoMutex _l(mLock);

        Request request;
        request.fd = fd;
        request.ident = ident;
        request.callback = callback;
        request.data = data;

        struct epoll_event eventItem;
        memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
        eventItem.events = epollEvents;
        eventItem.data.fd = fd;

        ssize_t requestIndex = mRequests.indexOfKey(fd);
        if (requestIndex < 0) {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error adding epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.add(fd, request);
        } else {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.replaceValueAt(requestIndex, request);
        }
    } // release lock
    return 1;
}
复制代码
复制代码

如果callback为空会检查mAllowNonCallbacks看是否允许callback为空,如果允许callback为空还会检测 ident是否>=0。如果callback不为空会把ident的值赋值为ALOOPER_POLL_CALLBACK,不管传进来的是什么值。

接下来把传进来的参数值封装到一个Request结构体中,并以描述符为键保存到一个KeyedVector mRequests中,然后通过epoll_ctl添加或替换(如果这个描述符之前有调用addFD添加监听)对这个描述符事件的监听。

类图:

%title插图%num

发送消息

通过Looper.prepare初始化好消息队列后就可以调用Looper.loop进入消息循环了,然后我们就可以向消息队列发送消息,消息循环就会取出消息进行处理,在看消息处理之前,先看一下消息是怎么被添加到消息队列的。

在Java层,Message类表示一个消息对象,要发送消息首先就要先获得一个消息对象,Message类的构造函数是public的,但是不建 议直接new Message,Message内部保存了一个缓存的消息池,我们可以用obtain从缓存池获得一个消息,Message使用完后系统会调用 recycle回收,如果自己new很多Message,每次使用完后系统放入缓存池,会占用很多内存的,如下所示:

复制代码
复制代码
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

    public void recycle() {
        clearForRecycle();

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
复制代码
复制代码

Message内部通过next成员实现了一个链表,这样sPool就了为了一个Messages的缓存链表。

消息对象获取到了怎么发送呢,大家都知道是通过Handler的post、sendMessage等方法,其实这些方法*终都是调用的同一个方法sendMessageAtTime:

复制代码
复制代码
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
复制代码
复制代码

sendMessageAtTime获取到消息队列然后调用enqueueMessage方法,消息队列mQueue是从与Handler关联的Looper获得的。

复制代码
复制代码
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
复制代码
复制代码

enqueueMessage将message的target设置为当前的handler,然后调用MessageQueue的 enqueueMessage,在调用queue.enqueueMessage之前判断了mAsynchronous,从名字看是异步消息的意思,要明 白Asynchronous的作用,需要先了解一个概念Barrier。

Barrier与Asynchronous Message

Barrier是什么意思呢,从名字看是一个拦截器,在这个拦截器后面的消息都暂时无法执行,直到这个拦截器被移除了,MessageQueue有一个函数叫enqueueSyncBarier可以添加一个Barrier。

复制代码
复制代码
    int enqueueSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }
复制代码
复制代码

在enqueueSyncBarrier中,obtain了一个Message,并设置msg.arg1=token,token仅是一个每次调用 enqueueSyncBarrier时自增的int值,目的是每次调用enqueueSyncBarrier时返回唯一的一个token,这个 Message同样需要设置执行时间,然后插入到消息队列,特殊的是这个Message没有设置target,即msg.target为null。

进入消息循环后会不停地从MessageQueue中取消息执行,调用的是MessageQueue的next函数,其中有这么一段:

复制代码
复制代码
Message msg = mMessages;
if (msg != null && msg.target == null) {
    // Stalled by a barrier.  Find the next asynchronous message in the queue.
    do {
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
}
复制代码
复制代码

如果队列头部的消息的target为null就表示它是个Barrier,因为只有两种方法往mMessages中添加消息,一种是 enqueueMessage,另一种是enqueueBarrier,而enqueueMessage中如果mst.target为null是直接抛异 常的,后面会看到。

所谓的异步消息其实就是这样的,我们可以通过enqueueBarrier往消息队列中插入一个Barrier,那么队列中执行时间在这个 Barrier以后的同步消息都会被这个Barrier拦截住无法执行,直到我们调用removeBarrier移除了这个Barrier,而异步消息则 没有影响,消息默认就是同步消息,除非我们调用了Message的setAsynchronous,这个方法是隐藏的。只有在初始化Handler时通过 参数指定往这个Handler发送的消息都是异步的,这样在Handler的enqueueMessage中就会调用Message的 setAsynchronous设置消息是异步的,从上面Handler.enqueueMessage的代码中可以看到。

所谓异步消息,其实只有一个作用,就是在设置Barrier时仍可以不受Barrier的影响被正常处理,如果没有设置Barrier,异步消息就与同步消息没有区别,可以通过removeSyncBarrier移除Barrier:

复制代码
复制代码
void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    final boolean needWake;
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycle();
    }
    if (needWake) {
        nativeWake(mPtr);
    }
}
复制代码
复制代码

参数token就是enqueueSyncBarrier的返回值,如果没有调用指定的token不存在是会抛异常的。

enqueueMessage

接下来看一下是怎么MessageQueue的enqueueMessage。

复制代码
复制代码
    final boolean enqueueMessage(Message msg, long when) {
        if (msg.isInUse()) {
            throw new AndroidRuntimeException(msg + " This message is already in use.");
        }
        if (msg.target == null) {
            throw new AndroidRuntimeException("Message must have a target.");
        }

        boolean needWake;
        synchronized (this) {
            if (mQuiting) {
                RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            }

            msg.when = when;
            Message p = mMessages;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
        }
        if (needWake) {
            nativeWake(mPtr);
        }
        return true;
    }
复制代码
复制代码

注意上面代码红色的部分,当msg.target为null时是直接抛异常的。

在enqueueMessage中首先判断,如果当前的消息队列为空,或者新添加的消息的执行时间when是0,或者新添加的消息的执行时间比消息 队列头的消息的执行时间还早,就把消息添加到消息队列头(消息队列按时间排序),否则就要找到合适的位置将当前消息添加到消息队列。

Native发送消息

消息模型不只是Java层用的,Native层也可以用,前面也看到了消息队列初始化时也同时初始化了Native层的Looper与 NativeMessageQueue,所以Native层应该也是可以发送消息的。与Java层不同的是,Native层是通过Looper发消息的, 同样所有的发送方法*终是调用sendMessageAtTime:

复制代码
复制代码
void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,
        const Message& message) {
#if DEBUG_CALLBACKS
    ALOGD("%p ~ sendMessageAtTime - uptime=%lld, handler=%p, what=%d",
            this, uptime, handler.get(), message.what);
#endif

    size_t i = 0;
    { // acquire lock
        AutoMutex _l(mLock);

        size_t messageCount = mMessageEnvelopes.size();
        while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {
            i += 1;
        }

        MessageEnvelope messageEnvelope(uptime, handler, message);
        mMessageEnvelopes.insertAt(messageEnvelope, i, 1);

        // Optimization: If the Looper is currently sending a message, then we can skip
        // the call to wake() because the next thing the Looper will do after processing
        // messages is to decide when the next wakeup time should be.  In fact, it does
        // not even matter whether this code is running on the Looper thread.
        if (mSendingMessage) {
            return;
        }
    } // release lock

    // Wake the poll loop only when we enqueue a new message at the head.
    if (i == 0) {
        wake();
    }
}
复制代码
复制代码

Native Message只有一个int型的what字段用来区分不同的消息,sendMessageAtTime指定了Message,Message要执行的时 间when,与处理这个消息的Handler:MessageHandler,然后用MessageEnvelope封装了time, MessageHandler与Message,Native层发的消息都保存到了mMessageEnvelopes 中,mMessageEnvelopes是一个Vector<MessageEnvelope>。Native层消息同样是按时间排序,与 Java层的消息分别保存在两个队列里。

消息循环

消息队列初始化好了,也知道怎么发消息了,下面就是怎么处理消息了,看Handler.loop函数:

复制代码
复制代码
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycle();
        }
    }
复制代码
复制代码

loop每次从MessageQueue取出一个Message,调用 msg.target.dispatchMessage(msg),target就是发送message时跟message关联的handler,这样就 调用到了熟悉的dispatchMessage,Message被处理后会被recycle。当queue.next返回null时会退出消息循环,接下 来就看一下MessageQueue.next是怎么取出消息的,又会在什么时候返回null。

复制代码
复制代码
final Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;

        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(mPtr, nextPollTimeoutMillis);

            synchronized (this) {
                if (mQuiting) {
                    return null;
                }

                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (false) Log.v("MessageQueue", "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf("MessageQueue", "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
复制代码
复制代码

MessageQueue.next首先会调用nativePollOnce,然后如果mQuiting为true就返回null,Looper就会退出消息循环。

接下来取消息队列头部的消息,如果头部消息是Barrier(target==null)就往后遍历找到*个异步消息,接下来检测获取到的消息 (消息队列头部的消息或者*个异步消息),如果为null表示没有消息要执行,设置nextPollTimeoutMillis = -1;否则检测这个消息要执行的时间,如果到执行时间了就将这个消息markInUse并从消息队列移除,然后从next返回到loop;否则设置 nextPollTimeoutMillis = (int) Math.min(msg.when – now, Integer.MAX_VALUE),即距离*近要执行的消息还需要多久,无论是当前消息队列没有消息可以执行(设置了Barrier并且没有异步消息 或消息队列为空)还是队列头部的消息未到执行时间,都会执行后面的代码,看有没有设置IdleHandler,如果有就运行IdleHandler,当 IdleHandler被执行之后会设置nextPollTimeoutMillis = 0。

首先看一下nativePollOnce,native方法,调用JNI,*后调到了Native Looper::pollOnce,并从Java层传进去了nextPollTimeMillis,即Java层的消息队列中执行时间*近的消息还要多久到执行时间。

复制代码
复制代码
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
                ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
                        "fd=%d, events=0x%x, data=%p",
                        this, ident, fd, events, data);
#endif
                if (outFd != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
            }
        }

        if (result != 0) {
#if DEBUG_POLL_AND_WAKE
            ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }

        result = pollInner(timeoutMillis);
    }
}
复制代码
复制代码

先不看开始的一大串代码,先看一下pollInner:

复制代码
复制代码
int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif

    // Adjust the timeout based on when the next message is due.
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
        if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            timeoutMillis = messageTimeoutMillis;
        }
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d",
                this, mNextMessageUptime - now, timeoutMillis);
#endif
    }

    // Poll.
    int result = ALOOPER_POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // Acquire lock.
    mLock.lock();

    // Check for poll error.
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error, errno=%d", errno);
        result = ALOOPER_POLL_ERROR;
        goto Done;
    }

    // Check for poll timeout.
    if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - timeout", this);
#endif
        result = ALOOPER_POLL_TIMEOUT;
        goto Done;
    }

    // Handle all events.
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
#endif

    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeReadPipeFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                        "no longer registered.", epollEvents, fd);
            }
        }
    }
Done: ;

    // Invoke pending message callbacks.
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            // Remove the envelope from the list.
            // We keep a strong reference to the handler until the call to handleMessage
            // finishes.  Then we drop it so that the handler can be deleted *before*
            // we reacquire our lock.
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();

#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
                ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",
                        this, handler.get(), message.what);
#endif
                handler->handleMessage(message);
            } // release handler

            mLock.lock();
            mSendingMessage = false;
            result = ALOOPER_POLL_CALLBACK;
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

    // Release lock.
    mLock.unlock();

    // Invoke all response callbacks.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == ALOOPER_POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd);
            }
            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();
            result = ALOOPER_POLL_CALLBACK;
        }
    }
    return result;
}
复制代码
复制代码

Java层的消息都保存在了Java层MessageQueue的成员mMessages中,Native层的消息都保存在了Native Looper的mMessageEnvelopes中,这就可以说有两个消息队列,而且都是按时间排列的。timeOutMillis表示Java层下个 要执行的消息还要多久执行,mNextMessageUpdate表示Native层下个要执行的消息还要多久执行,如果timeOutMillis为 0,epoll_wait不设置TimeOut直接返回;如果为-1说明Java层无消息直接用Native的time out;否则pollInner取这两个中的*小值作为timeOut调用epoll_wait。当epoll_wait返回时就可能有以下几种情况:

  1. 出错返回。
  2. Time Out
  3. 正常返回,描述符上有事件产生。

如果是前两种情况直接goto DONE。

否则就说明FD上有事件发生了,如果是mWakeReadPipeFd的EPOLLIN事件就调用awoken,如果不是 mWakeReadPipeFd,那就是通过addFD添加的fd,在addFD中将要监听的fd及其events,callback,data封装成了 Request对象,并以fd为键保存到了KeyedVector mRequests中,所以在这里就以fd为键获得在addFD时关联的Request,并连同events通过pushResonse加入 mResonse队列(Vector),Resonse仅是对events与Request的封装。如果是epoll_wait出错或timeout,就 没有描述符上有事件,就不用执行这一段代码,所以直接goto DONE了。

复制代码
复制代码
void Looper::pushResponse(int events, const Request& request) {
    Response response;
    response.events = events;
    response.request = request;
    mResponses.push(response);
}
复制代码
复制代码

接下来进入DONE部分,从mMessageEnvelopes取出头部的Native消息,如果到达了执行时间就调用它内部保存的 MessageeHandler的handleMessage处理并从Native 消息队列移除,设置result为ALOOPER_POLL_CALLBACK,否则计算mNextMessageUptime表示Native消息队列 下一次消息要执行的时间。如果未到头部消息的执行时间有可能是Java层消息队列消息的执行时间小于Native层消息队列头部消息的执行时间,到达了 Java层消息的执行时间epoll_wait TimeOut返回了,或都通过addFd添加的描述符上有事件发生导致epoll_wait返回,或者epoll_wait是出错返回。Native消 息是没有Barrier与Asynchronous的。

*后,遍历mResponses(前面刚通过pushResponse存进去的),如果response.request.ident ==ALOOPER_POLL_CALLBACK,就调用注册的callback的handleEvent(fd, events, data) 进行处理,然后从mResonses队列中移除,这次遍历完之后,mResponses中保留来来的就都是ident>=0并且callback为 NULL的了。在NativeMessageQueue初始化Looper时传入了mAllowNonCallbacks为false,所以这次处理完后 mResponses一定为空。

接下来返回到pollOnce。pollOnce是一个for循环,pollInner中处理了所有 response.request.ident==ALOOPER_POLL_CALLBACK的Response,在第二次进入for循环后如果 mResponses不为空就可以找到ident>0的Response,将其ident作为返回值返回由调用pollOnce的函数自己处理,在 这里我们是在NativeMessageQueue中调用的Loope的pollOnce,没对返回值进行处理,而且 mAllowNonCallbacks为false也就不可能进入这个循环。pollInner返回值不可能是0,或者说只可能是负数,所以 pollOnce中的for循环只会执行两次,在第二次就返回了。

Native Looper可以单独使用,也有一个prepare函数,这时mAllowNonCallbakcs值可能为true,pollOnce中对mResponses的处理就有意义了。

 wake与awoken

在Native Looper的构造函数中,通过pipe打开了一个管道,并用mWakeReadPipeFd与mWakeWritePipeFd分别保存了管道的读端与写端,然后用epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd,&eventItem)监听了读端的EPOLLIN事件,在pollInner中通过epoll_wait(mEpollFd, eventItems,EPOLL_MAX_EVENTS, timeoutMillis)读取事件,那是在什么时候往mWakeWritePipeFd写,又是在什么时候读的mWakeReadPipeFd呢?

在Looper.cpp中我们可以发现如下两个函数:

复制代码
复制代码
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    ssize_t nWrite;
    do {
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);

    if (nWrite != 1) {
        if (errno != EAGAIN) {
            ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}

void Looper::awoken() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ awoken", this);
#endif

    char buffer[16];
    ssize_t nRead;
    do {
        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
}
复制代码
复制代码

wake函数向mWakeWritePipeFd写入了一个“W”字符,awoken从mWakeReadPipeFd读,往 mWakeWritePipeFd写数据只是为了在pollInner中的epoll_wait可以监听到事件返回。在pollInner也可以看到如果 是mWakeReadPipeFd的EPOLLIN事件只是调用了awoken消耗掉了写入的字符就往后处理了。

那什么时候调用wake呢?这个只要找到调用的地方分析一下就行了,先看Looper.cpp,在sendMessageAtTime即发送Native Message的时候,根据发送的Message的执行时间查找mMessageEnvelopes 计算应该插入的位置,如果是在头部插入,就调用wake唤醒epoll_wait,因为在进入pollInner时根据Java层消息队列头部消息的执行 时间与Native层消息队列头部消息的执行时间计算出了一个timeout,如果这个新消息是在头部插入,说明执行时间至少在上述两个消息中的一个之 前,所以应该唤醒epoll_wait,epoll_wait返回后,检查Native消息队列,看头部消息即刚插入的消息是否到执行时间了,到了就执 行,否则就可能需要设置新的timeout。同样在Java层的MessageQueue中,有一个函数nativeWake也同样可以通过JNI调用 wake,调用nativeWake的时机与在Native调用wake的时机类似,在消息队列头部插入消息,还有一种情况就是,消息队列头部是一个 Barrier,而且插入的消息是*个异步消息。

复制代码
复制代码
if (p == null || when == 0 || when < p.when) {
    // New head, wake up the event queue if blocked.
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
} else {
    // Inserted within the middle of the queue.  Usually we don't have to wake
    // up the event queue unless there is a barrier at the head of the queue
    // and the message is the earliest asynchronous message in the queue.
    needWake = mBlocked && p.target == null && msg.isAsynchronous();//如果头部是Barrier并且新消息是异步消息则“有可能”需要唤醒
    Message prev;
    for (;;) {
        prev = p;
        p = p.next;
        if (p == null || when < p.when) {
            break;
        }
        if (needWake && p.isAsynchronous()) { // 消息队列中有异步消息并且执行时间在新消息之前,所以不需要唤醒。
            needWake = false;
        }
    }
    msg.next = p; // invariant: p == prev.next
    prev.next = msg;
}
复制代码
复制代码

在头部插入消息不一定调用nativeWake,因为之前可能正在执行 IdleHandler,如果执行了IdleHandler,就在IdleHandler执行后把nextPollTimeoutMillis设置为0, 下次进入for循环就用0调用nativePollOnce,不需要wake,只有在没有消息可以执行(消息队列为空或没到执行时间)并且没有设置 IdleHandler时mBlocked才会为true。

如果Java层的消息队列被Barrier Block住了并且当前插入的是一个异步消息有可能需要唤醒Looper,因为异步消息可以在Barrier下执行,但是这个异步消息一定要是执行时间*早的异步消息。

退出Looper也需要wake,removeSyncBarrier时也可能需要。

Android Handler处理机制 ( 一 )(图+源码分析)

android的消息处理机制(图+源码分析)——Looper,Handler,Message

作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习 google大牛们的设计思想。android源码中包含了大量的设计模式,除此以外,android sdk还精心为我们设计了各种helper类,对于和我一样渴望水平得到进阶的人来说,都太值得一读了。这不,前几天为了了解android的消息处理机 制,我看了Looper,Handler,Message这几个类的源码,结果又一次被googler的设计震撼了,特与大家分享。

android的消息处理有三个核心类:Looper,Handler和Message。其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因此我没将其作为核心类。下面一一介绍:

线程的魔法师 Looper

Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:

复制代码

%title插图%num

复制代码
public class LooperThread extends Thread {
    @Override
    public void run() {
        // 将当前线程初始化为Looper线程
        Looper.prepare();
        
        // ...其他处理,如实例化handler
        
        // 开始循环处理消息队列
        Looper.loop();
    }
}
复制代码
复制代码

通过上面两行核心代码,你的线程就升级为Looper线程了!!!是不是很神奇?让我们放慢镜头,看看这两行代码各自做了什么。

1)Looper.prepare()

%title插图%num

通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MQ。注意,一个Thread只能有一个Looper对象,为什么呢?咱们来看源码。

复制代码

%title插图%num

复制代码
public class Looper {
    // 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象
    private static final ThreadLocal sThreadLocal = new ThreadLocal();
    // Looper内的消息队列
    final MessageQueue mQueue;
    // 当前线程
    Thread mThread;
    // 。。。其他属性

    // 每个Looper对象中有它的消息队列,和它所属的线程
    private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }

    // 我们调用该方法会在调用线程的TLS中创建Looper对象
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            // 试图在有Looper的线程中再次创建Looper将抛出异常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    // 其他方法
}
复制代码
复制代码

通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象定义为ThreadLocal。如果你还不清楚什么是ThreadLocal,请参考《理解ThreadLocal》。

2)Looper.loop()

%title插图%num

调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。其源码分析如下:

复制代码

%title插图%num

复制代码
  public static final void loop() {
        Looper me = myLooper();  //得到当前线程Looper
        MessageQueue queue = me.mQueue;  //得到当前looper的MQ
        
        // 这两行没看懂= = 不过不影响理解
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        // 开始循环
        while (true) {
            Message msg = queue.next(); // 取出message
            if (msg != null) {
                if (msg.target == null) {
                    // message没有target为结束信号,退出循环
                    return;
                }
                // 日志。。。
                if (me.mLogging!= null) me.mLogging.println(
                        ">>>>> Dispatching to " + msg.target + " "
                        + msg.callback + ": " + msg.what
                        );
                // 非常重要!将真正的处理工作交给message的target,即后面要讲的handler
                msg.target.dispatchMessage(msg);
                // 还是日志。。。
                if (me.mLogging!= null) me.mLogging.println(
                        "<<<<< Finished to    " + msg.target + " "
                        + msg.callback);
                
                // 下面没看懂,同样不影响理解
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf("Looper", "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
                // 回收message资源
                msg.recycle();
            }
        }
    }
复制代码
复制代码

除了prepare()和loop()方法,Looper类还提供了一些有用的方法,比如

Looper.myLooper()得到当前线程looper对象:

%title插图%num

    public static final Looper myLooper() {
        // 在任意线程调用Looper.myLooper()返回的都是那个线程的looper
        return (Looper)sThreadLocal.get();
    }

getThread()得到looper对象所属线程:

%title插图%num

    public Thread getThread() {
        return mThread;
    }

quit()方法结束looper循环:

复制代码

%title插图%num

    public void quit() {
        // 创建一个空的message,它的target为NULL,表示结束循环消息
        Message msg = Message.obtain();
        // 发出消息
        mQueue.enqueueMessage(msg, 0);
    }
复制代码

到此为止,你应该对Looper有了基本的了解,总结几点:

1.每个线程有且*多只能有一个Looper对象,它是一个ThreadLocal

2.Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行

3.Looper使一个线程变成Looper线程。

那么,我们如何往MQ上添加消息呢?下面有请Handler!(掌声~~~)

异步处理大师 Handler

什么是handler?handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。默认的构造方法:

复制代码

%title插图%num

复制代码
public class handler {

    final MessageQueue mQueue;  // 关联的MQ
    final Looper mLooper;  // 关联的looper
    final Callback mCallback; 
    // 其他属性

    public Handler() {
        // 没看懂,直接略过,,,
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
        // 默认将关联当前线程的looper
        mLooper = Looper.myLooper();
        // looper不能为空,即该默认的构造方法只能在looper线程中使用
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
        mQueue = mLooper.mQueue;
        mCallback = null;
    }
    
    // 其他方法
}
复制代码
复制代码

下面我们就可以为之前的LooperThread类加入Handler:

复制代码

%title插图%num

复制代码
public class LooperThread extends Thread {
    private Handler handler1;
    private Handler handler2;

    @Override
    public void run() {
        // 将当前线程初始化为Looper线程
        Looper.prepare();
        
        // 实例化两个handler
        handler1 = new Handler();
        handler2 = new Handler();
        
        // 开始循环处理消息队列
        Looper.loop();
    }
}
复制代码
复制代码

加入handler后的效果如下图:

%title插图%num

可以看到,一个线程可以有多个Handler,但是只能有一个Looper!

Handler发送消息

有了handler之后,我们就可以使用 post(Runnable)postAtTime(Runnable, long)postDelayed(Runnable, long)sendEmptyMessage(int)sendMessage(Message)sendMessageAtTime(Message, long)sendMessageDelayed(Message, long)这些方法向MQ上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象*后都被封装成message对象了,见源码:

复制代码

%title插图%num

复制代码
    // 此方法用于向关联的MQ上发送Runnable对象,它的run方法将在handler关联的looper线程中执行
    public final boolean post(Runnable r)
    {
       // 注意getPostMessage(r)将runnable封装成message
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private final Message getPostMessage(Runnable r) {
        Message m = Message.obtain();  //得到空的message
        m.callback = r;  //将runnable设为message的callback,
        return m;
    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
            msg.target = this;  // message的target必须设为该handler!
            sent = queue.enqueueMessage(msg, uptimeMillis);
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        }
        return sent;
    }
复制代码
复制代码

其他方法就不罗列了,总之通过handler发出的message有如下特点:

1.message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码

msg.target.dispatchMessage(msg);

2.post发出的message,其callback为Runnable对象

Handler处理消息

说完了消息的发送,再来看下handler如何处理消息。消息的处理是通过核心方法dispatchMessage(Messagemsg)与钩子方法handleMessage(Message msg)完成的,见源码

复制代码

%title插图%num

复制代码
    // 处理消息,该方法由looper调用
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 如果message设置了callback,即runnable消息,处理callback!
            handleCallback(msg);
        } else {
            // 如果handler本身设置了callback,则执行callback
            if (mCallback != null) {
                 /* 这种方法允许让activity等来实现Handler.Callback接口,避免了自己编写handler重写handleMessage方法。见http://alex-yang-xiansoftware-com.iteye.com/blog/850865 */
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 如果message没有callback,则调用handler的钩子方法handleMessage
            handleMessage(msg);
        }
    }
    
    // 处理runnable消息
    private final void handleCallback(Message message) {
        message.callback.run();  //直接调用run方法!
    }
    // 由子类实现的钩子方法
    public void handleMessage(Message msg) {
    }
复制代码
复制代码

可以看到,除了handleMessage(Message msg)和Runnable对象的run方法由开发者实现外(实现具体逻辑),handler的内部工作机制对开发者是透明的。这正是handler API设计的精妙之处!

Handler的用处

我在小标题中将handler描述为“异步处理大师”,这归功于Handler拥有下面两个重要的特点:

1.handler可以在任意线程发送消息,这些消息会被添加到关联的MQ上。

%title插图%num

2.handler是在它关联的looper线程中处理消息的。

%title插图%num

这就解决了android*经典的不能在其他非主线程中更新UI的问题。android的主线程也是一个looper线程(looper在android中运用很广),我们在其中创建的handler默认将关联主线程MQ。因此,利用handler的一个solution就是在activity中创建handler并将其引用传递给worker thread,worker thread执行完任务后使用handler发送消息通知activity更新UI。(过程如图)

%title插图%num

下面给出sample代码,仅供参考:

复制代码

%title插图%num

复制代码
public class TestDriverActivity extends Activity {
    private TextView textview;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        textview = (TextView) findViewById(R.id.textview);
        // 创建并启动工作线程
        Thread workerThread = new Thread(new SampleTask(new MyHandler()));
        workerThread.start();
    }
    
    public void appendText(String msg) {
        textview.setText(textview.getText() + "\n" + msg);
    }
    
    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            String result = msg.getData().getString("message");
            // 更新UI
            appendText(result);
        }
    }
}
复制代码
复制代码
复制代码

%title插图%num

复制代码
public class SampleTask implements Runnable {
    private static final String TAG = SampleTask.class.getSimpleName();
    Handler handler;
    
    public SampleTask(Handler handler) {
        super();
        this.handler = handler;
    }

    @Override
    public void run() {
        try {  // 模拟执行某项任务,下载等
            Thread.sleep(5000);
            // 任务完成后通知activity更新UI
            Message msg = prepareMessage("task completed!");
            // message将被添加到主线程的MQ中
            handler.sendMessage(msg);
        } catch (InterruptedException e) {
            Log.d(TAG, "interrupted!");
        }

    }

    private Message prepareMessage(String str) {
        Message result = handler.obtainMessage();
        Bundle data = new Bundle();
        data.putString("message", str);
        result.setData(data);
        return result;
    }

}
复制代码
复制代码

当然,handler能做的远远不仅如此,由于它能post Runnable对象,它还能与Looper配合实现经典的Pipeline Thread(流水线线程)模式。

封装任务 Message

在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。message的用法比较简单,这里不做总结了。但是有这么几点需要注意(待补充):

1.尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。

2.如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存

3.擅用message.what来标识信息,以便用不同方式处理message。