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

iOS通俗易懂的微信支付接入和爬坑指南,十分钟轻松搞完

$
0
0

        现在基本所有的App都会接入支付宝支付以及微信支付,也有很多第三方提供给你

SDK帮你接入,但是这种涉及到支付的东西还是自己服务器搞来的好一些,其实搞懂了

辑非常的简单,下面直接给大家说说下基本流程和接入需要注意的东西。


前期准备(这个东西一般来讲我们不需要来操心,但是还是稍微介绍下)

1.到微信开放平台注册账号点击打开链接

2.进入管理中心------移动应用------创建移动应用----根据页面完善应用资料

3.审核过后,通过应用详情页面,查看应用详情,查看AppID和AppSecret相关信息

4.创建这些是没有支付能力的,需要额外申请,还是根据提示一步步填写,填写完之后会发一封邮件到您的预留的邮箱,然后到商户平台点击打开链接填写资料,最主要的是验证下开户收款账号,会收到一波几分钱的巨额财产,那么这个时候如果你填写的是你的开户账号,直接跑路吧,这些钱够你在深圳买房了。。。。。。如果你是个好人,那么找你们财务验证下是否有收到,就代表通过了,愉快的代码时间来了

   


开撸代码之前先看下基本流程

商户系统和微信支付系统主要交互说明:

步骤1:用户在商户APP中选择商品,提交订单,选择微信支付。

步骤2:商户后台收到用户支付单,调用微信支付统一下单接口。点击打开链接

步骤3:统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式为Sign=WXPay

步骤4:商户APP调起微信支付。点击打开链接

步骤5:商户后台接收支付通知。点击打开链接

步骤6:商户后台查询支付结果。点击打开链接



看完流程,来看看咱们客户端要做什么准备

1.SDK接入


2.依赖库导入(貌似还差个libc++.dylib,也一并加入



3.iOS 9 配置白名单



4.配置下Scheme(这填写的是申请回来的ID)




终于可以愉快的写代码了


1.向微信注册你的AppID

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    //注册APP,这里的字符串就是Wechat URL Scheme里面对应的ID 也是申请回来的ID,必须一致
    [WXApi registerApp:@"这里填写申请回来的ID"];
    return YES;
}

2.请求服务器的参数,拉起微信支付App(超级关键,注意听)

#pragma mark - 微信支付
- (void)wechatPay
{
    // 把生成的订单信息组装起来传给服务器,如何组装就和服务器约定好
    [[TWTShoppingCartLogic sharedData] goToWechatEasyPay:self.orderStr way:@"2" complete:^(NSError *error, id data) {
       
        NSMutableString *stamp  = [data objectForKey:@"timestamp"];
        
        //调起微信支付
        PayReq* req             = [[PayReq alloc] init];
        req.partnerId           = [data objectForKey:@"partnerid"];
        req.prepayId            = [data objectForKey:@"prepayid"];
        req.nonceStr            = [data objectForKey:@"noncestr"];
        req.timeStamp           = stamp.intValue;
        req.package             = [data objectForKey:@"package"];
        req.sign                = [data objectForKey:@"sign"];
        [WXApi sendReq:req];
        
    }];
}

这里请求的方法和步骤就不写了,无非就是post信息给服务器,咱们看看需要的数据格式(假数据)

{
  "appid" : "wxb4b",
微信开放平台审核通过的AppID
  "noncestr" : "171127dd056d05e423c8b9e",随机字符串
  "package" : "Sign=WXPay", 固定值
  "partnerid" : "130", 微信支付分配的商户ID
  "prepayid" : "wx201609291601", 预支付交易会话ID
  "sign" : "684371081C049B6017641", 签名,除了sign,剩下6个组合的再次签名字符串
  "timestamp" : 147513 当前时间
}


注意啦!!!!!!

第一种:老司机后台类型


其实当你把订单传给后台的时候,后台事先会把订单通过微信的生成预支付订单生成


prepayID点击打开链接,那么对于老司机来说,怎么可能把这种返回的数据返回给你?


他们会把接受的prepayID根据上面的结构组装起来,那么预支付订单生成的时候也会返

回sign字段,老司机不会直接用,后台会把这个字段,也就是剩下6个字段再次md5签

名生成签名算法新的sign字段组装完毕返回给你,这种情况下直接在App上配置模型,

拉起微信支付,非常舒畅,一气呵成!!!


第二种:无法理解类型后台(让你自己签名)


当你把订单传给他的时候,同样他会生成个预订单prepayID,那么这种司机开车特别

猛,直接把返回的参数根据格式组装后弹回给你,sign字段也是预订单生成后的,没有

经过二次md5签名,他也没有告诉你,那么你也特别猛,没问他,直接用他的字段,组

装完毕,拉起微信,我擦,你会直接懵逼了,那么你将会只会看到这个。

问题不大,就是自己签名了,自己写个本地的md5玩玩(假的千万别用,网上

找来的分享下)

//创建package签名
-(NSString*) createMd5Sign:(NSMutableDictionary*)dict
{
    NSMutableString *contentString  =[NSMutableString string];
    NSArray *keys = [dict allKeys];
    //按字母顺序排序
    NSArray *sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        return [obj1 compare:obj2 options:NSNumericSearch];
    }];
    //拼接字符串
    for (NSString *categoryId in sortedArray) {
        if (   ![[dict objectForKey:categoryId] isEqualToString:@""]
            && ![categoryId isEqualToString:@"sign"]
            && ![categoryId isEqualToString:@"key"]
            )
        {
            [contentString appendFormat:@"%@=%@&", categoryId, [dict objectForKey:categoryId]];
        }
        
    }
    //添加key字段
    [contentString appendFormat:@"key=%@", self.spKey];
    //得到MD5 sign签名
    NSString *md5Sign =[contentString MD5];
    
    return md5Sign;
}

- (NSMutableDictionary*)payWithprePayid:(NSString*)prePayid

{
    if(prePayid == nil)
    {
        NSLog(@"prePayid 为空");
        return nil;
    }
    
    //获取到prepayid后进行第二次签名
    NSString    *package, *time_stamp, *nonce_str;
    //设置支付参数
    time_t now;
    time(&now);
    time_stamp  = [NSString stringWithFormat:@"%ld", now];
    nonce_str = [time_stamp MD5];
    //重新按提交格式组包,微信客户端暂只支持package=Sign=WXPay格式,须考虑升级后支持携带package具体参数的情况
    //package       = [NSString stringWithFormat:@"Sign=%@",package];
    package         = @"Sign=WXPay";
    //第二次签名参数列表
    NSMutableDictionary *signParams = [NSMutableDictionary dictionary];
    NSLog(@"%@",signParams);
    [signParams setObject: self.appId  forKey:@"appid"];
    [signParams setObject: self.mchId  forKey:@"partnerid"];
    [signParams setObject: nonce_str    forKey:@"noncestr"];
    [signParams setObject: package      forKey:@"package"];
    [signParams setObject: time_stamp   forKey:@"timestamp"];
    [signParams setObject: prePayid     forKey:@"prepayid"];
    
    //生成签名
    NSString *sign  = [self createMd5Sign:signParams];
    
    //添加签名
    [signParams setObject: sign         forKey:@"sign"];
    
    
    //返回参数列表
    return signParams;
}


如果真的要在App端二次签名的话,那加密的时候还要加入申请的密钥,但是真的不好


样做,其一:服务器已经做过一次签名了,第二次做了返回给你就好了,没必要再给


App。其二:不安全,全放在App上,这种东西一定要放到服务器


小技巧:其实出现上面那种情况有几种可能

1.sign没有二次签名

2.noncerStr是服务器返回的,不要自己生成

3.package是写死的,不要写错了

4.timeStamp是10位数

5.自己签名的sign一定要全部大写

6.为了避免上面的情况,交给服务器管理,我们负责组装拉起微信支付就好了


3.处理回调信息

Appdelegate

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
    
    // 跳转到URL scheme中配置的地址
    //NSLog(@"跳转到URL scheme中配置的地址-->%@",url);
    return  [WXApi handleOpenURL:url delegate:[WXApiManager sharedManager]];
}
//支付成功时调用,回到第三方应用中
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    if ([url.scheme isEqualToString:WECHAT_APPKEY])//微信调用结束
    {
        return [WXApi handleOpenURL:url delegate:[WXApiManager sharedManager]];
    }
    
}

这里的处理是根据微信官网提供的方法,
代理到专门处理的单利当中去统一处理WXApiManager

注意点:有些人用NSNotificationCenter来通知到发出请求的界面去,然后在发起的界面处理回调的逻辑,但是这里

你要考虑一种非人类的交互,TMD有人在拉起微信支付的时候把自己的App给推出了或者App自己挂了,那么当回调

生效的时候,原先拉起微信支付App的界面已经消失了,你发的通知他收不到了,这种情况我是存到本地的

[[NSUserDefaultsstandardUserDefaults]setValue:self.orderStrforKey:@"WECHAT_PAY_ORDER_TRADEID"];

[[NSUserDefaultsstandardUserDefaults]synchronize];

处理回调的时候直接从本地读取


最终处理逻辑的地方(这里不能直接用他的返回接过,要二次确认)

//微信回调,有支付结果的时候会回调这个方法
- (void)onResp:(BaseResp *)resp {
    if([resp isKindOfClass:[PayResp class]]){
        //支付返回结果,实际支付结果需要去微信服务器端查询
        NSString *strMsg,*strTitle = [NSString stringWithFormat:@"支付结果"];
        
        switch (resp.errCode) {
            case WXSuccess:
                strMsg = @"支付结果:成功!";
                NSLog(@"支付成功-PaySuccess,retcode = %d", resp.errCode);
                // 这里别用返回的状态来确定是否正真支付成功了,这样是不对的,我们必须拿着存到本地的traderID去服务器再次check,这样和服务器收到的异步回调结果匹配之后才能确认是否真的已经支付成功了
                [[TWTShoppingCartLogic sharedData] gotoCheckWeChatOrder:tradeID compelete:^(NSError *error, id data) {
                    // 二次确认
                }];
                break;
                
            default:
                strMsg = [NSString stringWithFormat:@"支付结果:失败!retcode = %d, retstr = %@", resp.errCode,resp.errStr];
                NSLog(@"错误,retcode = %d, retstr = %@", resp.errCode,resp.errStr);
                break;
        }
    }

}



还是总结下重要的地方吧


1.App Scheme一定要配置正确


2.千万不能用生成预订单返回的Sign,要重新生成(和后台沟通)


3.要考虑拉起App支付的时候自己程序被退出或者自杀了


4.一定不能用异步返回给App的参数进行判断成功与否,需要和后台进行二次确认,异步返回给后台的数据才是最终的


看官方给的说法

差不多介绍到这里了,自己微信遇到的坑没有接入支付宝的时候多,接过支付宝再接入

微信,真的太简单了,有空再写个支付宝支付,觉得有帮到大家的记得给个赞哦~~~

遇到其他问题了再补充


这里就没有Demo了,有个官方的已经很详细了

点击打开微信官方Demo链接


作者:Deft_MKJing 发表于2016/9/29 17:55:31 原文链接
阅读:649 评论:1 查看评论

swift3.0更新

$
0
0

一:访问权限变更(原文链接:http://www.jianshu.com/p/604305a61e57)

新的访问控制fileprivate和open

在swift 3中新增加了两种访问控制权限 fileprivate open。下面将对这两种新增访问控制做详细介绍。

fileprivate

在原有的swift中的 private其实并不是真正的私有,如果一个变量定义为private,在同一个文件中的其他类依然是可以访问到的。这个场景在使用extension的时候很明显。

class User {
    private var name = "private"
}

extension User{
    var accessPrivate: String {
        return name
    }
}

这样带来了两个问题:

  • 当我们标记为private时,意为真的私有还是文件内可共享呢?
  • 当我们如果意图为真正的私有时,必须保证这个类或者结构体在一个单独的文件里。否则可能同文件里其他的代码访问到。

由此,在swift 3中,新增加了一个 fileprivate来显式的表明,这个元素的访问权限为文件内私有。过去的private对应现在的fileprivate。现在的private则是真正的私有,离开了这个类或者结构体的作用域外面就无法访问。

open

open则是弥补public语义上的不足。
现在的pubic有两层含义:

  • 这个元素可以在其他作用域被访问
  • 这个元素可以在其他作用域被继承或者override

继承是一件危险的事情。尤其对于一个framework或者module的设计者而言。在自身的module内,类或者属性对于作者而言是清晰的,能否被继承或者override都是可控的。但是对于使用它的人,作者有时会希望传达出这个类或者属性不应该被继承或者修改。这个对应的就是 final

final的问题在于在标记之后,在任何地方都不能override。而对于lib的设计者而言,希望得到的是在module内可以被override,在被import到其他地方后其他用户使用的时候不能被override。

这就是 open产生的初衷。通过open和public标记区别一个元素在其他module中是只能被访问还是可以被override。

下面是例子:

/// ModuleA:

// 这个类在ModuleA的范围外是不能被继承的,只能被访问
public class NonSubclassableParentClass {

    public func foo() {}

    // 这是错误的写法,因为class已经不能被继承,
    // 所以他的方法的访问权限不能大于类的访问权限
    open func bar() {}

    // final的含义保持不变
    public final func baz() {}
}

// 在ModuleA的范围外可以被继承
open class SubclassableParentClass {
    // 这个属性在ModuleA的范围外不能被override
    public var size : Int

    // 这个方法在ModuleA的范围外不能被override
    public func foo() {}

    // 这个方法在任何地方都可以被override
    open func bar() {}

    ///final的含义保持不变
    public final func baz() {}
}

/// final的含义保持不变
public final class FinalClass { }
/// ModuleB:

import ModuleA

// 这个写法是错误的,编译会失败
// 因为NonSubclassableParentClass类访问权限标记的是public,只能被访问不能被继承
class SubclassA : NonSubclassableParentClass { }

// 这样写法可以通过,因为SubclassableParentClass访问权限为 `open`.
class SubclassB : SubclassableParentClass {

    // 这样写也会编译失败
    // 因为这个方法在SubclassableParentClass 中的权限为public,不是`open'.
    override func foo() { }

    // 这个方法因为在SubclassableParentClass中标记为open,所以可以这样写
    // 这里不需要再声明为open,因为这个类是internal的
    override func bar() { }
}

open class SubclassC : SubclassableParentClass {
    // 这种写法会编译失败,因为这个类已经标记为open
    // 这个方法override是一个open的方法,则也需要表明访问权限
    override func bar() { } 
}

open class SubclassD : SubclassableParentClass {
    // 正确的写法,方法也需要标记为open
    open override func bar() { }    
}

open class SubclassE : SubclassableParentClass {
    // 也可以显式的指出这个方法不能在被override
    public final override func bar() { }    
}

现在的访问权限则依次为:open,public,internal,fileprivate,private。
有的人会觉得访问权限选择的增加加大了语言的复杂度。但是如果我们思考swift语言的设计目标之一就是一门安全的语言(“Designed for Safety”)就能理解这次的改动。更加明确清晰的访问权限控制可以使程序员表达出更准确的意图,当然也迫使在编码时思考的更加深入


二:方法返回值

Swift 3.0 中方法的返回值必须有接收否则会报警告,当然其实主要目的是为了避免开发人员忘记接收返回值的情况,但是有些情况下确实不需要使用返回值可以使用"_"接收来忽略返回值。当然你也可以增加@discardableResult声明,告诉编译器此方法可以不用接收返回值。

struct Caculator {
    func sum(a:Int,b:Int) -> Int {
        return a + b
    }
    
    @discardableResult
    func func1(a:Int,b:Int) ->Int {
        return a - b + 1
    }
}
let ca = Caculator()
ca.sum(a: 1, b: 2) // 此处会警告,因为方法有返回值但是没有接收
let _ = ca.sum(a: 1, b: 2) // 使用"_"接收无用返回值
ca.func1(a: 1, b: 2) // 由于func1添加了@discardableResult声明,即使不接收返回值也不会警告

三:函数或方法参数

  • 调用函数或方法时从第一个参数开始就必须指定参数名

在Swift的历史版本中出现过在调用函数时不需要指定任何函数参数(或者从第二个参数开始指定参数名),在调用方法时则必须从第二个参数开始必须指定参数名等多种情况,而在Swift3.0中不管是函数还是方法都必须从第一个参数开始必须指定参数名(当然可以使用“_”明确指出调用时省略参数)。

// 从第一个参数就必须指定参数名,除非使用"_"明确指出省略参数
func sum(num1:Int,num2:Int)->Int{
    return num1 + num2
}

sum(num1: 1, num2: 2) // old: sum(1,2)或者sum(1, num2: 2)
  • 取消var参数
//func increase(var a:Int){
//    a += 1
//}
// 上面的代码会报错,可改写成
func increase(a:Int){
    var a = a
    a += 1
}
  • inout参数修饰改放到类型前
//func increase(inout a:Int) {
//    a += 1
//}
// 上面的代码会报错,可改为
func increase( a:inout Int) {
    a += 1
}

四:方法返回值

Swift 3.0 中方法的返回值必须有接收否则会报警告,当然其实主要目的是为了避免开发人员忘记接收返回值的情况,但是有些情况下确实不需要使用返回值可以使用"_"接收来忽略返回值。当然你也可以增加@discardableResult声明,告诉编译器此方法可以不用接收返回值。

struct Caculator {
    func sum(a:Int,b:Int) -> Int {
        return a + b
    }
    
    @discardableResult
    func func1(a:Int,b:Int) ->Int {
        return a - b + 1
    }
}
let ca = Caculator()
ca.sum(a: 1, b: 2) // 此处会警告,因为方法有返回值但是没有接收
let _ = ca.sum(a: 1, b: 2) // 使用"_"接收无用返回值
ca.func1(a: 1, b: 2) // 由于func1添加了@discardableResult声明,即使不接收返回值也不会警告

五:可选类型

Swift3.0对于可选类型控制更加严谨,隐式可选类型和其他类型的运算之后获得的是可选类型而不是隐式可选类型。

let a:Int! = 1
let b = a + 1 // 此时强制解包,b是Int型
let c = a // 注意此时c是Int? 在之前的Swift版本中c是Int!

六:Selector的变化

Selector的改变其实从1.0到3.0经历了多次变化,从最早的@Selector("method:")到现在的#selector(method(param1:))可以说经历了多次修改,好在它变得越来越好,毕竟字符串操作对于语法检查来说是很无助的。

class MyClass {
    @objc func sum(a:Int,b:Int) -> Int {
        return a + b
    }
    
    func func1(){
        let _ = #selector(sum(a:b:))
    }
}

// old: Swift 2.2
//class MyClass {
//    @objc func sum(a:Int,b:Int) -> Int {
//        return a + b
//    }
//    
//    func func1(){
//        let _ = #selector(sum(_:b:))
//    }
//}

七:协议中的可选方法

在Swift3.0之前如果要定义协议中可选方法,只需要给协议加上@objc之后方法使用optional修饰就可以了但是Swift3.0中除了协议需要@objc修饰可选方法也必须使用@objc来修饰

@objc protocol MyProtocol {
    @objc optional func func1() //old: optional func func1()
    func func2()
}

八:取消++、--操作符

var d = 1
d++ //报错,可以改写成 d += 1 或者 d = d + 1

九:取消C风格for循环

//for var i = 0 ;i < 10 ; i += 1 {
//    debugPrint(i)
//}
// 上面的代码会报错,可改写成如下代码
for i in 0  ..< 10  {
    debugPrint(i)
}

十:命名

// 1.去掉前缀
let url1 = URL(string: "www.cmjstudio.com")
let isFileURL = url1?.isFileURL //old:url1.fileURL ,现在更加注重语意
let data1 = Data() //NSData

// 2.方法名使用动词,其他名词、介词等作为参数或移除
var array1 = [1,2,3]
array1.append(contentsOf: [4,5,6]) // old:array1.appendContentsOf([4,5,6])
array1.remove(at: 0) // old:array1.removeAtIndex(0)

// 3.不引起歧义的情况下尽量消除重复
let color1 = UIColor.red() // old:var color1 = UIColor.redColor()

// 4.枚举成员首字母变成小写
let label1 = UILabel()
label1.textAlignment = .center // old:label1.textAlignment = .Center

// 5.按钮的Normal状态去掉
let btn1 = UIButton()
btn1.setTitle("hello", for: UIControlState()) // 相当于Normal状态

十一:去C风格

Swift发展初期很多类库的引入依然保持的ObjC风格,但是ObjC由于根出C语言,因此很多操作其实并不是对象和方法操作而是C语言的函数形式。到了Swift3.0之后这一现状将发生变化,全局函数将会变成某些类型的方法;某些常量定义将以某个枚举类型的成员来表示。

let rect1 = CGRect(x: 0, y: 0, width: 100, height: 100)
// 下面的代码将要报错,3.0完全废除这种类C的风格
//let rect1 = CGRectMake(0, 0, 100, 100)

if let context1 = UIGraphicsGetCurrentContext() {
    CGContext.fillPath(context1) // old:CGContextFillPath(context1!)
}

// GCD的改变
let queue = DispatchQueue(label: "myqueue")
queue.async {
    debugPrint("hello world!")
}
// old:
//let queue = dispatch_queue_create("myqueue", nil)
//dispatch_async(queue) {
//    debugPrint("hello world!")
//}

// 相关常量定义被移到枚举内部
NotificationCenter.defaultCenter().addObserver(self, selector: #selector(userDefaultChange()), name: UserDefaults.didChangeNotification, object: nil)
//old:NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(userDefaultChange()), name: NSUserDefaultsDidChangeNotification, object: nil)

集合API的变化

let array1 = [1,2,3]
let next = array1.index(after: 0)  // old:let start = array1.startIndex let next = start.successor()
let first = array1.first { (element) -> Bool in // 增加新的方法
    element > 1
}

let r = Range(0..<3) //old: let _ = NSRange(location: 0, length: 3)

// 下面的代码必须在控制器中执行,用于遍历当前view及其父视图
for subview in sequence(first: self.view, next: { $0?.superview }){
    debugPrint(subview)
}

十二:新的浮点协议

Float、Double、CGFloat使用了新的协议,提供了提供 IEEE-754标准的属性和方法。

let a = 2 * Float.pi // old: let a = 2 * M_PI
let b = 2.0 * .pi // 注意前面是浮点型,后面可以省略Float

十三:随机数函数的变化

random()->arc4random()


作者:wahaha13168 发表于2016/9/29 18:37:55 原文链接
阅读:222 评论:0 查看评论

React Native原生模块与JS模块通信的几种方式

$
0
0

React Native原生模块与JS模块通信的几种方式

React Native原生模块向JS传递数据的几种方式.png

在做React Native开发的时候避免不了的需要原生模块和JS之间进行数据传递,这篇文章将向大家分享原生模块向JS传递数据的几种方式。

方式一:通过Callbacks的方式

说起Callbacks大家都不陌生,它是最常用的设计模式之一。无论是Java,Object-c,C#,还是JavaScript等都会看到Callbacks的身影。

原生模块支持Callbacks类型的参数,该Callbacks对应JS中的function。

在原生模块中:

public class RNTestModule extends ReactContextBaseJavaModule{
    public RNTestModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }
    @Override
    public String getName() {
        return "RNTest";
    }

  @ReactMethod
  public void measureLayout(
      int tag,
      int ancestorTag,
      Callback errorCallback,
      Callback successCallback) {
    try {
      measureLayout(tag, ancestorTag, mMeasureBuffer);
      map.putDouble("relativeX",1);
      map.putDouble("relativeY", 1);
      map.putDouble("width", 2);
      map.putDouble("height",3);
      successCallback.invoke(relativeX, relativeY, width, height);
    } catch (IllegalViewOperationException e) {
      errorCallback.invoke(e.getMessage());      
    }
}

在上述代码中,measureLayout方法的参数中后两个就是Callbacks,当原生模块处理成功时通过successCallback回调来告知JS处理成功的结果,当原生模块发生异常时,则通过errorCallback回调来JS处理异常。

在JS模块中:

RNTest.measureLayout(
  100,
  100,
  (msg) => {
    console.log(msg);
  },
  (x, y, width, height) => {
    console.log(x + ':' + y + ':' + width + ':' + height);
  }
);

上述代码,是在JS模块中调用原生模块的方法measureLayout,同时向它传递了四个参数,后两个是function类型的参数用于接收原生模块的处理结果。

通过上述的方式,JS调用原生模块的measureLayout方法,原生模块则通过errorCallbacksuccessCallbackCallbacks来将处理结果传递到JS。

这种“You call me,I will callback”,的方式小伙伴们都看懂了吧。

方式二:通过Promises的方式

Promises是ES6的一个新的特性,在React Native中你会看到Promises的大量使用。
原生模块也是支持Promises的,这对喜欢使用Promises的小伙伴则是一个很好的消息。

在原生模块中:

public class RNTestModule extends ReactContextBaseJavaModule{
    public RNTestModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }
    @Override
    public String getName() {
        return "RNTest";
    }
    @ReactMethod
    public void measureLayout(
            int tag,
            int ancestorTag,
            Promise promise) {
        try {
            WritableMap map = Arguments.createMap();
            map.putDouble("relativeX",1);
            map.putDouble("relativeY", 1);
            map.putDouble("width", 2);
            map.putDouble("height",3);

            promise.resolve(map);
        } catch (IllegalViewOperationException e) {
            promise.reject(e);
        }
    }
}

上述代码中,measureLayout方法接收的最后一个为Promise,当相应的处理结果出来之后原生模块通过调用Promise的相应方法来向JS模块传递处理成功,或处理失败的数据。

提示:在原生模块中Promise类型的参数要放在最后一位,这样JS调用的时候才能返回一个Promise。

在JS模块中:

async test() {
  try {
    var {
        relativeX,
        relativeY,
        width,
        height,
    } = await RNTest.measureLayout(100, 100);

    console.log(relativeX + ':' + relativeY + ':' + width + ':' + height);  
  } catch (e) {
    console.error(e);
  }
}

在上述代码中,通过ES7的新特性async/await来修饰了test方法,来以同步方式调用原生模块的measureLayout方法,如果原生模块处理成功,
那么JS中relativeX,relativeY,width,height会获得相应的值,如果原生模块处理失败,则会抛出异常。

如果,不希望以同步的形式调用,可以这样写:

test2(){
  RNTest.measureLayout(100,100).then(e=>{
    console.log(e.relativeX + ':' + e.relativeY + ':' + e.width + ':' + e.height);
    this.setState({
      relativeX:e.relativeX,
      relativeY:e.relativeY,
      width:e.width,
      height:e.height,
    })
  }).catch(error=>{
    console.log(error);
  });
}

以上就是通过Promises的方式向JS传递数据的方式,小伙伴们看懂了吗。

上述两种方式,通过Callbacks的方式通过Promises的方式,都可以向JS模块传递数据,但都是只能传递一次。
如果,你需要多次向JS模块传递数据(如:按键事件)上述方式还是不够好,下面就像大家分享可以多次传递数据的方式。

方式三:通过发送事件的方式

原生模块支持另外一种向JS模块传递数据的方式,通过发送事件的方式。

原生模块,可以向JS传递事件而不要而不需要直接的调用,就像Android中的广播,iOS中的通知中心。

下面就向大家演示通过RCTDeviceEventEmitter,来向JS传递事件。

在原生模块中:

@Override
public void onHandleResult(String barcodeData) {
    WritableMap params = Arguments.createMap();
    params.putString("result", barcodeData);
    sendEvent(getReactApplicationContext(), "onScanningResult", params);
}

private void sendEvent(ReactContext reactContext,String eventName, @Nullable WritableMap params) {
    reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit(eventName, params);
}

上述代码向JS模块发送了一个名为“onScanningResult”的事件,并携带了“params”作为参数。

在JS模块中:

下面是在JS代码中进行监听原生模块发出的名为“onScanningResult”的事件。

componentDidMount() {
    //注册扫描监听
    DeviceEventEmitter.addListener('onScanningResult',this.onScanningResult);
}
onScanningResult = (e)=> {
    this.setState({
        scanningResult: e.result,
    });
    // DeviceEventEmitter.removeListener('onScanningResult',this.onScanningResult);//移除扫描监听
}

在JS中通过DeviceEventEmitter注册监听了名为“onScanningResult”的事件,当原生模块发出名为“onScanningResult”的事件后,绑定在该事件上的onScanningResult = (e)会被回调。
然后通过e.result就可获得事件所携带的数据。

心得:如果在JS中有多处注册了onScanningResult事件,那么当原生模块发出事件后,这几个地方会同时收到该事件。不过大家也可以通过DeviceEventEmitter.removeListener('onScanningResult',this.onScanningResult)
来移除对名为“onScanningResult”事件的监听。

另外,JS模块也支持通过Subscribable mixin,也注册监听事件,因为ES6已经不再推荐使用mixin,所以在这里也就不向大家介绍了。

三种方式的优缺点

方式 缺点 优点
通过Callbacks的方式 只能传递一次 传递可控,JS模块调用一次,原生模块传递一次
通过Promises的方式 只能传递一次 传递可控,JS模块调用一次,原生模块传递一次
通过发送事件的方式 原生模块主动传递,JS模块被动接收 可多次传递

最后

既然来了,留下个喜欢再走吧,鼓励我继续创作(^_^)∠※

如果喜欢我的文章,那就关注我的博客吧,让我们一起做朋友~~

戳这里,加关注哦:

微博:第一时间获取推送
个人博客:干货文章都在这里哦
GitHub:我的开源项目

作者:fengyuzhengfan 发表于2016/9/29 18:52:01 原文链接
阅读:204 评论:0 查看评论

Android 异步任务:AsyncTask机制 源码详细版解析------从入门到升天

$
0
0

相信大家对AsyncTask机制都不陌生?基本的使用的方法和注意事项都清楚,编写一个类,继承AsyncTask类,必须重写doInBackground()方法,运行在子线程,耗时的操作在此执行;onPostExecute()方法在doInBackground()执行完被回调,运行在主线程,可进行UI更新;若需要显示更新的进度,可重写onProgressUpdate()方法。

在一开始学习异步任务AsyncTask时,我们就在被各种教程传输这些理论知识,确实对于它已经了然于心,可是有考虑过底层是如何详细实现AsyncTask机制的吗?为何onPreExecute()方法最先被调用doInBackground()如何封装子线程中的?onPostExecute方法回调又是如何实现的?这些方法之间是如何被联系起来的?

虽然Android已为我们封装AsyncTask类,只有从源代码的角度去剖析才能真正掌握。(以下讲解会一步步深入,剥茧抽丝,弄清楚详细实现方式,想要彻底剖析一定要有耐心,另配有逻辑图供大家整理AsyncTask机制的逻辑流程!)



一. AsyncTask机制

1. AsyncTask基本使用:

AsyncTask<String, Integer, Object> asyncTask = new AsyncTask<String, Integer, Object>() {

    protected void onPreExecute() {};

    @Override
    protected Object doInBackground(String... params) {
        return null;
    }

    protected void onPostExecute(Object result) {};

    protected void onProgressUpdate(Integer[] values) {};

};
asyncTask.execute("params")

问题:

  1. onPreExecute() 方法为何在主线程中执行?为何最先执行?
  2. doInBackground()方法为何在子线程中执行?如何实现该方法执行完就回调 onPostExecute() 方法?
  3. onPostExecute() 方法为何在主线程中进行?

以上为AsyncTask的基本使用:new 一个AsyncTask类,重写需要逻辑实现的方法。调用execute方法,任务就开始执行了。所以,看源代码就从这个入口execute方法开始分析:






AsyncTask的execute方法

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    //(1)检测状态
    if(mStatus != Status.PENDING){
        switch(mStatus){
        case RUNNING:
            throw new IllegalStateException("Cannot execute task:"
                    +" the task is already running.");
        case FINISHED:
            throw new IllegalStateException("Cannot execute task:"
                    +" the task has already been executed"
                    +"(a task can be executed only once)");         
        }
    }
    //(2)修改状态
    mStatus = Status.RUNNING;

    //(3)调用onPreExecute方法
    onPreExecute();

    //(4)把参数赋值给mWorker对象
    mWorker.mParams = params;

    //(5)线程池对象执行mFuture
    sExecutor.execute(mFuture);

    return this;
}

在此方法体中,开始执行异步任务,逐步骤分析:


(1)检测状态

若状态为RUNNING:顾名思义为正在执行,抛出异常“无法执行,该任务正在执行”。所以说一旦new 了一个AsyncTask连续调用两次execute方法,肯定会报错。

若状态为FINISHED:顾名思义为已经执行过,抛出异常“无法执行,该任务已经执行过,一个任务只能执行一次”。相当于AsyncTask类中的doInBackground方法已经执行完,回调过onPostExecute方法了,再次调用execute方法,比如想再执行一次下载任务,就会报错!

由以上我们可以得出一个结论:execute方法只能调用一次,若想多次执行,多次的new一个AsyncTask类,再去调用对应的execute方法。



(2)修改状态

在进过步骤一后(代表状态判断未报错),即可修改状态为RUNNING(即该任务正在执行)。



(3)调用onPreExecute方法

看到onPreExecute方法是不是觉得倍感亲切,虽然我们在new一个AsyncTask重写其方法时,很少会去重写该方法。现在我们知道此方法为何是在主线程中执行的了:

因为最开始new一个AsyncTask后,执行execute方法这些步骤本就是在主线程中执行的,而execute方法中又调用onPreExecute方法,所以自然其运行在主线程,并且也是以上四个方法中最先执行的方法!


【AsyncTask机制逻辑流程图 v1】:
这里写图片描述



(4)把参数赋值给mWorker对象

mWorker.mParams = params;

以上代码中的params就是 一开始调用AsyncTaskexecute方法时传进来的参数,此参数被保存到mWorker对象中的mParams变量中,可是这个mWorker又是什么?首先来看它的声明

privaet final WorkerRunnable<Params , Result> mWorker;

mWorker是一个WorkerRunnable类,继续深入,查看其具体实现

private static class InternalHandler extends Handler{
    @Override
    public void handleMessage(MMessage msg){
        ...//我也很重要,后面再讲
    }

    //☆☆☆看我☆☆☆
    private static abstact class WorkerRunnable<Params, Result> implements Callable<Result>{
        Params[] mParams;
    }
    ...
}

以上可得知,WorkerRunnable是一个InternalHandler 类(重要的一个类)的内部类,有一个mParams数组,来保存了一开始调用AsyncTaskexecute方法时传进来的参数


【AsyncTask机制逻辑流程图 v2】:
这里写图片描述


继续分析,此类还实现了一个Callable接口,查看具体实现

public interface Callable<V>{
        V call() throws Exception;
}

这个Callable接口中只有一个call()方法,方法作用?何时调用?先留下一个疑问,我们后文分解。既然”mWorker对象的声明“这一个线索已经挖到底了,就寻找下一个突破口,查看它的初始化

public AsyncTask(){
    //☆☆☆看我☆☆☆
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return doInBackground(mParams);
        }
    };

    mFuture = new FutureTask<Result>(mWorker){
        ...//我也很重要,后面再讲
    }
}

mWorker初始化部分在AsyncTask的构造方法中,代表最开始new 一个 AsyncTask时,mWorker对象就已经被初始化了。以上分析,其实mWorker就是一个Callable类型对象,它在初始化时new完后,覆盖了一个call方法,而call方法的返回值正是 调用doInBackground()方法的返回值,并且在调用的时候传递了参数mParams


【AsyncTask机制逻辑流程图 v3】:
这里写图片描述


现在问题二就来了,这个doInBackground()方法是如何运行在子线程中?也就是说这个call方法何时被一个子线程调用?

莫方!先意识到这个问题,保留疑问,继续往下走。以上就是步骤四的逻辑,我们继续回到execute方法中,依次执行步骤五,来解决疑问。



(5)线程池对象执行mFuture

sExecutor.execute(mFuture);

老样子!这一行代码看不懂,首先查找sExecutor对象声明

private static final ThreadPool Executor sExecutor = new ThreadPoolsExecutor(CORE_POOL_SIZE,MAXIMUN_POOL_SIZE,KEEP_ALIVE,TimeUnit.SECONDS,sWorkQueue,sThreadFactory);

真面目揭晓!所以说Asynctask能够执行多个任务,是因为类中有一个线程池回到步骤五的那行代码中,sExecutor对象执行execute方法就是往线程池中取出来一个线程,去处理括号中的内容(即mFuture),mFuture又是何物?来查看声明

FutureTask<Result> mFuture;

得知mFuture是一个FutureTask类型,继续深入,查看具体实现

public class FutureTask<V> implements RunnableFuture<V>{
    public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
        sync = new Sync(callable);
    }
    ...
}

发现FutureTask类型又是实现了RunnableFuture,剥茧抽丝,继续深入查看具体实现

public interface RunnableFuture<V> extends Runnable,Future<V>{
    void run();
}

可以恍然大悟了!原来这个RunnableFuture接口是实现了Runnable,所以里面就有一个run方法,这跟我们所学习的理论知识“运行子线程时一般要在run方法中执行”是非常吻合的!理解之后,这一条“mFuture的声明”线索也到底了,注意需要明确一点:执行mFuture对象就是在执行run方法!现在问题就是这个run方法在哪里被调用了?或者说mFuture在哪里覆盖重写run方法?寻找新线索,mFuture声明完后,来查找它初始化的地方:

public AsyncTask(){
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return doInBackground(mParams);
        }
    };

//☆☆☆看我☆☆☆
    mFuture = new FutureTask<Result>(mWorker){
    //此处重写done方法
    @Override
    protected void done() {
        ...
        //获取doInbackground方法返回的结果
        result = get();
        ...
        //创建一个消息
        message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(AsyncTask.this, result));
        //把这条消息发送给创建这个消息的Handler:target.sendMessage(this)
        message.sendToTarget();
    }
    };
}

找到mFuture初始化地方,乍一看到步骤四中分析的mWorker对象也是在 AsyncTask的构造方法中(即AsyncTasknew出来的时候这两个对象就都实例化了!)。并且mFuturenew 一个 FutureTask时,传了一个参数mWorker。(这里简单回顾mWorker是一个Callable对象,有一个call方法,方法中调用doInBackground),代表将参数mWorker存储FutureTask类的构造方法中了,继续深入,查看FutureTask实现

public class FutureTask<V> implements RunnableFuture<V>{
    private final Sync sync;

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
            sync = new Sync(callable);
    }
} 

查看FutureTask构造方法,也可以证实之前对mWorker对象的回顾,就是一个Callable类型。sync = new Sync(callable);又把mWorker封装Sync对象Sync为何物?查看其具体实现


Sync(Callable<V> callable) {
    //这里的callable就是mWorker
    this.callable = callable;
}

只看Sync构造方法即可,可得知只是做了简单的保存。这条线索“FutureTask类的声明和实现”也挖到底了,


【AsyncTask机制逻辑流程图 v4】:
这里写图片描述


回到mFuture对象初始化时(在Asynctask构造方法中),它在new 了一个FutureTask类中,覆盖重写了父类的done方法,先暂缓done方法具体实现的分析。

我们之前说过“执行mFuture对象就是在执行run方法”!那么现在查找run方法在何处被调用

//FutureTask类中的方法
//mFuture的run方法被调用了
public void run() {
    sync.innerRun();
}

run方法中又调用Sync对象innerRun方法,深入,查看其具体实现:

void innerRun() {
    ...
        //调用mWorker的call()
        result = callable.call();
        ...
        set(result);
    ...
}

innerRun方法中,调用了callable(也就是mWorker)的call方法,因为在syncnew出来的时候,在构造方法中就已经把mWorker赋值给了callable,所以实际上是调用mWorker的call方法所以一大悬案—–问题二“为何doInBackground方法运行在子线程”已被侦破:

mFuturerun方法肯定执行在子线程中,run方法又调用innerRun方法,其也在子线程中,innerRun方法又调用了mWorkercall方法!其call方法中又调用了 doInBackground方法,所以它必然执行在子线程(这一系列的推理一定要掌握)


【AsyncTask机制逻辑流程图 v5】:
这里写图片描述


理解之后,新的问题又来了,doInBackground方法如何将其返回值传给onPostExecute方法中,它是如何被回调的?怎么又在主线程中运行的?莫方!回到上面代码继续往下:
result = callable.call();
调用mWorkercall方法后有一个返回值result
set(result);
又调用了set方法(方法在Sync对象的innerRun方法里面),将其保存起来。查看set方法具体实现

//FutureTask类中的方法
protected void set(V v) {
     sync.innerSet(v);
}

set方法(在FutureTask类中)中又调用Sync对象的innerSet方法,继续深入查看:

void innerSet(V v){
    ...
    ...
     if (compareAndSetState(s, RAN)) {
                result = v;
                releaseShared(0);
                //关键的done方法
                done();
                return;
      }
  }

innerSet方法中调用了done方法,怎么有点眼熟?!查看其实现:

//FutureTask类中的方法
protected void done(){}

done方法(在FutureTask类中)没有方法体,那调用此方法有何意义?但存在即合理!这个方法肯定是在new 一个FutureTask类时,将其覆盖重写了!这条线索“执行mFuture对象就是在执行run方法”已完毕,带着我们的猜测回到mFuture初始化AsyncTask构造方法)代码中,上面有代码,再次粘贴主要部分:

mFuture = new FutureTask<Result>(mWorker){
    //此处重写done方法
    @Override
    protected void done() {
        ...
        //获取doInbackground方法返回的结果
        result = get();
        ...
        //创建一个消息
        message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(AsyncTask.this, result));
        //把这条消息发送给创建这个消息的Handler:target.sendMessage(this)
        message.sendToTarget();
    }
    };

首先是get方法,之前在FutureTask中,Sync变量保存了doInBackground返回值!所以这里取出结果,取出来后创建了一个消息,传入了两个参数:MESSAGE_POST_RESULT(是post的一个结果)、new了一个AsyncTaskresult类,并将doInBackground返回值传入到newAsyncTaskresult的参数中。


【AsyncTask机制逻辑流程图 v6】:
这里写图片描述


其实创建消息时接受的两个参数,一个是post结果,一个是Message里面的Object,用Object保存了一个AsyncTaskresult对象,这个对象就保存有返回值。最后调用messagesendToTarget方法,这个方法感觉很陌生,继续深入,查找其实现:

public void sendToTarget(){
    target.sendMessage(this);
}

target调用了sendMessage方法,其实这个target就是一个hanlder,来发送消息,由于sendToTarget方法在Message内部,所以传入参数this,就是自身。一发消息就会发到Handler中,查找Handler何处声明

private static final InternalHandler sHandler =  new InternalHandler();

Handler在这里new 出来了,是一个InternalHandler 类,里面一定有一个hanlderMessage方法来处理接收的消息:

private static class InternalHandler extends Handler{
    public void handleMessage(Message msg) {
        AsyncTaskResult result = (AsyncTaskResult) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_RESULT:
                //调用finish方法
                result.mTask.finish(result.mData[0]);
                break;
             ...
             ...
        }
    }
}

果然如此,声明了一个内部类继承于Handler重写了父类的handleMessage方法,所以之前mFuture对象重写的done**方法**中发送的消息会发送至此!

之前创建消息的时候将doInBackground返回值作为参数传入到AsyncTaskresult类中(也就是MessageObject类),现在将result取出来,执行了:

result.mTask.finish(result.mData[0]);

这个mTask就是之前创建消息时传入的第一个参数,也就是AsyncTask本身mData[0]就是doInBackground返回值。再看AsyncTask对象的finish的方法体:

private void finish(Result result) {
 if (isCancelled()) result = null;
    //调用onPostExecute方法,并传入结果
    onPostExecute(result);
    mStatus = Status.FINISHED;
}

首先看方法体中的参数result就是doInBackground返回值,注意注意!此处就回调了onPostExecute方法,并传入结果所以又一大悬案“onPostExecute如何在doInBackground执行完后实行回调并得到结果?”已侦破!

可是最后一大悬案“onPostExecute方法为何运行在主线程?”

是因为通过Handler发送消息实现的!通过InternalHandler发消息,一发就跑到主线程了呀!






二. AsyncTask机制逻辑流程图【最终版】

这里写图片描述






三. AsyncTask机制归纳

AsyncTask 异步任务, 主要包含两个关键类:

(1)InternalHandler : 消息处理器, 用于处理线程之间消息.

(2)ThreadPoolExecutor : 线程池, 用于处理耗时任务.



归纳:

结合源代码分析之后,我们会发现主要是InternalHandlerThreadPoolExecutor这两个类起着重要作用!

所以,Android 原生的 AsyncTask机制 就是对线程池(也就是ThreadPoolExecutor)的一个封装,使用其自定义Executor来调度线程的执行方式并发还是串行),最后使用 Handler(也就是InternalHandler) 来完成子线程主线程数据共享






其实写此博客之前,我搜到网上已经有很多大牛分析过了,并且更加精髓。我在考虑自己再写会不会有点炒冷饭的感觉,可是别人的东西毕竟是别人的,多总结归纳总是有好处的,共勉~

最后!我讨厌csdn博客保存功能,因为之前没保存,重写了两次,心碎!但是当我重写第二遍的时候,有些细节部分更明白了,所谓温故而知新啊,也许第一遍看源代码分析不太懂,但是多看几遍就理解了!而且此篇博客关键在于一步步分析,十分详细!有何问题欢迎讨论。

希望对你们有帮助:)

作者:ITermeng 发表于2016/9/29 18:52:24 原文链接
阅读:212 评论:0 查看评论

详解Android主流框架不可或缺的基石

$
0
0

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


前言

经过几年的发展和沉淀,Android开发中涌现出许多优秀的框架,比如:Retrofit、Afinal、OKHttp、ButterKnife、AndFix等等。这些框架的出现极大地简化了开发流程,提高了工作效率。在项目开发的过程中我们主要是使用这些轮子完成项目,很难有时间去顾及框架的内部实现。在项目交付之后我们可能就要去看看这些框架的源码了。

这些主流框架的功能各不相同,但每当打开浩繁的源码时我们几乎都可以看到反射,注解,泛型的广泛应用;也正是这些技术使得框架具有了高度的灵活性,优良的扩展性和健壮的稳定性。鉴于这些框架必备知识的重要性故在此对这部分内容做一个全面的梳理和总结。

主要内容:

  • ClassLoader
    • ClassLoader的分析
    • 几种不同ClassLoader的介绍
    • ClassLoader的应用
  • 泛型
    • 泛型的由来
    • 自定义泛型
    • 泛型的擦除
  • 反射
    • Class
    • 常用反射技术
    • Type以及ParameterizedType
    • 反射与泛型的结合使用
  • 注解
    • 常用注解的介绍和使用
    • 元注解
    • 自定义注解及其使用

ClassLoader

在程序运行时首先要将类加载到内存中,这个加载工作就是由ClassLoader完成的,故在中文文档中将其翻译为”类加载器”。
那么我们代码中所用到的类有什么不同呢?——它们的”来源”是不一样的。
有的类是属于系统提供的类,比如:String、Date、Object等
所以,在Android系统启动时会自动创建一个Boot类型的ClassLoader,该ClassLoader用于加载一些系统层级的类。
有的类属于我们自己写的类,比如:User、Girl、Beauty等等
所以,每个APP会创建一个自己的ClassLoader实例,该ClassLoader用于加载dex。
嗯哼,我们再通过代码来验证一下:

   /**
     * 原创作者:
     * 谷哥的小弟
     * 
     * 博客地址:
     * http://blog.csdn.net/lfdfhl
     */
    private void getClassLoaders() {
        ClassLoader classLoader = getClassLoader();
        while (null != classLoader) {
            System.out.println("----> classLoader=" + classLoader);
            classLoader = classLoader.getParent();
        }
    }

输出结果如下所示:

这里写图片描述

此处一共展示了两个ClassLoader

第一个ClassLoader,如下:

dalvik.system.PathClassLoader[DexPathList[[zip file
“/data/app/cc.testreflection-2/base.apk”],nativeLibraryDirectories=[/vendor/lib,/system/lib]]]

该PathClassLoader在应用启动时创建,用于加载/data/app/cc.testreflection-2/base.apk中的类。

ClassLoader是一个抽象类,它有三个常用的子类:PathClassLoader、URLClassLoader、DexClassLoader。

PathClassLoader
它只能加载已经安装的apk中的资源,比如dex文件
URLClassLoader
它只能用于加载jar文件中的资源。但是dalvik不能直接识别jar,所以这个加载器极少使用。
DexClassLoader
它用于从.jar和.apk类型的文件内部加载classes.dex。该类加载器常用来完成动态加载apk的需求。

第二个ClassLoader,如下:

classLoader=java.lang.BootClassLoader@21b737fd

该BootClassLoader在系统启动的时候创建,用于加载系统层级的类。

在认识了这两种类加载器之后,我们看看它们的应用。

 /**
     * 原创作者:
     * 谷哥的小弟
     *
     * 博客地址:
     * http://blog.csdn.net/lfdfhl
     */
    private void testClassLoader() {
        try {
            Class clazz = Class.forName("cc.testreflection.Girl");
            ClassLoader classLoader = clazz.getClassLoader();
            System.out.println("----> classLoader=" + classLoader);

            classLoader = mContext.getClass().getClassLoader();
            InputStream inputStream = classLoader.getResourceAsStream("assets/ic_launcher.png");
            System.out.println("----> classLoader=" + classLoader);

            clazz = Class.forName("java.lang.String");
            classLoader = clazz.getClassLoader();
            System.out.println("----> classLoader=" + classLoader);
        } catch (Exception e) {

        }
    }

输出结果如下所示:

这里写图片描述

嗯哼,和之前的分析一样:
我们自己的类cc.testreflection.Girl和assets文件夹中的图片ic_launcher.png都是由PathClassLoader加载的,而java.lang.String是由BootClassLoader加载的。


泛型

泛型始现于JDK1.5,从那以后大家在项目常常使用泛型,比如:

    ArrayList<Girl> arrayList=new ArrayList<Girl>();
    for(int i=0;i<10;i++){
        Girl girl =new Girl();
        arrayList.add(girl);
    }

在与此类似的场景中利用泛型限定了集合中的输入类型,从而让编译器屏蔽了源程序中的非法数据输入,比如此时往ArrayList<Girl>中add一个Boy就无法通过编译器的编译。

刚才已经说了,泛型主要是给编译器看的;那么在编译完成之后生成的字节码里泛型会发生什么变化呢?来看个例子:

private void testArraylistClass() {
    Class clazz1 = new ArrayList<Integer>().getClass();
    Class clazz2 = new ArrayList<String>().getClass();
    boolean isEqual=(clazz1 == clazz2);
    System.out.println("----> isEqual=" +isEqual);
}

输出结果:

—-> isEqual=true

哇哈!看到了没有?——带不同泛型的ArrayList在编译后生成的Class是相同的!也就是说,泛型在编译生成字节码文件时会被”擦除”;不管ArrayList<E>带什么泛型,在编译后都是ArrayList所对应的字节码文件。

我们再来个更直观的:

这里写图片描述

请注意报错:both methods have same erasure
因为泛型擦除后这两个方法就是变成了完全相同了,当然也就不是我们认为的方法重载了,所以Android Studio直接就提示了这个错误。

嗯哼,在认识了泛型的擦除之后,我们来体验一下非一般的感觉:

private void testArraylistGeneric(){
    try {
        ArrayList<Integer> arrayList =new ArrayList<Integer>();
        arrayList.add(9527);
        arrayList.add(9528);
        Method method=arrayList.getClass().getMethod("add",Object.class);
        method.invoke(arrayList,"hello,java");
        for (int i=0;i<arrayList.size();i++){
            System.out.println("----> arrayList.get("+i+")=" + arrayList.get(i));
        }
    }catch (Exception e){

    }

}

输出结果如下图所示:

这里写图片描述

看到了吧,之所以能把一个字符串add到该ArrayList<Integer>中,究其原因还是因为泛型的擦除所致。

嗯哼,接下来瞅瞅自定义泛型。

  1. 自定义泛型方法

    public static <T> T genericMethod1(T t) {
        return null;
    }
    
    public <K, V> K genericMethod2(K k, V v) {
        return null;
    }
    
    public <K, V> String genericMethod3(K k, V v) {
        return null;
    }

    在自定义泛型方法时,请注意在方法的返回值之前声明一个泛型,比如:<T>这就表示该方法使用到了泛型T。在此之后,在方法的输入参数中和方法体中均可以使用该泛型

  2. 自定义泛型接口

    public interface UserInfo<T> {
        public void printUserInfo(T t);
    }
    
    
    private class UserInfoImpl<T> implements UserInfo<T> {
        @Override
        public void printUserInfo(T t) {
    
        }
    }

    在自定义泛型接口时,请注意在接口名之后声明一个泛型,比如:<T>这就表示该接口使用到了泛型T。在此之后,在接口定义方法时就可以使用该泛型了

  3. 自定义泛型类

    public class Collection<K, V> {
        private K key;
        private V value;
    
        private K getValue(K k) {
            return null;
        }
    
        private void printValue(V v) {
    
        }
    }

    自定义泛型类与自定义泛型接口非常类似,不再赘述


反射

我们知道Java代码会被编译成字节码文件,当需要用一个类创建其对象的时候就会将其对应的字节码文件装载到内层,然后新建对象。也就是说,当一个类编译完成后,在生成的.class文件中会产生一个Class对象,该对象用于表示这个类的信息,比如类的属性,字段,构造方法等等。
既然Class中包含了这么多有用的信息,那么我们可以用什么方式获取Class呢?

 private void testGetClass() {
        try {
            //第一种方式
            Class clazz = Girl.class;
            System.out.println("----> " + clazz.getName());

            //第二种方式
            Girl girl = new Girl();
            clazz = girl.getClass();
            System.out.println("----> " + clazz.getName());

            //第三种方式
            clazz = Class.forName("cc.testreflection.Girl");
            System.out.println("----> " + clazz.getName());
        } catch (Exception e) {

        }
    }

获取Class的三种方式:

  1. 利用类名.class获取
  2. 利用对象.getClass()获取
  3. 利用Class.forName(“类名”)获取

在获取到Class之后,就可以利用newInstance()方法生成一个对象。

Object object = clazz.newInstance();

其实,在调用newInstance()方法时实际上是调用了该类的无参构造方法。
当然,我们的目的不仅仅是利用newInstance()生成一个对象,更重要的是要采用反射技术结合Class获取到该类的构造方法,属性,方法等信息。

比如有这么一个类:

public class Girl {
    public String country;
    public String city;
    private String name;
    private int age;
    private int bust;
    private int waist;
    private int hip;

    public Girl(){
        System.out.println("调用Girl的无参构造方法");
    }

    public Girl(String name, int waist, int bust, int hip, int age) {
        this.name = name;
        this.waist = waist;
        this.bust = bust;
        this.age = age;
        this.hip = hip;
    }

    private Girl(String name,Integer age){
        this.name = name;
        this.age=age;
    }

    private String getMobile(String number){
        String mobile="010-110"+"-"+number;
        return  mobile;
    }

    @Override
    public String toString() {
        return "Girl{" +
                "country='" + country + '\'' +
                ", city='" + city + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", bust=" + bust +
                ", waist=" + waist +
                ", hip=" + hip +
                '}';
    }
}

在该类中有一些简单的属性,比如年龄,姓名,国家,城市,腰围,胸围,臀围。还有一些简单的方法比如,构造方法Girl(String name,Integer age),获取电话号码getMobile();看到这里获取大家可能发现了:这些属性和方法有的是公有的,有的是私有的。访问属性的不同会带来哪些差异呢?带着这个小疑问,我们来看看常见的反射使用方法。

  1. 利用反射获取构造方法

    /**
     * 利用反射获取类的构造器
     * 
     * 1 getConstructors()获取类的构造器,但获取不到私有构造器
     * 2 getDeclaredConstructors()获取类的所有构造器
     * 3 getDeclaredConstructor()获取指定的构造器
     */
    private void testGetConstructor() {
        try {
            Class clazz = Class.forName("cc.testreflection.Girl");
            Constructor[] Constructors = clazz.getConstructors();
            for (Constructor constructor : Constructors) {
                System.out.println("----> constructor=" + constructor);
            }
    
            Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
            for (Constructor declaredConstructor : declaredConstructors) {
                System.out.println("----> declaredConstructor=" + declaredConstructor);
            }
    
            Constructor constructor = clazz.getDeclaredConstructor(String.class, Integer.class);
            constructor.setAccessible(true);
            Girl girl = (Girl) constructor.newInstance("liuyan", Integer.valueOf(22));
            System.out.println("----> girl=" + girl);
    
        } catch (Exception e) {
    
        }
    }

    获取类所有的构造器,这个没啥可说的。那么怎么获取指定的构造器呢?一个类可能有多个重载的构造方法,它们的方法名都是一样的;所以此时需要从构造器的输入参数入手,比如:

    clazz.getDeclaredConstructor(String.class, Integer.class);

    就可以获取到如下的构造方法:

    private Girl(String name,Integer age){ }

    但是请注意该构造方法是private的,所以需要将该方法的accessible标志设置为true 表示取消语言访问检查。即:

    constructor.setAccessible(true);

    在获取构造方法后即可利用newInstance()创建对象,即:

    Girl girl = (Girl) constructor.newInstance(“liuyan”,Integer.valueOf(22));

  2. 利用反射获取字段

     /**
     * 利用反射操作类的字段
     * 1 getFields()获取类的字段,但是获取不到私有字段
     * 2 getDeclaredFields()获取类的所有字段
     * 3 获取指定的字段及其type
     * 4 获取指定对象的某个字段值
     * 5 设置指定对象的某个字段值
     */
    private void testGetField() {
        try {
            Class clazz = Class.forName("cc.testreflection.Girl");
            Field[] fields = clazz.getFields();
            for (Field field : fields) {
                System.out.println("----> field=" + field);
            }
    
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                System.out.println("----> declaredField=" + declaredField);
            }
    
            //获取指定的字段及其type
            Field field = clazz.getDeclaredField("name");
            Class type = field.getType();
            System.out.println("----> field=" + field + ",type=" + type);
    
            //获取指定对象的某个字段值
            Girl girl = new Girl("lucy", 100, 100, 100, 18);
            field.setAccessible(true);
            String name = (String) field.get(girl);
            System.out.println("----> name=" + name);
    
    
            //设置指定对象的某个字段值
            field.setAccessible(true);
            field.set(girl, "hanmeimei");
            System.out.println("----> girl=" + girl);
        } catch (Exception e) {
    
        }
    }

    此处,对于私有字段同样要先取消语言访问检查再进行操作。

  3. 利用反射获取类中的方法

    /**
     * 利用反射获取类的方法
     * 1 getMethods()获取该类及其父类的方法,但不能获取到私有方法
     * 2 getDeclaredMethods()获取该类本身所声明的所有方法
     * 3 反射出类中的指定方法
     */
    private void testGetMethod() {
        try {
            Class clazz = Class.forName("cc.testreflection.Girl");
            Object object = clazz.newInstance();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                System.out.println("----> method=" + method);
            }
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                System.out.println("----> declaredMethod=" + declaredMethod);
            }
    
            Method method = clazz.getDeclaredMethod("getMobile", String.class);
            Class returnType = method.getReturnType();
            System.out.println("----> method="+method+",returnType=" + returnType);
            method.setAccessible(true);
            String mobile = (String) method.invoke(object, "678");
            System.out.println("----> mobile=" + mobile);
        } catch (Exception e) {
    
        }
    }

    此处,请注意getMethods()和getDeclaredMethods()的区别;除此以外,对于私有方法同样要先取消语言访问检查再进行操作。

  4. 利用反射操作数组

     /**
     * 利用反射操作数组
     * 1 利用反射修改数组中的元素
     * 2 利用反射获取数组中的每个元素
     */
    private void testArrayClass() {
        int[] intArray = new int[]{5,7,9};
        Array.set(intArray,0,9527);
        Class clazz = intArray.getClass();
        if (clazz.isArray()) {
            int length = Array.getLength(intArray);
            for (int i = 0; i < length; i++) {
                Object object = Array.get(intArray, i);
                String className=object.getClass().getName();
                System.out.println("----> object=" + object+",className="+className);
            }
        }
    }

    相对于类而言,数组要简单得多;所以在利用反射操作数组时较为容易。

  5. 利用反射获取泛型的参数类型
    在许多框架中有这样的需求:根据不同的泛型参数响应不同的操作。
    一说到泛型参数类型,可能大家立马就想到了刚才说的泛型擦除,比如ArrayList<String>在编译后就变成了ArrayList,它原本的泛型被”擦除”了。但是我们有时确实需要知道泛型的参数类型,又该怎么来实现呢?按照刚才的那些思路恐怕是走不通了,得另辟蹊径了。

    /**
     * 利用反射获取泛型的参数类型
     * 
     * 原创作者:
     * 谷哥的小弟
     *
     * 博客地址:
     * http://blog.csdn.net/lfdfhl
     */
    public void getGenericHelper(HashMap<String, Integer> hashMap) {
    
    }
    
    private Class getGenericType() {
        try {
            HashMap<String, Integer> hashMap = new HashMap<String, Integer>();
            Method method = getClass().getDeclaredMethod("getGenericHelper",HashMap.class);
            Type[] genericParameterTypes = method.getGenericParameterTypes();
            if (null == genericParameterTypes || genericParameterTypes.length < 1) {
                return null;
            }
            ParameterizedType parameterizedType=(ParameterizedType)genericParameterTypes[0];
            Type rawType = parameterizedType.getRawType();
            System.out.println("----> rawType=" + rawType);
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            if (actualTypeArguments==genericParameterTypes || actualTypeArguments.length<1) {
                return null;
            }
    
            for (int i = 0; i < actualTypeArguments.length; i++) {
                Type type = actualTypeArguments[i];
                System.out.println("----> type=" + type);
            }
        } catch (Exception e) {
    
        }
        return null;
    }

    主要步骤如下

    第一步:
    定义getGenericHelper()方法其输入参数为带泛型的参数,比如ArrayList<String,Integer>

    第二步:
    利用反射获取到该getGenericHelper()方法,即:

    Method method=getClass().getDeclaredMethod(“getGenericHelper”,HashMap.class);

    第三步:
    获取到该方法的带泛型的输入参数,即:

    Type[] genericParameterTypes = method.getGenericParameterTypes();

    注意getGenericParameterTypes()方法返回的是一个数组,因为方法可能有多个参数,但是依据我们的需求这个数组中是仅有一个元素的

    第四步:
    获取到该带泛型参数的所有泛型的类型,即:

    Type[] actualTypeArguments =parameterizedType.getActualTypeArguments();

    因为一个参数的泛型可能有多个,所以getActualTypeArguments()的返值是一个数组。
    比如,此处的ArrayList<String,Integer>该参数就有两个泛型

    第五步:
    获取每个泛型的类型,即:

    Type type = actualTypeArguments[i];


Type

在刚才的示例中,我们看到了一个不太熟悉的东西——Type

嗯哼,我们一起来瞅瞅这个玩意儿

Type是Java编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。

这是官方文档对于Type的解释,这段描述是什么意思呢?
在JDK1.5之前还没有泛型时,我们的数据都是这样的:int,double,String,User,Girl…….在泛型出来之后又多了这些东西:<T>,HashMap<String,Number>,List<String>[]…..这些新出来的数据类型又该用什么去表示呢?为了兼容和统一以前的JDK版本,这才出现了Type。

Type一共有四个子接口:
TypeVariable,ParameterizedType,GenericArrayType,WildcardType

现在这四个子接口都洗干净整整齐齐地摆在这里了,我们来挨个解读。

  • TypeVariable

    TypeVariable称为类型变量,比如<T>、<C extends String>中的变量T、C都是TypeVariable

    /**
     * 原创作者:
     * 谷哥的小弟
     *
     * 博客地址:
     * http://blog.csdn.net/lfdfhl
     */
    private HashMap<K, Integer> generichashMap = null;
    public  void testTypeVariable() throws Exception{
        Class clazz=GenericTest.class;
        Field field=clazz.getDeclaredField("generichashMap");
        Type genericType = field.getGenericType();
        if(genericType instanceof ParameterizedType){
            ParameterizedType parameterizedType= (ParameterizedType) genericType;
            Type[] types = parameterizedType.getActualTypeArguments();
            for (int i = 0; i < types.length; i++) {
                Type type=types[i];
                if( type instanceof TypeVariable){
                    System.out.println("----> type="+type +"是TypeVariable");
                }else{
                    System.out.println("----> type="+type+"不是TypeVariable");
                }
            }
        }
    }

    此处的HashMap<K, Integer> generichashMap一共涉及到两个泛型:K和Integer;其中K是TypeVariable,但Integer不是

  • ParameterizedType

    ParameterizedType称为参数化类型,比如HashMap<K, Integer>

    /**
     * 原创作者:
     * 谷哥的小弟
     *
     * 博客地址:
     * http://blog.csdn.net/lfdfhl
     */
    private HashMap<K, Integer> hashMap = null;
    public  void testParameterizedType() throws Exception{
        Class clazz=GenericTest.class;
        Field field=clazz.getDeclaredField("hashMap");
        Type genericType =field.getGenericType();
        if(genericType instanceof ParameterizedType){
            ParameterizedType parameterizedType= (ParameterizedType) genericType;
            Type rawType = parameterizedType.getRawType();
            Type ownerType = parameterizedType.getOwnerType();
            System.out.println("---->rawType="+rawType+",ownerType="+ownerType);
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            for (int i = 0; i < actualTypeArguments.length; i++) {
                Type type=actualTypeArguments[i];
                System.out.println("---->i="+i+",type="+type);
            }
        }
    }

    由于HashMap<K, Integer>是参数化类型,所以可用getRawType()获取其原始的类型,即:

    Type rawType = parameterizedType.getRawType();

    HashMap<K, Integer>的原始类型就是class java.util.HashMap
    除此以外,还可以获得参数的实际类型,即:

    Type[] actualTypeArguments =parameterizedType.getActualTypeArguments();

    其中,第一个参数为K,第二个参数为class java.lang.Integer

  • GenericArrayType

    GenericArrayType称为数组类型,比如T[] tArray,List<String>[] listArray这些带有泛型的数组称为GenericArrayType

    class Test<T> {
        public void test(List<String>[] stringList) {
    
        }
    }
    
    public  void testGenericArrayType() throws Exception{
        Class clazz=Test.class;
        Method method = clazz.getDeclaredMethods()[0];
        Type[] types = method.getGenericParameterTypes();
        for (Type type : types) {
            boolean isGenericArrayType=(type instanceof GenericArrayType);
            System.out.println("------> isGenericArrayType="+isGenericArrayType);
        }
    }

    那么String[] stringArray,int[] intArray是GenericArrayType么?请自行验证,将其传入此处的test( ) 方法即可。

  • WildcardType

    WildcardType称为通配符类型,比如:? extends Integer 和 ? super String都是WildcardType

    /**
     * 原创作者:
     * 谷哥的小弟
     *
     * 博客地址:
     * http://blog.csdn.net/lfdfhl
     */
    private List<? extends Number> numberList;
    private List<? super String> stringList;
    public  void testWildcardType() throws Exception{
        Class clazz=GenericTest.class;
        Field numberField = clazz.getDeclaredField("numberList");
        Field stringField = clazz.getDeclaredField("stringList");
        ParameterizedType numberParameterizedType=(ParameterizedType)numberField.getGenericType();
        ParameterizedType stringParameterizedType=(ParameterizedType)stringField.getGenericType();
        Type [] numberActualTypeArguments=numberParameterizedType.getActualTypeArguments();
        WildcardType numberWildcardType = (WildcardType) numberActualTypeArguments[0];
        Type [] StringActualTypeArguments=stringParameterizedType.getActualTypeArguments();
        WildcardType stringWildcardType = (WildcardType) StringActualTypeArguments[0];
        System.out.println("----> numberWildcardType="+numberWildcardType);
        System.out.println("----> stringWildcardType="+stringWildcardType);
        Type numberUpperBound=numberWildcardType.getUpperBounds()[0];
        Type stringLowerBound=stringWildcardType.getLowerBounds()[0];
        System.out.println("----> numberUpperBound="+numberUpperBound);
        System.out.println("----> stringLowerBound="+stringLowerBound);
    }

    我们知道在泛型中extends 表示上限,super表示下限。比如此处:
    ? extends Number 的上限是class java.lang.Number
    ? super String 的下限是class java.lang.String
    所以,可利用getUpperBounds()和getLowerBounds()获取WildcardType的上限或下限

Type除了这四个子接口,它还有一个实现类Class
Class所表示的是原始类型,或者说是泛型出现之前的类型。比如String,User,Girl,Integer等等。


注解

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

Annotation作为元数据可以被添加到Java源代码类、方法、变量、参数、包。虽然源码中添加了注释,但是Annotation不会直接影响程序的执行,无论增加或者删除Annotation,原代码的执行都始终如一。在中文里,常将Annotation翻译为“注解”,在以下的论述中也采用该译法。

  • 标准注解

    在Java的JDK中内置了一些系统自带的注解,这些注解也常称为标准注解,常见的有:@Override, @Deprecated, @SuppressWarnings

    @Override

    @Override作用于方法,表示被标注的方法重载了父类的方法。若该重载的方法写错了方法名那么在编译期就会有出现警告

    这里写图片描述

    看到了吧,在子类Customer中用@Override标记了方法setId()表示这个方法是重载自父类;假若把方法名错写成了setIdd()编译器就会报错了。

    @Deprecated

    当一个类型或者类型成员使用@Deprecated标记则表示建议不再使用该元素。

    这里写图片描述

    这里写图片描述

    在User类中,将setId()方法标记为@Deprecated,当我们准备调用该方法时编译器就会提示该方法已经过时,建议换用其他方法

    @SuppressWarnings

    @SuppressWarnings翻译成中文就是抑制警告,它被用于关闭编译器对类、方法、成员变量、变量初始化的警告。

    这里写图片描述

    定义了一个变量testSuppressWarnings,但是该变量从未被使用,所以提示了一个警告:private field ‘testSuppressWarnings’ is never used。为了抑制该警告,我们给testSuppressWarnings添加一个注解:

    @SuppressWarnings(“unused”)

    这里写图片描述

    该注解就表示不再警告变量未被使用。
    除了该处使用的unused以外@SuppressWarnings还有许多其他参数,比如:
    serial:可序列化的类上缺少serialVersionUID定义的警告
    finally:finally语句不能正常完成的警告
    deprecation:使用了过时的类型或者类型成员方法时的警告
    unchecked:执行了未检查的转换的警告
    all:所有情况的警告。

  • 自定义注解
    除了使用系统提供的注解,我们当然也可以自定义注解。
    自定义注解和创建接口非常相似,但注解需要以@开头。方法体中的每一个方法实际上是声明了一个属性,其中方法名是属性的名称,方法的返回值类型就是属性的类型(返回值类型只能是基本类型、String、enum、Class)。当然也可以通过default来声明属性的默认值。
    比如:

    public @interface TestAnnotation {
        public String name();
        public int age() default 20;
    }

    如上代码就定义了一个自定义的注解TestAnnotation,它有两个属性name和age,并且age的默认值为20
    有时注解中只需要一个属性,为简便起见可将该属性命名为value,比如:

    @Retention(RetentionPolicy.RUNTIME)
    public @interface ValueAnnotation {
       String value();
    }

    如上代码就定义了一个自定义的注解ValueAnnotation,并且该注解只有一个属性value。其实,@SuppressWarnings也与此类似。
    在定义完成之后,现再将这个两个自定义的注解作用于类

    @TestAnnotation(name="lucy")
    @ValueAnnotation("lucy9527")
    public class Beauty {
    
    }

    在这段代码中为类Beauty添加了两个注解。在使用注解TestAnnotation时采用键值对的形式为属性name赋值,在使用注解ValueAnnotation时由于其只有一个属性value,所以可将value = “lucy9527”简写成”lucy9527”。

    好了,在一个类上使用了自定义注解,现再瞅瞅怎么样把这些注解的属性值提取出来

    /**
     * 
     * 提取自定义注解的属性
     * 
     * 原创作者:
     * 谷哥的小弟
     *
     * 博客地址:
     * http://blog.csdn.net/lfdfhl
     */
    private void getAnnotationValue(){
        TestAnnotation annotation=null;
        Class clazz=Beauty.class;
        boolean isPresent= clazz.isAnnotationPresent(TestAnnotation.class);
        if(isPresent){
            annotation = (TestAnnotation)clazz.getAnnotation(TestAnnotation.class);
            String name=annotation.name();
            int age=annotation.age();
            System.out.println("----> annotation=" + annotation);
            System.out.println("----> name=" + name+",age="+age);
        }
     }
    
    /**
     * 提取自定义注解的属性
     */
    private void getAnnotationDefaultValue(){
        ValueAnnotation annotation=null;
        Class clazz=Beauty.class;
        boolean isPresent= clazz.isAnnotationPresent(TestAnnotation.class);
        isPresent= clazz.isAnnotationPresent(ValueAnnotation.class);
        if(isPresent){
            annotation= (ValueAnnotation) clazz.getAnnotation(ValueAnnotation.class);
            String value=annotation.value();
            System.out.println("----> defaultValue=" + value);
        }
    }

    主要步骤如下

    第一步:
    先判断该注解是否存在,即:

    clazz.isAnnotationPresent(TestAnnotation.class);

    第二步:
    获取注解,即:

    annotation =(TestAnnotation)clazz.getAnnotation(TestAnnotation.class);

    第三步:
    获取注解的属性值,即:

    String name=annotation.name();
    int age=annotation.age();

    输出结果:

    —-> annotation=@cc.testreflection.TestAnnotation(age=20, name=lucy)
    —-> name=lucy,age=20
    —-> defaultValue=lucy9527

  • 元注解

    刚才通过三个系统的标准注解和自定义注解我们看到:注解是用来标记或者说明类,方法,变量的。
    与此类似,Java还提供了元注解用于标记注解。
    常见的元注解有:@Target、@Retention、@Documented、@Inherited

    @Target

    @Target用于确定Annotation所修饰的对象范围。我们知道Annotation可用于packages、types(类、接口、枚举)、类型成员(方法、成员变量、枚举值)、方法参数等等。所以,可用@Target表示Annotation修饰的目标。
    比如@Override的源码:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    
    }

    在此使用了元注解@Target表示@Override只能用于修饰方法。
    除ElementType.METHOD以外,@Target还可以使用其他的值用于表示注解的修饰对象,比如:
    CONSTRUCTOR:用于描述构造器
    FIELD:用于描述类成员变量
    LOCAL_VARIABLE:用于描述局部变量
    PACKAGE:用于描述包
    PARAMETER:用于描述参数

    @Retention

    @Retention定义了Annotation的有效范围,类似于Android中常提到的生命周期。Java文件从生产到执行,要经过三个主要的阶段:java源文件,Class文件,JVM运行。与此类似,有的Annotation仅出现在源代码中而被编译器丢弃,而另一些却被编译在Class文件中;有的编译在Class文件中的Annotation在运行时会被虚拟机忽略,而另一些在运行时被读取读取。
    所以,在@Retention中使用RetentionPoicy标明注解会存留到哪个阶段,RetentionPoicy有三个值:
    SOURCE:在源文件中有效(即仅在源文件保留)
    CLASS:在Class文件中有效(即Class保留)
    RUNTIME:在运行时有效(即保留至运行时)

    比如@Deprecated的源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Deprecated {
    
    }

    在这段源码中用@Retention(RetentionPolicy.RUNTIME)标明该注解会保留至运行时。

    @Documented

    @Documented表示在生成javadoc文档时将该Annotation也写入到帮助文档。
    比如有一个注解:

    @Target(ElementType.METHOD)
    @Documented  
    public @interface TestDocumented {   
         String name();   
    } 

    在此使用@Target(ElementType.METHOD)表示这个注解是用来修饰方法的,并且为该注解添加了元注解@Documented

    public class CommonClass {   
    /**  
     * This is a java method  
     */  
    @TestDocumented(name = "google")   
    public void testMethod() {   
    
     }  
    
    } 

    然后在一个方法上使用注解@DocumentTest

    @TestDocumented(name = “google”)
    public void testMethod()
    This is a java method

    最后生成类似如上所示的帮助文档

    @Inherited

    @Inherited用于指示注释类型被自动继承。

    请参见如下完整示例

    第一步:
    定义一个注解,并在该注解上使用了元注解@Inherited

    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface InheritedAnnotation {
    String value( );
    }

    第二步:
    定义一个父类,并且在该类上使用了自定义注解@InheritedAnnotation

    @InheritedAnnotation("@InheritedAnnotation on class of ParentClass")
    public class ParentClass {
    public void print() {
        System.out.println("----> This is parent print( )");
    }
    }

    第三步:
    定义一个子类继承自父类。

    public class ChildClass extends ParentClass {
    
    }

    第四步:
    测试子类是否继承父类中类上的注解

    public  void testInheritedOnClass(){
        InheritedAnnotation annotation=null;
        Class clazz = ChildClass.class;
        Class annotationClass=InheritedAnnotation.class;
        boolean isPresent=clazz.isAnnotationPresent(annotationClass);
        if (isPresent) {
            annotation = (InheritedAnnotation) clazz.getAnnotation(annotationClass);
            String value = annotation.value();
            System.out.println("----> value=" + value);
        }
    }

    输出结果为:

    —-> value=@InheritedAnnotation on class of ParentClass


后语

这篇文章有点长,内容不少,所以能看到这里的人可能不多了。

有人戏谑地说:26个英语字母每个我都认识,但是它们其中几个组合在一起我就不认识了。在开发中,我们在刚面对一个复杂的功能时有些不知所措;但在几经周折完成后再回过头来看时发现:这个功能涉及到的技术实际上不是特别难,只不过它将各方面的技术进行了合理的组合和综合的运用。框架又何尝不是如此呢?正所谓,万丈高楼平地起;再高的楼也是从地下室慢慢地一层层地往上修的。当我们还没有能力去建一幢大厦的时候,就应该在太阳底下多捡几块砖,多箍几捆钢筋,暗暗地积蓄。假以时日,这些材料终将成为大厦的基石。

好了,不多说了,搬砖去;工头又在催我了

作者:lfdfhl 发表于2016/9/30 0:34:14 原文链接
阅读:677 评论:6 查看评论

Android开发学习之路--基于vitamio的视频播放器(一)

$
0
0

  之前也试过vitamio这个库,后来不知道被什么事情给耽搁了,就没继续下去。近来觉得视频还是需要学习一下的,谁让直播那么火呢,就想着写一个简单的视频播放的app先吧。好了那就开始吧,暂时取名为JPlayer,后续慢慢改进,源码也在github上(https://github.com/imchenjianneng/JPlayer),后续不断更新吧。
  首先新建工程JPlayer,然后新建个主界面吧:

<layout>
    <data class="MainBinding"></data>
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/coordinator_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.AppBarLayout
            android:id="@+id/appbar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true">

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

                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    android:background="@color/colorPrimary"
                    android:fitsSystemWindows="true"
                    android:minHeight="?attr/actionBarSize"
                    app:contentInsetLeft="0dp"
                    app:contentInsetStart="0dp">
                    <!--app:layout_scrollFlags="scroll|enterAlways"-->

                    <RelativeLayout
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:paddingLeft="12dp"
                        android:paddingRight="12dp">

                        <RadioGroup
                            android:id="@+id/rg_tab"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:layout_centerVertical="true"
                            android:layout_marginLeft="10dp"
                            android:layout_marginRight="10dp"
                            android:orientation="horizontal">

                            <RadioButton
                                android:id="@+id/rb_local"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:layout_weight="1"
                                android:button="@null"
                                android:gravity="center"
                                android:text="本地"
                                android:textColor="@drawable/top_tab_text_color"
                                android:textSize="16sp" />

                            <RadioButton
                                android:id="@+id/rb_intenet"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:layout_weight="1"
                                android:button="@null"
                                android:gravity="center"
                                android:text="网络"
                                android:textColor="@drawable/top_tab_text_color"
                                android:textSize="16sp" />
                        </RadioGroup>
                    </RelativeLayout>
                </android.support.v7.widget.Toolbar>
            </RelativeLayout>

        </android.support.design.widget.AppBarLayout>

        <android.support.v4.view.ViewPager
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    </android.support.design.widget.CoordinatorLayout>
</layout>

  这里简单简绍下,具体可以看源码,界面由是两个radiobutton,一个viewpager组成,比较简陋,后续再改吧,我们先实现功能要紧。真实项目一般ui都会提供好界面的。
  既然界面搭建好了,那么继续开始代码实现吧:

public class MainActivity extends BaseActivity implements ViewPager.OnPageChangeListener {

    private MainBinding binding;

    private List<Integer> bts = Arrays.asList(
            R.id.rb_local,
            R.id.rb_intenet);

    private float normalSize, normalSelected;

    @Override
    protected void initParams() {
        binding = DataBindingUtil.setContentView(getActivity(), R.layout.activity_main);
    }

    @Override
    protected void initViews() {
        super.initViews();

        normalSize = getResources().getDimension(R.dimen.normal_size);
        normalSelected = getResources().getDimension(R.dimen.selected_size);

        binding.viewpager.setOffscreenPageLimit(2);
        binding.viewpager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager()));
        initTabPagerListener();
        binding.rgTab.check(bts.get(0));
    }

    private void initTabPagerListener() {

        binding.viewpager.addOnPageChangeListener(this);
        binding.rgTab.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                int i = bts.indexOf(checkedId);
                if (i != -1) {
                    selectTitle(checkedId);
                    binding.viewpager.setCurrentItem(i);
                }
            }
        });
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        selectTitle(bts.get(position));

        binding.rgTab.check(bts.get(position));
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }

    private void selectTitle(int selectResId) {
        binding.rbLocal.setTextSize(TypedValue.COMPLEX_UNIT_PX, normalSize);
        binding.rbIntenet.setTextSize(TypedValue.COMPLEX_UNIT_PX, normalSize);
        ((TextView)findViewById(selectResId)).setTextSize(TypedValue.COMPLEX_UNIT_PX,
                normalSelected);
    }

    public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
        LocalVideoFragment localVideoFragment = new LocalVideoFragment();
        InternetVideoFragment internetVideoFragment = new InternetVideoFragment();
        private List<? extends Fragment> list = Arrays.asList(
                localVideoFragment,
                internetVideoFragment);

        public MyFragmentPagerAdapter(FragmentManager fm) {
            super(fm);
        }

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

        @Override
        public Fragment getItem(int position) {
            return list.get(position);
        }
    }
}

  代码其实比较简单,主要实现了viewpager,然后LocalVideoFragment和InternetVideoFragment两个fragment,第一个是本地的界面,第二个是网络(这里暂时不实现),好了,主要的代码都在LocalVideoFragment里了。那么就接着看这个Frament的代码了。

  既然要播放视频,首先是需要实现扫描所有视频文件。先看这一段代码吧:

private class ScanVideoTask extends AsyncTask<Void, File, Void> {

        @Override
        protected Void doInBackground(Void... params) {
            eachAllMedias(Environment.getExternalStorageDirectory());
            return null;
        }

        @Override
        protected void onProgressUpdate(File... values) {
            LocalSource localSource = new LocalSource();
            localSource.setName(values[0].getName());
            localSource.setUrl(values[0].getAbsolutePath());
            localSource.setBitmap(getVideoThumbnail(localSource, localSource.getUrl()));
            localSource.setBitmap(getVideoThumbnail(localSource.getUrl()));
            Log.d("LocalVideoFragment", "hhh"+localSource.getName()+":"+localSource.getUrl());
            fileAdapter.appendItem(localSource);
            fileAdapter.notifyDataSetChanged();

        }

        /** 遍历所有文件夹,查找出视频文件 */
        public void eachAllMedias(File f) {
            if (f != null && f.exists() && f.isDirectory()) {
                File[] files = f.listFiles();
                if (files != null) {
                    for (File file : f.listFiles()) {
                        if (file.isDirectory()) {
                            eachAllMedias(file);
                        } else if (file.exists() && file.canRead() && isVideo(file)) {
                            publishProgress(file);
                        }
                    }
                }
            }
        }
    }

  扫描需要花费比较长的时间,所以这里放到一个异步task中去处理,深度优先的搜索所有的文件,扫描到了文件判断是否为视频文件,若是的话,那就直接加到recycleview中去。对于是否是视频文件,这里仅仅是判断后缀名,更合理的方式应该是解码一部分,然后判断头文件的信息的,之类就偷懒了一下,具体可以参考源码。
  既然已经扫描出了文件,那么接下来就需要这个播放列表了,因为我们要显示视频的缩略图,视频的文件名,以及播放时长和大小。用到了MediaMetadataRetriever类,通过extractMetadata方法来获取视频的相关信息,看下代码吧:

public Bitmap getVideoThumbnail(LocalSource localSource, String filePath) {
        Bitmap bitmap = null;
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        try {
            retriever.setDataSource(filePath);
            bitmap = retriever.getFrameAtTime(0);
            localSource.setAuthor(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR));
            localSource.setDuration(Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)));
            localSource.setDate(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
            localSource.setBitrate(Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE)));

        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        }
        return bitmap;
    }

  这通过retriever.getFrameAtTime来获取一帧的图像bitmap用来当缩略图,然后通过MediaMetadataRetriever.METADATA_KEY_DURATION获取播放时长。通过MediaMetadataRetriever.METADATA_KEY_BITRATE获取比特率,计算就可以得到所需要的文件大小。
  要上班去了,接下去的话就是重点了,怎么使用vitamio,下次继续了,想直接看代码的话可以直接github看起来,代码写得比较挫,还没有整理过,为了实现功能而写。

作者:eastmoon502136 发表于2016/9/30 8:32:24 原文链接
阅读:72 评论:0 查看评论

OkHttp3源码详解(二)

$
0
0

     上一篇文章已经对Request相关的类进行了详细的学习,后面我发现Okhttp这种底层框架一个类一个类看没什么用,所以这篇文章开始就只对Okhttp的整体流程作一个学习

   1.简单的get请求

  

   Request request = new Request.Builder()
                .url("https://www.baidu.com/")
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        });

  我们来看一下RealCall中的enqueue的逻辑看一下,
  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

这个方法调用Dispatcher的enqueue方法

 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //正在运行的异步任务队列数量小于最大请求数,线程池执行该任务
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      //把该方法放到异步任务准备队列中
      readyAsyncCalls.add(call);
    }
  }

线程池执行该异步任务,会执行异步任务的run方法,run方法中调用了execute抽象方法,我们来看一下这个方法的实现

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        //由getResponseWithInterceptorChain()来执行网络请求,得到response
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          //失败后回调Callback的onFailure方法
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          //成功后回调CallBack的onResponse方法
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        //最后调用Dispatcher的finish方法
        client.dispatcher().finished(this);
      }
    }
  

我们先来看finish方法,后面再去看getResponseWithInterceptorChain(),来解决简单的再去看复杂的

  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      //promoteCalls()该方法会去从异步准备运行的队列中去取任务去执行
      if (promoteCalls) promoteCalls();
      //得到异步和同步任务正在执行数
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }
    //如果设置了该线程,执行回调线程
    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

走完一个流程后我们来看一下getResponseWithInterceptorChain()方法的实现细节,这个方法在call.execute()也调用到了,OkHttp中真正发出网络请求,解析返回结果的,就是这个方法

private Response getResponseWithInterceptorChain() throws IOException {

    //构建全栈拦截器
    List interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());//自定义拦截器
    interceptors.add(retryAndFollowUpInterceptor);//重试拦截器
    interceptors.add(new BridgeInterceptor(client.cookieJar()));//桥接拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));//缓存拦截器
    interceptors.add(new ConnectInterceptor(client));//连接拦截器
    if (!retryAndFollowUpInterceptor.isForWebSocket()) {
      interceptors.addAll(client.networkInterceptors());//用户预定义的网络拦截器
    }
    interceptors.add(new CallServerInterceptor(
        retryAndFollowUpInterceptor.isForWebSocket()));//调用服务拦截器

    //内部通过责任链模式来使用拦截器
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);

    return chain.proceed(originalRequest);//获取Response
  }

这么多的拦截器,看着肯定很懵逼吧,是的我就是这样。我们就一个一个的来看吧


RetryAndFollowUpInterceptor

     重试与重定向拦截器,用来实现重试和重定向功能,内部通过while(true)死循环来进行重试获取Response(有重试上限,超过会抛出异常)。followUpRequest主要用来根据响应码来判断属于哪种行为触发的重试和重定向(比如未授权,超时,重定向等),然后构建响应的Request进行下一次请求。当然,如果没有触发重新请求就会直接返回Response。

 

BridgeInterceptor

 桥接拦截器,用于完善请求头,比如Content-Type、Content-Length、Host、Connection、Accept-Encoding、User-Agent等等,这些请求头不用用户一一设置,如果用户没有设置该库会检查并自动完善。此外,这里会进行加载和回调cookie。


CacheInterceptor

缓存拦截器,首先根据Request中获取缓存的Response,然后根据用于设置的缓存策略来进一步判断缓存的Response是否可用以及是否发送网络请求(CacheControl.FORCE_CACHE因为不会发送网络请求,所以networkRequest一定为空)。如果从网络中读取,此时再次根据缓存策略来决定是否缓存响应。


ConnectInterceptor

 连接拦截器,用于打开一个连接到远程服务器。说白了就是通过StreamAllocation获取HttpStream和RealConnection对象,以便后续读写。

 

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpStream, connection);
  }
   实际上建立连接就是创建了一个HttpStream 对象,它将在后面的步骤中被使用,那它又是何方神圣呢?它是对 HTTP 协议操作

的抽象,有两个实现:Http2xStreamHttp1xStream,顾名思义,它们分别对应 HTTP/1.1 和 HTTP/2 版本的实现。

 在Http1xStram中,它利用Okio对Socket的读写操作进行封装,而创建HttpStream 对象的过程涉及到 StreamAllocationRealConnection,代码较长,这里就不展开,这个过程概括来说,就是找到一个可用的RealConnection,再利用 RealConnection 的输入输出(BufferedSourceBufferedSink)创建HttpStream 对象,供后续步骤使用。

  

 CallServerInterceptor

调用服务拦截器,拦截链中的最后一个拦截器,通过网络与调用服务器。通过HttpStream依次次进行写请求头、请求头(可选)、读响应头、读响应体。

@Override public Response intercept(Chain chain) throws IOException {
    HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
    StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
    Request request = chain.request();
    
    long sentRequestMillis = System.currentTimeMillis();
    //向服务器发生request header
    httpStream.writeRequestHeaders(request);
    
    //如果有 request body
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
      BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
      //将请求实体内容通过Okio发送到服务器
      request.body().writeTo(bufferedRequestBody);
      bufferedRequestBody.close();
    }
    
    httpStream.finishRequest();   
    //读取 response header,先构造一个 Response 对象
    Response response = httpStream.readResponseHeaders()
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();
    //如果有 response body,就在response的基础上加上body构造一个新的 Response 对象       
    if (!forWebSocket || response.code() != 101) {
      response = response.newBuilder()
          .body(httpStream.openResponseBody(response))
          .build();
    }
    
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    int code = response.code();
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }
    return response;
  }
 

  这里我们可以看到,核心工作都由HttpStream对象完成,而HttpStream实际上利用的是 Okio,而 Okio 实际上还是用的Socket,所以没什么神秘的,只不过一层套一层,层数有点多。

 其实 Interceptor 的设计也是一种分层的思想,每个 Interceptor 就是一层。为什么要套这么多层呢?分层的思想在 TCP/IP 协议中就体现得淋漓尽致,分层简化了每一层的逻辑,每层只需要关注自己的责任(单一原则思想也在此体现),而各层之间通过约定的接口/协议进行合作(面向接口编程思想),共同完成复杂的任务。




OkHttp 还有很多细节部分没有在本文展开,例如 HTTP2/HTTPS 的支持等,但建立一个清晰的概览非常重要。对整体有了清晰认识之后,细节部分如有需要,再单独深入将更加容易。

在文章最后我们再来回顾一下完整的流程图:




   


 

  

 



 

作者:qq_31694651 发表于2016/9/30 9:20:03 原文链接
阅读:41 评论:0 查看评论

ERROR: In MenuView, unable to find attribute android:preserveIconSpacing

$
0
0
eclipse  sdk从低版本切换到高版本sdk的时候   v7包会包这个错ERROR: In <declare-styleable> MenuView, unable to find attribute android:preserveIconSpacing

   问题解决:

     点击V7包找到values文件夹   打开attrs.xml      ctrl+f   查找 MenuView     将preserveIconSpacing注释掉或者删掉    clean项目

ok 完成。

作者:qq_32059827 发表于2016/9/30 9:45:14 原文链接
阅读:45 评论:0 查看评论

Dialog对话框以及自定义Dialog

$
0
0

Android中的对话框形式大致可分为五种:分别是一般对话框形式,列表对话框形式,单选按钮对话框,多选按钮对话框,自定义对话框

在实际开发中,用系统的对话框会很少,因为太丑了,美工不愿意,多是使用自定义对话框。当然学会系统的,自定义就简单了,所以我们先来学习系统的,后面在写一篇自定义对话框。

一般对话框:

不多说先上图:

代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void dialog1(){  
  2.         AlertDialog.Builder builder=new AlertDialog.Builder(this);  //先得到构造器  
  3.         builder.setTitle("提示"); //设置标题  
  4.         builder.setMessage("是否确认退出?"); //设置内容  
  5.         builder.setIcon(R.mipmap.ic_launcher);//设置图标,图片id即可  
  6.         builder.setPositiveButton("确定"new DialogInterface.OnClickListener() { //设置确定按钮  
  7.             @Override  
  8.             public void onClick(DialogInterface dialog, int which) {  
  9.                 dialog.dismiss(); //关闭dialog  
  10.                 Toast.makeText(MainActivity.this"确认" + which, Toast.LENGTH_SHORT).show();  
  11.             }  
  12.         });  
  13.         builder.setNegativeButton("取消"new DialogInterface.OnClickListener() { //设置取消按钮  
  14.             @Override  
  15.             public void onClick(DialogInterface dialog, int which) {  
  16.                 dialog.dismiss();  
  17.                 Toast.makeText(MainActivity.this"取消" + which, Toast.LENGTH_SHORT).show();  
  18.             }  
  19.         });  
  20.   
  21.         builder.setNeutralButton("忽略"new DialogInterface.OnClickListener() {//设置忽略按钮  
  22.             @Override  
  23.             public void onClick(DialogInterface dialog, int which) {  
  24.                 dialog.dismiss();  
  25.                 Toast.makeText(MainActivity.this"忽略" + which, Toast.LENGTH_SHORT).show();  
  26.             }  
  27.         });  
  28.         //参数都设置完成了,创建并显示出来  
  29.         builder.create().show();  
  30.     }  
说明:代码上注释已经比较全了,相信大家都能看得懂!dialog可以设置三个选择按钮,设置时指定按钮响应事件。

是不是觉得三个按钮写了三遍响应事件,很繁琐呢?注意:onClick的参数中有一个which,这个是什么意思呢?这个which实际上代表的是一个唯一的int型数值。像上面的setPositiveButton中的which代表的是-1,setNegativeButton中的which代表的是-3,setNeutralButton中的which代表的是-2. 到了这里相信大家已经想到怎么简洁的写法了,只要写一个响应事件,用which参数去区分是那个按钮就可以了!  


看简洁的代码2:和上面的效果是一样的!

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void dialog1_1(){  
  2.        //先new出一个监听器,设置好监听  
  3.        DialogInterface.OnClickListener dialogOnclicListener=new DialogInterface.OnClickListener(){  
  4.   
  5.            @Override  
  6.            public void onClick(DialogInterface dialog, int which) {  
  7.                switch(which){  
  8.                    case Dialog.BUTTON_POSITIVE:  
  9.                        Toast.makeText(MainActivity.this"确认" + which, Toast.LENGTH_SHORT).show();  
  10.                        break;  
  11.                    case Dialog.BUTTON_NEGATIVE:  
  12.                        Toast.makeText(MainActivity.this"取消" + which, Toast.LENGTH_SHORT).show();  
  13.                        break;  
  14.                    case Dialog.BUTTON_NEUTRAL:  
  15.                        Toast.makeText(MainActivity.this"忽略" + which, Toast.LENGTH_SHORT).show();  
  16.                        break;  
  17.                }  
  18.            }  
  19.        };  
  20.        //dialog参数设置  
  21.        AlertDialog.Builder builder=new AlertDialog.Builder(this);  //先得到构造器  
  22.        builder.setTitle("提示"); //设置标题  
  23.        builder.setMessage("是否确认退出?"); //设置内容  
  24.        builder.setIcon(R.mipmap.ic_launcher);//设置图标,图片id即可  
  25.        builder.setPositiveButton("确认",dialogOnclicListener);  
  26.        builder.setNegativeButton("取消", dialogOnclicListener);  
  27.        builder.setNeutralButton("忽略", dialogOnclicListener);  
  28.        builder.create().show();  
  29.    }  


列表对话框:



代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void dialog2() {  
  2.         final String items[]={"张三","李四","王五"};  
  3.         //dialog参数设置  
  4.         AlertDialog.Builder builder=new AlertDialog.Builder(this);  //先得到构造器  
  5.         builder.setTitle("提示"); //设置标题  
  6.         //builder.setMessage("是否确认退出?"); //设置内容  
  7.         builder.setIcon(R.mipmap.ic_launcher);//设置图标,图片id即可  
  8.         //设置列表显示,注意设置了列表显示就不要设置builder.setMessage()了,否则列表不起作用。  
  9. builder.setCancelable(false);//设置是否可以被取消(就是点击对话框范围外的地方是否取消对话框)
  10.         builder.setItems(items,new DialogInterface.OnClickListener() {  
  11.             @Override  
  12.             public void onClick(DialogInterface dialog, int which) {  
  13.                 dialog.dismiss();  
  14.                 Toast.makeText(MainActivity.this, items[which], Toast.LENGTH_SHORT).show();  
  15.   
  16.             }  
  17.         });  
  18.         builder.setPositiveButton("确定",new DialogInterface.OnClickListener() {  
  19.             @Override  
  20.             public void onClick(DialogInterface dialog, int which) {  
  21.                 dialog.dismiss();  
  22.                 Toast.makeText(MainActivity.this"确定", Toast.LENGTH_SHORT).show();  
  23.             }  
  24.         });  
  25.         builder.create().show();  
  26.     }  

说明:列表对话框只需设置Items属性即可,注意不能在设置Message属性,否则只会显示Message,不会显示列表。这里的setItems中的Onclick中的which属性是items数组的下标!

单选按钮对话框:


代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void dialog3(){  
  2.         final String items[]={"男","女"};  
  3.         AlertDialog.Builder builder=new AlertDialog.Builder(this);  //先得到构造器  
  4.         builder.setTitle("提示"); //设置标题  
  5.         builder.setIcon(R.mipmap.ic_launcher);//设置图标,图片id即可  
  6.         builder.setSingleChoiceItems(items,0,new DialogInterface.OnClickListener() {  
  7.             @Override  
  8.             public void onClick(DialogInterface dialog, int which) {  
  9.                 //dialog.dismiss();  
  10.                 Toast.makeText(MainActivity.this, items[which], Toast.LENGTH_SHORT).show();  
  11.             }  
  12.         });  
  13.         builder.setPositiveButton("确定",new DialogInterface.OnClickListener() {  
  14.             @Override  
  15.             public void onClick(DialogInterface dialog, int which) {  
  16.                 dialog.dismiss();  
  17.                 Toast.makeText(MainActivity.this"确定", Toast.LENGTH_SHORT).show();  
  18.             }  
  19.         });  
  20.         builder.create().show();  
  21.     }  

说明:其实也没什么好说的,和上面的差不多,只是设置单选用setSingleChoiceItems,注意这里的参数:items是显示的文本,0表示默认选中是第一个,如图所示是默认选中“男”。


多选列表对话框:



代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void dialog4(){  
  2.         final String items[]={"篮球","足球","排球"};  
  3.         final boolean selected[]={true,false,true};  
  4.         AlertDialog.Builder builder=new AlertDialog.Builder(this);  //先得到构造器  
  5.         builder.setTitle("提示"); //设置标题  
  6.         builder.setIcon(R.mipmap.ic_launcher);//设置图标,图片id即可  
  7.         builder.setMultiChoiceItems(items,selected,new DialogInterface.OnMultiChoiceClickListener() {  
  8.             @Override  
  9.             public void onClick(DialogInterface dialog, int which, boolean isChecked) {  
  10.                // dialog.dismiss();  
  11.                 Toast.makeText(MainActivity.this, items[which]+isChecked, Toast.LENGTH_SHORT).show();  
  12.             }  
  13.         });  
  14.         builder.setPositiveButton("确定",new DialogInterface.OnClickListener() {  
  15.             @Override  
  16.             public void onClick(DialogInterface dialog, int which) {  
  17.                 dialog.dismiss();  
  18.                 Toast.makeText(MainActivity.this"确定", Toast.LENGTH_SHORT).show();  
  19.                 //android会自动根据你选择的改变selected数组的值。  
  20.                 for (int i=0;i<selected.length;i++){  
  21.                     Log.e("hongliang",""+selected[i]);  
  22.                 }  
  23.             }  
  24.         });  
  25.         builder.create().show();  
  26.     }  

说明:setMultiChoiceItems中的参数:selected是默认的对应的选中状态。当你选择时,系统会自动帮你把selected中的值做相应改变,所以在确定按钮中可以得到所有的选择状态。其他和单选一样。

总结:要想更好的使用对话框,自定义对话框是少不了的,当然啦,要和activity通信,回调也是少不了的,后面会详细介绍。不多说了,完整代码来一份:

MainActivity.Java

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.example.liang.dialogdemo;  
  2.   
  3. import android.app.AlertDialog;  
  4. import android.app.Dialog;  
  5. import android.content.DialogInterface;  
  6. import android.os.Bundle;  
  7. import android.support.v7.app.ActionBarActivity;  
  8. import android.util.Log;  
  9. import android.view.View;  
  10. import android.widget.Button;  
  11. import android.widget.Toast;  
  12.   
  13.   
  14. public class MainActivity extends ActionBarActivity implements View.OnClickListener{  
  15.     private Button  btn1;  
  16.     private Button  btn2;  
  17.     private Button  btn3;  
  18.     private Button  btn4;  
  19.     @Override  
  20.     protected void onCreate(Bundle savedInstanceState) {  
  21.         super.onCreate(savedInstanceState);  
  22.         setContentView(R.layout.activity_main);  
  23.         btn1= (Button) findViewById(R.id.main_btn1);  
  24.         btn1.setOnClickListener(this);  
  25.         btn2= (Button) findViewById(R.id.main_btn2);  
  26.         btn2.setOnClickListener(this);  
  27.         btn3= (Button) findViewById(R.id.main_btn3);  
  28.         btn3.setOnClickListener(this);  
  29.         btn4= (Button) findViewById(R.id.main_btn4);  
  30.         btn4.setOnClickListener(this);  
  31.   
  32.     }  
  33.     private void dialog1(){  
  34.         AlertDialog.Builder builder=new AlertDialog.Builder(this);  //先得到构造器  
  35.         builder.setTitle("提示"); //设置标题  
  36.         builder.setMessage("是否确认退出?"); //设置内容  
  37.         builder.setIcon(R.mipmap.ic_launcher);//设置图标,图片id即可  
  38.         builder.setPositiveButton("确定"new DialogInterface.OnClickListener() { //设置确定按钮  
  39.             @Override  
  40.             public void onClick(DialogInterface dialog, int which) {  
  41.                 dialog.dismiss(); //关闭dialog  
  42.                 Toast.makeText(MainActivity.this"确认" + which, Toast.LENGTH_SHORT).show();  
  43.             }  
  44.         });  
  45.         builder.setNegativeButton("取消"new DialogInterface.OnClickListener() { //设置取消按钮  
  46.             @Override  
  47.             public void onClick(DialogInterface dialog, int which) {  
  48.                 dialog.dismiss();  
  49.                 Toast.makeText(MainActivity.this"取消" + which, Toast.LENGTH_SHORT).show();  
  50.             }  
  51.         });  
  52.   
  53.         builder.setNeutralButton("忽略"new DialogInterface.OnClickListener() {//设置忽略按钮  
  54.             @Override  
  55.             public void onClick(DialogInterface dialog, int which) {  
  56.                 dialog.dismiss();  
  57.                 Toast.makeText(MainActivity.this"忽略" + which, Toast.LENGTH_SHORT).show();  
  58.             }  
  59.         });  
  60.         //参数都设置完成了,创建并显示出来  
  61.         builder.create().show();  
  62.     }  
  63.     private void dialog1_1(){  
  64.         //先new出一个监听器,设置好监听  
  65.         DialogInterface.OnClickListener dialogOnclicListener=new DialogInterface.OnClickListener(){  
  66.   
  67.             @Override  
  68.             public void onClick(DialogInterface dialog, int which) {  
  69.                 switch(which){  
  70.                     case Dialog.BUTTON_POSITIVE:  
  71.                         Toast.makeText(MainActivity.this"确认" + which, Toast.LENGTH_SHORT).show();  
  72.                         break;  
  73.                     case Dialog.BUTTON_NEGATIVE:  
  74.                         Toast.makeText(MainActivity.this"取消" + which, Toast.LENGTH_SHORT).show();  
  75.                         break;  
  76.                     case Dialog.BUTTON_NEUTRAL:  
  77.                         Toast.makeText(MainActivity.this"忽略" + which, Toast.LENGTH_SHORT).show();  
  78.                         break;  
  79.                 }  
  80.             }  
  81.         };  
  82.         //dialog参数设置  
  83.         AlertDialog.Builder builder=new AlertDialog.Builder(this);  //先得到构造器  
  84.         builder.setTitle("提示"); //设置标题  
  85.         builder.setMessage("是否确认退出?"); //设置内容  
  86.         builder.setIcon(R.mipmap.ic_launcher);//设置图标,图片id即可  
  87.         builder.setPositiveButton("确认",dialogOnclicListener);  
  88.         builder.setNegativeButton("取消", dialogOnclicListener);  
  89.         builder.setNeutralButton("忽略", dialogOnclicListener);  
  90.         builder.create().show();  
  91.     }  
  92.     private void dialog2() {  
  93.         final String items[]={"张三","李四","王五"};  
  94.         //dialog参数设置  
  95.         AlertDialog.Builder builder=new AlertDialog.Builder(this);  //先得到构造器  
  96.         builder.setTitle("提示"); //设置标题  
  97.         //builder.setMessage("是否确认退出?"); //设置内容  
  98.         builder.setIcon(R.mipmap.ic_launcher);//设置图标,图片id即可  
  99.         //设置列表显示,注意设置了列表显示就不要设置builder.setMessage()了,否则列表不起作用。  
  100.         builder.setItems(items,new DialogInterface.OnClickListener() {  
  101.             @Override  
  102.             public void onClick(DialogInterface dialog, int which) {  
  103.                 dialog.dismiss();  
  104.                 Toast.makeText(MainActivity.this, items[which], Toast.LENGTH_SHORT).show();  
  105.   
  106.             }  
  107.         });  
  108.         builder.setPositiveButton("确定",new DialogInterface.OnClickListener() {  
  109.             @Override  
  110.             public void onClick(DialogInterface dialog, int which) {  
  111.                 dialog.dismiss();  
  112.                 Toast.makeText(MainActivity.this"确定", Toast.LENGTH_SHORT).show();  
  113.             }  
  114.         });  
  115.         builder.create().show();  
  116.     }  
  117.     private void dialog3(){  
  118.         final String items[]={"男","女"};  
  119.         AlertDialog.Builder builder=new AlertDialog.Builder(this);  //先得到构造器  
  120.         builder.setTitle("提示"); //设置标题  
  121.         builder.setIcon(R.mipmap.ic_launcher);//设置图标,图片id即可  
  122.         builder.setSingleChoiceItems(items,0,new DialogInterface.OnClickListener() {  
  123.             @Override  
  124.             public void onClick(DialogInterface dialog, int which) {  
  125.                 //dialog.dismiss();  
  126.                 Toast.makeText(MainActivity.this, items[which], Toast.LENGTH_SHORT).show();  
  127.             }  
  128.         });  
  129.         builder.setPositiveButton("确定",new DialogInterface.OnClickListener() {  
  130.             @Override  
  131.             public void onClick(DialogInterface dialog, int which) {  
  132.                 dialog.dismiss();  
  133.                 Toast.makeText(MainActivity.this"确定", Toast.LENGTH_SHORT).show();  
  134.             }  
  135.         });  
  136.         builder.create().show();  
  137.     }  
  138.     private void dialog4(){  
  139.         final String items[]={"篮球","足球","排球"};  
  140.         final boolean selected[]={true,false,true};  
  141.         AlertDialog.Builder builder=new AlertDialog.Builder(this);  //先得到构造器  
  142.         builder.setTitle("提示"); //设置标题  
  143.         builder.setIcon(R.mipmap.ic_launcher);//设置图标,图片id即可  
  144.         builder.setMultiChoiceItems(items,selected,new DialogInterface.OnMultiChoiceClickListener() {  
  145.             @Override  
  146.             public void onClick(DialogInterface dialog, int which, boolean isChecked) {  
  147.                // dialog.dismiss();  
  148.                 Toast.makeText(MainActivity.this, items[which]+isChecked, Toast.LENGTH_SHORT).show();  
  149.             }  
  150.         });  
  151.         builder.setPositiveButton("确定",new DialogInterface.OnClickListener() {  
  152.             @Override  
  153.             public void onClick(DialogInterface dialog, int which) {  
  154.                 dialog.dismiss();  
  155.                 Toast.makeText(MainActivity.this"确定", Toast.LENGTH_SHORT).show();  
  156.                 //android会自动根据你选择的改变selected数组的值。  
  157.                 for (int i=0;i<selected.length;i++){  
  158.                     Log.e("hongliang",""+selected[i]);  
  159.                 }  
  160.             }  
  161.         });  
  162.         builder.create().show();  
  163.     }  
  164.     @Override  
  165.     public void onClick(View v) {  
  166.         switch (v.getId()){  
  167.             case R.id.main_btn1:  
  168.                // dialog1();  
  169.                 dialog1_1();//等同与上面的dialog1(),比之更简洁,推荐  
  170.                 break;  
  171.             case R.id.main_btn2:  
  172.                 dialog2();  
  173.                 break;  
  174.             case R.id.main_btn3:  
  175.                 dialog3();  
  176.                 break;  
  177.             case R.id.main_btn4:  
  178.                 dialog4();  
  179.                 break;  
  180.         }  
  181.     }  
  182. }  
布局文件:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical"  
  6.     android:paddingBottom="@dimen/activity_vertical_margin"  
  7.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  8.     android:paddingRight="@dimen/activity_horizontal_margin"  
  9.     android:paddingTop="@dimen/activity_vertical_margin"  
  10.     tools:context=".MainActivity">  
  11.   
  12.     <Button  
  13.         android:id="@+id/main_btn1"  
  14.         android:layout_width="wrap_content"  
  15.         android:layout_height="wrap_content"  
  16.         android:text="普通对话框" />  
  17.   
  18.     <Button  
  19.         android:id="@+id/main_btn2"  
  20.         android:layout_width="wrap_content"  
  21.         android:layout_height="wrap_content"  
  22.         android:text="列表对话框" />  
  23.   
  24.     <Button  
  25.         android:id="@+id/main_btn3"  
  26.         android:layout_width="wrap_content"  
  27.         android:layout_height="wrap_content"  
  28.         android:text="单选列表对话框" />  
  29.     <Button  
  30.         android:id="@+id/main_btn4"  
  31.         android:layout_width="wrap_content"  
  32.         android:layout_height="wrap_content"  
  33.         android:text="多选列表对话框" />  
  34. </LinearLayout>  
作者:laochidao 发表于2016/10/2 19:36:43 原文链接
阅读:87 评论:0 查看评论

1.3Android Studio如何集成Genymotion

$
0
0

1.3Android Studio如何集成Genymotion
1.3.1Genymotion简介
性能卓越作为历史上最快的Android模拟器(没有之一),秒级开机关机速度足够让你膜拜了(粗略估计5-20s不等),Android模拟器应该是1min起吧(如果你够幸运的话)? 此外,堪比真机的操作体验实在让人欲罢不能(希望你的真机性能足够卓越,不然在Genymotion面前,一切都是浮云)!返璞归真傻瓜式安装,易于使用,将复杂的技术隐藏于VitualBox、HardWare OpenGL等驱动引擎中。 完美仿真支持绝大部分的模拟器功能与感应器,甚至支持语音、NFC、蓝牙等等…作为Beta版,初出茅庐的Genymotion就坐拥数十万忠实粉丝,其中包括一些知名度极高的业内人士。在Genymotion团队的蓝图中,它将与开发测试完美的契合在一起。截至7月1日,Genymotion的功能已经足够应付开发的需求,胜任测试开发调试等工作。同时它兼容各大系统,提供Eclipse、IntelliJ插件。
1.3.2安装Genymotion
注册登录,进入Genymotion官网,点击下载按钮,这里必须要输入邮箱。如果是已经注册过直接输入邮箱即可。
如果未注册,输入邮箱和两次密码后,系统会发送一封邮件到你邮箱,请一定要去验证后在进行下一步下载。
注意:一定要进行验证。
这里写图片描述
1.3-1
下载相关软件,因为Genymotion运行需要VirtualBox,如果电脑中没安装过,选这个版本。
这个版本包含Oracle VirtualBox 4.2.12支持,因此您不用再手动下载安装VirtualBox。
这里写图片描述
1.3-2
如果已经安装过VirtualBox,选择这个版本
这里写图片描述
1.3-3
运行安装Genymotion,双击运行下载的Genymotion安装文件,选择中文语言并点击下一步,下一步
这里写图片描述
1.3-4
可更改安装路径,点击浏览Browse,软件默认的路径为C:\Program Files\Genymobile\Genymotion
然后下一步
这里写图片描述
1.3-5
在弹出框选择是否创建快捷菜单[Don’t create a Start Menu folder]点击下一步
这里写图片描述
1.3-6
在弹出框选择是否创建桌面快捷方式[Create a desktop icon],点击下一步–>安装–>完成
这里写图片描述
1.3-7
运行安装VirtualBox,在安装完Genymotion后,会继续安装VirtualBox。
在VirtualBox安装界面,点击下一步
这里写图片描述
1.3-8
更改安装路径,点击浏览Browse.. 更改Location的地址VirtualBox软件默认路径为C:\Program Files\Oracle\VirtualBox\
然后下一步
这里写图片描述
1.3-9
询问是否现在安装,选择是
这里写图片描述
1.3-10
点击安装
这里写图片描述
1.3-11

到这里,我们的程序就已经安装好了。下面我们就开始使用Genymotion
1.3.3使用Genymotion
打开Genymotion,第一次进入Genymotion,会检查你是否有安卓虚拟设备。如果没有会弹出对话框,询问你是否现在添加一个虚拟设备,点击yes就可以了
这里写图片描述
1.3-12
第一次使用Genymotion需要创建一个新的虚拟设备[Create a new virtual device]。
需要你输入用户名和密码验证。如:我的是163邮箱 如:**@163.com
(注意:如果验证不通过,请到邮箱确认是否已经验证过)
这里写图片描述
1.3-13

验证成功后,可以看到有很多虚拟设备,如:S3,S4等选择想添加的虚拟设备,选择后点击下一步
这里写图片描述
1.3-14
下载安装,等到下载到100%.点击[Finish]按钮
这里写图片描述
1.3-15
这里写图片描述
1.3-16
给已创建的模拟器命名,然后点击创建按钮。
这里写图片描述
1.3-17
回到主窗口,选择一个我们已经添加的模拟器,点击启动按钮启动模拟器。启动过程会弹出对话框,询问是否设置ADB连接的对话框,如果需要就点击是,否则就点击否。
这里写图片描述
1.3-18
启动虚拟机,几秒钟的事,速度非常快
这里写图片描述
1.3-19
1.3.4使用Genymotion注意问题
如果Genymotion打开的时候出现如图所示错误,需要使用如下方法解决问题。
这里写图片描述
1.3-20
第一种解决办法:关闭当前Genymotion模拟器,重新再打开,有的时候由VirtuadakailBox与本地通信不了的原因造成的

2、第二种解决办法:打开VirtualBox,点击“管理——》全局设定——》网络——》仅主机(Host-Only)网络”,点击编辑,查看IP和DHCP是否填写
这里写图片描述
1.3-21
如果你的VirtualBox里面为填写对应的IP和DHCP,可以按照上图填写,点击“确定”后,重启Genymotion模拟器
这里写图片描述
1.3-22
1.3.5在Android Studio中安装Genymotion插件
在系统安装完Genymotion后,现在我们要在Android Studio中安装Genymotion插件这样才能在Android Studio中使用Genymotion模拟器。
打开Android Studio,依次【File】-【Settings】
这里写图片描述
1.3-23
在打开的settings界面里找到plugins设置项,点击右侧的“Browser。。”按钮
这里写图片描述
1.3-24
在搜索栏里输入genymotion关键字,可以看到右侧已经搜索到插件,点击install安装。
这里写图片描述
1.3-25
开始下载,速度很快。
这里写图片描述
1.3-26

这里写图片描述
1.3-27
安装后重新启动Android Studio,我们就可以工具栏看到genymotion插件的图标。
这里写图片描述
1.3-28
初次点开需要我们设置一下genymotion的安装目录。
这里写图片描述
1.3-29
单击浏览按钮找到Genymotion的安装目录并选择
这里写图片描述
1.3-30
设置好目录,我们再次点击工具栏的图标如下图所示。
这里写图片描述
1.3-31
弹出Genoymotion模拟器选择窗口,单击需要启动的模拟器就可以启动虚拟机了。
这里写图片描述
1.3-32

在Android Studio中project工具窗口中选择一个app单击菜单栏中的run按钮在Genymotion中运行程序。
这里写图片描述
1.3-33

作者:zwj20100406 发表于2016/10/2 20:16:34 原文链接
阅读:79 评论:0 查看评论

Intent以及IntentFilter

$
0
0

Intent的概念:
Android中提供了Intent机制来协助应用间的交互与通讯,或者采用更准确的说法是,Intent不仅可用于应用程序之间,也可用于应用程序内部的activity, service和broadcast receiver之间的交互。Intent这个英语单词的本意是“目的、意向、意图”。
Intent是一种运行时绑定(runtime binding)机制,它能在程序运行的过程中连接两个不同的组件。通过Intent,你的程序可以向Android表达某种请求或者意愿,Android会根据意愿的内容选择适当的组件来响应。
activity、service和broadcast receiver之间是通过Intent进行通信的,而另外一个组件Content Provider本身就是一种通信机制,不需要通过Intent。

对于向这三种组件发送intent有不同的机制:
使用Context.startActivity() 或 Activity.startActivityForResult(),传入一个intent来启动一个activity。使用 Activity.setResult(),传入一个intent来从activity中返回结果。
将intent对象传给Context.startService()来启动一个service或者传消息给一个运行的service。将intent对象传给 Context.bindService()来绑定一个service。
将intent对象传给 Context.sendBroadcast(),Context.sendOrderedBroadcast(),或者Context.sendStickyBroadcast()等广播方法,则它们被传给 broadcast receiver。

意图中包含的属性:Component,Action,Category,Date,Type,Extra,Flag这7个属性。

   Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用。

——显示意图和隐式意图的区别?
一、显式意图:明确指定Intent的目标组件。 一般用于同一应用程序之间的跳转。
1)Component :意图所要启动的组件
ComponentName有三种构造器:
(1)ComponentName(Context,Class)
(2)ComponentName(Context,String)
(3)ComponentName(String package,String clz)
其中用来表示类名的字符串必须使用全类名.
包名只是用来区分android应用程序的。
也可以启动不同android应用程序的activity
intent.setComponent(component);
也可以使用intent.setClass()
也可以使用new Intent(Context,Class)
缺点:不能调用其他应用中的Activity。(但是5.0的版本可以使用了)

二、隐式意图:没有明确指定组件名的Intent为隐式意图,

    Android系统会根据隐式意图中设置的动作(action)、类别(category)、数据(URI和数据类型)找到最合适的组件来处理这个意图。一般用于不同应用程序之间的跳转。
    Android系统使用IntentFilter来寻找与隐式相关的组件。
    举个例子,说白了意图就是启动一个组件的的完整的动作信息,就像打人,打就是Action动作,人就是Data内容,而Type就是类型,打什么人呢?打坏人,type就是坏指的类型,只有这些信息全了才能执行一个完整的意图

(1)Action 动作
Action 是一个用户定义的字符串,用于描述一个 Android 应用程序组件,一个 Intent Filter 可以包含多个 Action。在 AndroidManifest.xml 的Activity 定义时,可以在其 节点指定一个 Action列表用于标识 Activity 所能接受的“动作”。
(2)Category 为Action增加额外的附加条件(用来表现动作的类别)
一般将action和category结合使用
注意:
Intent:只能包含一个Action,可以有多个Category
Activity:可以配置多种Action,可以配置多个Category,类别越多,动作越具体,意图越明确(类似于相亲时,给对方提了很多要求)。
问题:如果有两个Activity的action配置是相同的,由另外一个Activity启动该action会怎样?
当Intent匹配成功的组件有多个时,显示优先级高的组件,如果优先级相同,显示列表让用户自己选择,优先级从-1000至1000,并且其中一个必须为负的才有效
注意:
a)每一个通过 startActivity() 方法发出的隐式Intent都至少有一个 category,就是 “android.intent.category.DEFAULT”,所以只要是想接收一个隐式 Intent 的 Activity 都应该包括”android.intent.category.DEFAULT” category,不然将导致 Intent 匹配失败
b)一个至少应该包含一个,否则任何Intent请求都不能和该匹配。若Intent请求的Action和中的某一个匹配,那么该Intent就通过这条的动作测试。
c)如中没有包含任何Action类型,那么无论什么Intent请求都无法和这个匹配
e)当Intent请求中包含并所有的Category与组件中某个IntentFilter的完全匹配时,才会让该Intent请求通过测试

练习:测试系统的Action—Intent.ACTION_DIAL 用于打开系统的拨打电话的界面。
如:给某个人打电话:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);//打电话的动作
intent.setData(Uri.parse(“tel:123”));//具体的数据,打给123
startActivity(intent);
如:给10086发短信
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SENDTO);// 发送信息的动作
intent.putExtra(“sms_body”, “The SMS text”);
intent.setData(Uri.parse(“sms:10086”));// 具体的数据,发送给10086
startActivity(intent);

(3)Data 向Action属性提供操作的数据,Data属性接受一个Uri对象。
URI 通用资源标识符:在网络中或者本地如何找到一个唯一的资源的标识符
URL 统一资源定位符:把一个地址作为唯一的资源的标识符,
如:http://www.baidu.com(域名解析器解析域名会解析出ip地址和资源名)
网络资源:http://localhost:8080/Test/index.html
协议:规定传输数据的规则
URN 统一资源命名符:定义一个唯一的名字,比如说isbn号

android中一个Uri对象通常通过如下形式的字符串来表示:
scheme://host:port/path
协议://地址:端口号/资源路径
如:content://com.android.contacts/contacts/1
Activity配置时,使用设置其匹配的Data.
一般情况下Activty可以有多个action,多个category,但是只能有一个data.
并且配置data之前需要先有action.

<data android:scheme=""/>
scheme://...
<data android:scheme=""
      android:host=""/>
scheme://host...
<data android:scheme=""
      android:host=""
      android:port=""/>
scheme://host:port...
<data android:scheme=""
      android:host=""
      android:path=""/>
scheme://host/path
<data android:scheme=""
      android:host=""
      android:port=""
      android:path=""/>   
scheme://host:port/path

(4)Type 为Action属性提供操作的数据的数据类型,格式为xxx/xxx
Activity配置时,使用设置可以匹配的Type
请求的Intent必须设置Type,才能匹配到该Activity.

     MIME:全称Multipurpose Internet Mail Extensions,多功能Internet 邮件扩充服务。它是一种多用途网际邮件扩充协议,在1992年最早应用于电子邮件系统,但后来也应用到浏览器。MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
    在Android中通过文件的MIME类型来判断有哪些应用程序可以处理这些文件,并使用其中的某一个应用程序(如果有多个可选的应用程序,则用户必须指定一个)处理之。

注意:
      1)Intent中不能单独设置Type,需要结合Action和Data一起使用
      2)默认Data与Type不可以同时使用,后设置的属性会覆盖前设置的属性
     可以使用setDataAndType()同时设置两个属性

(5) Extra
使用Bundle对象在Intent中以键值对的形式保存数据,
用来在多个组件之间传递数据
调用: putExtra(key,value)方法向Intent的默认bundle中添加数据
putExtras(Bundle)方法用来为Intent设置新的Bundle
getXXXExtra(key)方法获取数据,部分类型可以设置默认值
getExtras()方法获取Intent中的Bundle对象

(6)Flag 旗标
setFlag(),getFlag()

AndroidManifest.xml

  <activity android:name="com.xspacing.intent.SecondActivity" >
            <intent-filter>
                <action android:name="com.xspacing.intent.action.briup" />
                <action android:name="com.xspacing.intent.action.smile" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="com.xspacing.intent.category.briup" />

                <!-- host:ip地址     path:路径      port:端口号   scheme:协议   mimeType 数据 类型-->
                <data
                    android:host="127.0.0.1"   
                    android:path="/ss"
                    android:port="8080"
                    android:scheme="www"
                    android:mimeType="aa/bb" />
            </intent-filter>
        </activity>
        Intent intent = new Intent();
        intent.setAction("com.xspacing.intent.action.briup");
        intent.addCategory("com.xspacing.intent.category.briup");
        // intent.setAction("com.xspacing.intent.action.smile");
        // Type不能跟data分开设置,前一个会把后一个覆盖
        // intent.setType("aa/bb");
        // intent.setData(Uri.parse("www://127.0.0.1:8080/ss"));
        intent.setDataAndType(Uri.parse("www://127.0.0.1:8080/ss"), "aa/bb");
        startActivity(intent);
作者:hellcw 发表于2016/10/2 20:31:57 原文链接
阅读:71 评论:0 查看评论

指定Action,Category调用系统Activity

$
0
0

Intent代表了启动某个程序组件的意图,实际上Intent对象不仅可以启动本应用内程序组件,也可启动Android系统的其他应用的程序组件,包含系统自带的程序组件—只要权限允许。实际上android内部提供了大量标准的Acton,Catetory常量,其中用于启动Activity的标准Action常量及对对应的字符串:

标准的action:
常量 对应字符串 简单说明
ACTION_MAIN android.intent.action.MAIN 应用程序入口
ACTION_VIEW android.intent.action.VIEW 显示指定数据
ACTION_ATTACH_DATE android.intent.action.ATTACH_DATE 指定某快数据将被附加到其他地方
ACTION_EDIT android.intent.action.EDIT 编辑指定数据
ACTION_PICK android.intent.action.PICK 从列表中选择某项,并返回所选的数据
ACTION_CHOOSER android.intent.action.CHOOSER 显示一个Activity选择器
ACTION_GET_CONTENT android.intent.action.GET_CONTENT 让用户选择数据,并返回所选数据
ACTION_DIAL android.intent.action.DIAL 显示拨号面板
ACTION_CALL android.intent.action.CALL 直接向指定用户打电话
ACTION_SEND android.intent.action.SEND 向其他人发送数据
ACTION_SENDTO android.intent.action.SENDTO 向其他人发送信息
ACTION_ANSWER android.intent.action.ANSWER 应答电话
ACTION_INSERT android.intent.action.INSERT 插入数据
ACTION_DELETE android.intent.action.DELETE 删除数据
ACTION_RUN android.intent.action.RUN 运行数据
ACTION_SYNC android.intent.action.SYNC 执行数据同步
ACTION_PICK_ACTIVITY android.intent.action.PICK_ACTIVITY 用于选择Activity
ACTION_SEARCH android.intent.action.SEARCH 执行搜索
ACTION_WEB_SEARCH android.intent.action.WEB_SEARCH 执行web搜索
ACTION_FACTORY_TEST android.intent.action.FACTORY_TEST 工厂测试的入口点

Category常量:
CATEGORY_DEFAULT android.intent.category.DEFAULT 默认的Category
CATEGORY_BROWSABLE android.intent.category.BROWSABLE 指定该Activity能被浏览器完全调用
CATEGORY_TAB android.intent.category.TAB 指定该Activity作为TabActivity的Tab页
CATEGORY_LAUNCHER android.intent.category.LAUNCHER 设置该Activity随系统启动而运行
CATEGORY_INFO android.intent.category.INFO 用于提供包信息
CATEGORY_HOME android.intent.category.HOME Activity显示顶级程序列表中
CATEGORY_PREFERENCE android.intent.category.PREFERENCE 该Activity是参数面板
CATEGORY_TEST android.intent.category.TEST 该Activity是一个测试
CATEGORY_CAR_DOCK android.intent.category.CAR_DOCK 指定手机被插入汽车底座时运行
CATEGORY_DESK_DOCK android.intent.category.DESK_DOCK 指定手机被插入桌面底座时运行
CATEGORY_CAR_MODE android.intent.category.CAR_MODE 指定该Activity可在车载环境下使用

附录:
【一】系统Action作用:
String ADD_SHORTCUT_ACTION 动作:在系统中添加一个快捷方式。
String ALL_APPS_ACTION 动作:列举所有可用的应用。输入:无。
String ALTERNATIVE_CATEGORY 类别:说明 activity 是用户正在浏览的数据的一个可选操作。
String ANSWER_ACTION 动作:处理拨入的电话。
String BATTERY_CHANGED_ACTION 广播:充电状态,或者电池的电量发生变化。
String BOOT_COMPLETED_ACTION 广播:在系统启动后,这个动作被广播一次(只有一次)。
String BROWSABLE_CATEGORY 类别:能够被浏览器安全使用的 activities 必须支持这个类别。
String BUG_REPORT_ACTION 动作:显示 activity 报告错误。
String CALL_ACTION 动作:拨打电话,被呼叫的联系人在数据中指定。
String CALL_FORWARDING_STATE_CHANGED_ACTION 广播:语音电话的呼叫转移状态已经改变。
String CLEAR_CREDENTIALS_ACTION 动作:清除登陆凭证 (credential)。
String CONFIGURATION_CHANGED_ACTION 广播:设备的配置信息已经改变,参见 Resources.Configuration.
Creator CREATOR 无 无
String DATA_ACTIVITY_STATE_CHANGED_ACTION 广播:电话的数据活动(data activity)状态(即收发数据的状态)已经改变。
String DATA_CONNECTION_STATE_CHANGED_ACTION 广播:电话的数据连接状态已经改变。
String DATE_CHANGED_ACTION 广播:日期被改变。
String DEFAULT_ACTION 动作:和 VIEW_ACTION 相同,是在数据上执行的标准动作。
String DEFAULT_CATEGORY 类别:如果 activity 是对数据执行确省动作(点击, center press)的一个选项,需要设置这个类别。
String DELETE_ACTION 动作:从容器中删除给定的数据。
String DEVELOPMENT_PREFERENCE_CATEGORY 类别:说明 activity 是一个设置面板 (development preference panel).
String DIAL_ACTION 动作:拨打数据中指定的电话号码。
String EDIT_ACTION 动作:为制定的数据显示可编辑界面。
String EMBED_CATEGORY 类别:能够在上级(父)activity 中运行。
String EMERGENCY_DIAL_ACTION 动作:拨打紧急电话号码。
int FORWARD_RESULT_LAUNCH 启动标记:如果这个标记被设置,而且被一个已经存在的 activity 用来启动新的 activity,已有 activity 的回复目标 (reply target) 会被转移给新的 activity。
String FOTA_CANCEL_ACTION 广播:取消所有被挂起的 (pending) 更新下载。
String FOTA_INSTALL_ACTION 广播:更新已经被确认,马上就要开始安装。
String FOTA_READY_ACTION 广播:更新已经被下载,可以开始安装。
String FOTA_RESTART_ACTION 广播:恢复已经停止的更新下载。
String FOTA_UPDATE_ACTION 广播:通过 OTA 下载并安装操作系统更新。
String FRAMEWORK_INSTRUMENTATION_TEST_CATEGORY 类别:To be used as code under test for framework instrumentation tests.
String GADGET_CATEGORY 类别:这个 activity 可以被嵌入宿主 activity (activity that is hosting gadgets)。
String GET_CONTENT_ACTION 动作:让用户选择数据并返回。
String HOME_CATEGORY 类别:主屏幕 (activity),设备启动后显示的第一个 activity。
String INSERT_ACTION 动作:在容器中插入一个空项 (item)。
String INTENT_EXTRA 附加数据:和 PICK_ACTIVITY_ACTION 一起使用时,说明用户选择的用来显示的 activity;和 ADD_SHORTCUT_ACTION 一起使用的时候,描述要添加的快捷方式。
String LABEL_EXTRA 附加数据:大写字母开头的字符标签,和 ADD_SHORTCUT_ACTION 一起使用。
String LAUNCHER_CATEGORY 类别:Activity 应该被显示在顶级的 launcher 中。
String LOGIN_ACTION 动作:获取登录凭证。
String MAIN_ACTION 动作:作为主入口点启动,不需要数据。
String MEDIABUTTON_ACTION 广播:用户按下了“Media Button”。
String MEDIA_BAD_REMOVAL_ACTION 广播:扩展介质(扩展卡)已经从 SD 卡插槽拔出,但是挂载点 (mount point) 还没解除 (unmount)。
String MEDIA_EJECT_ACTION 广播:用户想要移除扩展介质(拔掉扩展卡)。
String MEDIA_MOUNTED_ACTION 广播:扩展介质被插入,而且已经被挂载。
String MEDIA_REMOVED_ACTION 广播:扩展介质被移除。
String MEDIA_SCANNER_FINISHED_ACTION 广播:已经扫描完介质的一个目录。
String MEDIA_SCANNER_STARTED_ACTION 广播:开始扫描介质的一个目录。
String MEDIA_SHARED_ACTION 广播:扩展介质的挂载被解除 (unmount),因为它已经作为 USB 大容量存储被共享。
String MEDIA_UNMOUNTED_ACTION 广播:扩展介质存在,但是还没有被挂载 (mount)。
String MESSAGE_WAITING_STATE_CHANGED_ACTION 广播:电话的消息等待(语音邮件)状态已经改变。
int MULTIPLE_TASK_LAUNCH 启动标记:和 NEW_TASK_LAUNCH 联合使用,禁止将已有的任务改变为前景任务 (foreground)。
String NETWORK_TICKLE_RECEIVED_ACTION 广播:设备收到了新的网络 “tickle” 通知。
int NEW_TASK_LAUNCH 启动标记:设置以后,activity 将成为历史堆栈中的第一个新任务(栈顶)。
int NO_HISTORY_LAUNCH 启动标记:设置以后,新的 activity 不会被保存在历史堆栈中。
String PACKAGE_ADDED_ACTION 广播:设备上新安装了一个应用程序包。
String PACKAGE_REMOVED_ACTION 广播:设备上删除了一个应用程序包。
String PHONE_STATE_CHANGED_ACTION 广播:电话状态已经改变。
String PICK_ACTION 动作:从数据中选择一个项目 (item),将被选中的项目返回。
String PICK_ACTIVITY_ACTION 动作:选择一个 activity,返回被选择的 activity 的类(名)。
String PREFERENCE_CATEGORY 类别:activity是一个设置面板 (preference panel)。
String PROVIDER_CHANGED_ACTION 广播:更新将要(真正)被安装。
String PROVISIONING_CHECK_ACTION 广播:要求 polling of provisioning service 下载最新的设置。
String RUN_ACTION 动作:运行数据(指定的应用),无论它(应用)是什么。
String SAMPLE_CODE_CATEGORY 类别:To be used as an sample code example (not part of the normal user experience).
String SCREEN_OFF_ACTION 广播:屏幕被关闭。
String SCREEN_ON_ACTION 广播:屏幕已经被打开。
String SELECTED_ALTERNATIVE_CATEGORY 类别:对于被用户选中的数据,activity 是它的一个可选操作。
String SENDTO_ACTION 动作:向 data 指定的接收者发送一个消息。
String SERVICE_STATE_CHANGED_ACTION 广播:电话服务的状态已经改变。
String SETTINGS_ACTION 动作:显示系统设置。输入:无。
String SIGNAL_STRENGTH_CHANGED_ACTION 广播:电话的信号强度已经改变。
int SINGLE_TOP_LAUNCH 启动标记:设置以后,如果 activity 已经启动,而且位于历史堆栈的顶端,将不再启动(不重新启动) activity。
String STATISTICS_REPORT_ACTION 广播:要求 receivers 报告自己的统计信息。
String STATISTICS_STATE_CHANGED_ACTION 广播:统计信息服务的状态已经改变。
String SYNC_ACTION 动作:执行数据同步。
String TAB_CATEGORY 类别:这个 activity 应该在 TabActivity 中作为一个 tab 使用。
String TEMPLATE_EXTRA 附加数据:新记录的初始化模板。
String TEST_CATEGORY 类别:作为测试目的使用,不是正常的用户体验的一部分。
String TIMEZONE_CHANGED_ACTION 广播:时区已经改变。
String TIME_CHANGED_ACTION 广播:时间已经改变(重新设置)。
String TIME_TICK_ACTION 广播:当前时间已经变化(正常的时间流逝)。
String UMS_CONNECTED_ACTION 广播:设备进入 USB 大容量存储模式。
String UMS_DISCONNECTED_ACTION 广播:设备从 USB 大容量存储模式退出。
String UNIT_TEST_CATEGORY 类别:应该被用作单元测试(通过 test harness 运行)。
String VIEW_ACTION 动作:向用户显示数据。
String WALLPAPER_CATEGORY 类别:这个 activity 能过为设备设置墙纸。
String WALLPAPER_CHANGED_ACTION 广播:系统的墙纸已经改变。
String WALLPAPER_SETTINGS_ACTION 动作:显示选择墙纸的设置界面。输入:无。
String WEB_SEARCH_ACTION 动作:执行 web 搜索。
String XMPP_CONNECTED_ACTION 广播:XMPP 连接已经被建立。
String XMPP_DISCONNECTED_ACTION 广播:XMPP 连接已经被断开

【二】Intent功能举例
1.从google搜索内容
Intent intent = new Intent();
intent.setAction(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY,”天气”)
startActivity(intent);

2.浏览网页
Uri uri = Uri.parse(“http://www.google.com“);
Intent it = new Intent(Intent.ACTION_VIEW,uri);
startActivity(it);

3.显示地图
Uri uri = Uri.parse(“geo:38.899533,-77.036476”);
Intent it = new Intent(Intent.Action_VIEW,uri);
startActivity(it);

4.路径规划
Uri uri = Uri.parse(“http://maps.google.com/maps?f=dsaddr=startLat%20startLng&daddr=endLat%20endLng&hl=en“);
Intent it = new Intent(Intent.ACTION_VIEW,URI);
startActivity(it);

5.拨打电话
Uri uri = Uri.parse(“tel:xxxxxx”);
Intent it = new Intent(Intent.ACTION_DIAL, uri);
startActivity(it);

6.调用发短信的程序
Intent it = new Intent(Intent.ACTION_VIEW);
it.putExtra(“sms_body”, “The SMS text”);
it.setType(“vnd.android-dir/mms-sms”);
startActivity(it);

7.发送短信
Uri uri = Uri.parse(“smsto:0800000123”);
Intent it = new Intent(Intent.ACTION_SENDTO, uri);
it.putExtra(“sms_body”, “The SMS text”);
startActivity(it);
String body=”this is sms demo”;
Intent mmsintent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(“smsto”, number, null));
mmsintent.putExtra(Messaging.KEY_ACTION_SENDTO_MESSAGE_BODY, body);
mmsintent.putExtra(Messaging.KEY_ACTION_SENDTO_COMPOSE_MODE, true);
mmsintent.putExtra(Messaging.KEY_ACTION_SENDTO_EXIT_ON_SENT, true);
startActivity(mmsintent);

8.发送彩信
Uri uri = Uri.parse(“content://media/external/images/media/23”);
Intent it = new Intent(Intent.ACTION_SEND);
it.putExtra(“sms_body”, “some text”);
it.putExtra(Intent.EXTRA_STREAM, uri);
it.setType(“image/png”);
startActivity(it);
StringBuilder sb = new StringBuilder();
sb.append(“file://”);
sb.append(fd.getAbsoluteFile());
Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(“mmsto”, number, null));
// Below extra datas are all optional.
intent.putExtra(Messaging.KEY_ACTION_SENDTO_MESSAGE_SUBJECT, subject);
intent.putExtra(Messaging.KEY_ACTION_SENDTO_MESSAGE_BODY, body);
intent.putExtra(Messaging.KEY_ACTION_SENDTO_CONTENT_URI, sb.toString());
intent.putExtra(Messaging.KEY_ACTION_SENDTO_COMPOSE_MODE, composeMode);
intent.putExtra(Messaging.KEY_ACTION_SENDTO_EXIT_ON_SENT, exitOnSent);
startActivity(intent);

9.发送Email
Uri uri = Uri.parse(“mailto:xxx@abc.com”);
Intent it = new Intent(Intent.ACTION_SENDTO, uri);
startActivity(it);
Intent it = new Intent(Intent.ACTION_SEND);
it.putExtra(Intent.EXTRA_EMAIL, “me@abc.com”);
it.putExtra(Intent.EXTRA_TEXT, “The email body text”);
it.setType(“text/plain”);
startActivity(Intent.createChooser(it, “Choose Email Client”));
Intent it=new Intent(Intent.ACTION_SEND);
String[] tos={“me@abc.com”};
String[] ccs={“you@abc.com”};
it.putExtra(Intent.EXTRA_EMAIL, tos);
it.putExtra(Intent.EXTRA_CC, ccs);
it.putExtra(Intent.EXTRA_TEXT, “The email body text”);
it.putExtra(Intent.EXTRA_SUBJECT, “The email subject text”);
it.setType(“message/rfc822”);
startActivity(Intent.createChooser(it, “Choose Email Client”));

Intent it = new Intent(Intent.ACTION_SEND);
it.putExtra(Intent.EXTRA_SUBJECT, “The email subject text”);
it.putExtra(Intent.EXTRA_STREAM, “file:///sdcard/mysong.mp3”);
sendIntent.setType(“audio/mp3”);
startActivity(Intent.createChooser(it, “Choose Email Client”));

10.播放多媒体
Intent it = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse(“file:///sdcard/song.mp3”);
it.setDataAndType(uri, “audio/mp3”);
startActivity(it);
Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, “1”);
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);

11.uninstall apk
Uri uri = Uri.fromParts(“package”, strPackageName, null);
Intent it = new Intent(Intent.ACTION_DELETE, uri);
startActivity(it);

12.install apk
Uri installUri = Uri.fromParts(“package”, “xxx”, null);
returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri);

  1. 打开照相机
    <1>Intent i = new Intent(Intent.ACTION_CAMERA_BUTTON, null);
    this.sendBroadcast(i);
    <2>long dateTaken = System.currentTimeMillis();
    String name = createName(dateTaken) + “.jpg”;
    fileName = folder + name;
    ContentValues values = new ContentValues();
    values.put(Images.Media.TITLE, fileName);
    values.put(“_data”, fileName);
    values.put(Images.Media.PICASA_ID, fileName);
    values.put(Images.Media.DISPLAY_NAME, fileName);
    values.put(Images.Media.DESCRIPTION, fileName);
    values.put(Images.ImageColumns.BUCKET_DISPLAY_NAME, fileName);
    Uri photoUri = getContentResolver().insert(
    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

    Intent inttPhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    inttPhoto.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
    startActivityForResult(inttPhoto, 10);

14.从gallery选取图片
Intent i = new Intent();
i.setType(“image/*”);
i.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(i, 11);

  1. 打开录音机
    Intent mi = new Intent(Media.RECORD_SOUND_ACTION);
    startActivity(mi);

16.显示应用详细列表
Uri uri = Uri.parse(“market://details?id=app_id”);
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
//where app_id is the application ID, find the ID
//by clicking on your application on Market home
//page, and notice the ID from the address bar

刚才找app id未果,结果发现用package name也可以
Uri uri = Uri.parse(“market://details?id=”);
这个简单多了

17寻找应用
Uri uri = Uri.parse(“market://search?q=pname:pkg_name”);
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
//where pkg_name is the full package path for an application

18打开联系人列表
<1>
Intent i = new Intent();
i.setAction(Intent.ACTION_GET_CONTENT);
i.setType(“vnd.android.cursor.item/phone”);
startActivityForResult(i, REQUEST_TEXT);
<2>
Uri uri = Uri.parse(“content://contacts/people”);
Intent it = new Intent(Intent.ACTION_PICK, uri);
startActivityForResult(it, REQUEST_TEXT);

19 打开另一程序
Intent i = new Intent();
ComponentName cn = new ComponentName(“com.yellowbook.android2”,
“com.yellowbook.android2.AndroidSearch”);
i.setComponent(cn);
i.setAction(“android.intent.action.MAIN”);
startActivityForResult(i, RESULT_OK);

20.调用系统编辑添加联系人(高版本SDK有效):
Intent it = newIntent(Intent.ACTION_INSERT_OR_EDIT);
it.setType(“vnd.android.cursor.item/contact”);
//it.setType(Contacts.CONTENT_ITEM_TYPE);
it.putExtra(“name”,”myName”);
it.putExtra(android.provider.Contacts.Intents.Insert.COMPANY, “organization”);
it.putExtra(android.provider.Contacts.Intents.Insert.EMAIL,”email”);
it.putExtra(android.provider.Contacts.Intents.Insert.PHONE,”homePhone”);
it.putExtra(android.provider.Contacts.Intents.Insert.SECONDARY_PHONE,
“mobilePhone”);
it.putExtra( android.provider.Contacts.Intents.Insert.TERTIARY_PHONE,
“workPhone”);
it.putExtra(android.provider.Contacts.Intents.Insert.JOB_TITLE,”title”);
startActivity(it);

21.调用系统编辑添加联系人(全有效):
Intent intent = newIntent(Intent.ACTION_INSERT_OR_EDIT);
intent.setType(People.CONTENT_ITEM_TYPE);
intent.putExtra(Contacts.Intents.Insert.NAME, “My Name”);
intent.putExtra(Contacts.Intents.Insert.PHONE, “+1234567890”);
intent.putExtra(Contacts.Intents.Insert.PHONE_TYPE,Contacts.PhonesColumns.TYPE_MOBILE);
intent.putExtra(Contacts.Intents.Insert.EMAIL, “com@com.com”);
intent.putExtra(Contacts.Intents.Insert.EMAIL_TYPE, Contacts.ContactMethodsColumns.TYPE_WORK);

作者:hellcw 发表于2016/10/2 20:36:27 原文链接
阅读:82 评论:0 查看评论

IOS 动画设计(1)——高内聚,低耦合原则

$
0
0

模块独立性(单一职能原则)指每个模块只完成系统要求的独立子功能,并且与其他模块的联系最少且接口简单,两个定性的度量标准就是耦合性和内聚性。

耦合性也称块间联系。是软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。

内聚性又称块内联系。指模块的功能强度的度量,即一个模块内部各个元素彼此结合的紧密程度的度量。若一个模块内各元素(语名之间、程序段之间)联系的越紧密,则它的内聚性就越高。

耦合性与内聚性是模块独立性的两个定性标准,将软件系统划分模块时,尽量做到高内聚低耦合,提高模块的独立性,为设计高质量的软件结构奠定基础。同样的,在 IOS 动画设计中,也同样要求做到高聚类,低耦合。下面举例说明。

需求很简单,就是画一条横线,2秒后向右位移(持续1秒),4秒后向右位移并在1秒内逐渐消失。
传统的比较弱的新手写法如下:
这里写图片描述
把所有的语句都写在一个函数中,造成 viewDidLoad 函数体臃肿,且可读性较差,给后期维护与修改带来不便。

秉承高聚类、低耦合的原则,现做修改如下:
这里写图片描述

这里写图片描述
将动画分解为“出现”和“隐藏”两种属性,并将单一的某种动画属性(此处为线条的动画属性)封装成类。

此时,便使用高内聚、低耦合的原则设计出了动画,每个模块都有其单一职能。由此,可以作出相应总结与理解:

1. 高内聚
具有高内聚性的一个元素,只完成它职责内的事情,而把那些不在它职责内的事情拿去请求别人来完成。如果我是一个项目经理,我的职责是监控和协调我的项目各个阶段的工作。当我的项目进入需求分析阶段,我会请求需求分析员来完成;当我的项目进入开发阶段,我会请求软件开发人员来完成;当我的项目需要测试的时候,我会请求测试人员……如果我参与了开发,我就不是一个高内聚的元素,因为开发不是我的职责。“高内聚”给软件项目带来的优点是:可读性强、易维护和变更、支持低耦合、移植和重用性强。

2. 低耦合
低耦合就是要求在我们的软件系统中,某元素不要过度依赖于其它元素。举个例子,一个程序有50个函数,这个程序执行得非常好;然而一旦你修改其中一个函数,其他49个函数都需要做修改,这就是高耦合的后果。“低耦合”给软件项目带来的优点是:易于变更、易于重用。

3. 高内聚与低耦合的类比理解
高内聚、低耦合讲的是程序单位协作的问题, 可以这样理解,一个企业的管理, 最理想的情况就是各个部门各司其职,井然有序,互不干涉, 但是需要沟通交流的时候呢, 各个部门都可以找到接口人专门负责部门沟通以及对外沟通。
在软件设计中, 就是说各个模块要智能明确, 一个功能尽量由一个模块实现, 同样,一个模块最好只实行一个功能。这个是所谓的“内聚”; 模块与模块之间、系统与系统之间的交互,是不可避免的,但是我们要尽量减少由于交互引起的单个模块无法独立使用或者无法移植的情况发生, 尽可能多的单独提供接口用于对外操作, 这个就是所谓的“低耦合”。
但是实际的设计开发过程中,总会发生这样那样的问题与情况,真正做到高内聚、低耦合是很难的,很多时候未必一定要这样,更多的时候“最适合”的才是最好的,不过,理解思想,审时度势地使用,融会贯通,灵活运用,才是设计的王道。

作者:huangfei711 发表于2016/10/2 20:52:59 原文链接
阅读:106 评论:0 查看评论

File类源代码解析及使用

$
0
0

路径名可以是绝对的(相对于文件系统的根目录)或相对于在该程序正在运行的当前目录。

由File类定义的文件在实际中有可能存在也有可能不存在,有可能是一个目录或者其它非普通文件。

File类提供用于获取/设置文件权限、文件类型和最后修改时间等有限功能。


1.构造函数

使用指定路径创建新的文件

path:指定的文件路径

fixSlashes(path): 去掉路径中重复的斜杠和反斜杠,如:‘//’-->'/'

public File(String path) {
        this.path = fixSlashes(path);
    }


构造使用指定的目录和文件名的文件。

dir:已存储的文件的目录,注意是File类型

name:文件名,带后缀

public File(File dir, String name) {
        this(dir == null ? null : dir.getPath(), name);
    }


使用指定的目录和文件名构造新的文件。文件名不能为空,不然会报错。

dirPath: 新的文件的目录/路径

name:文件名

join(dirPath,name):连接dirPath和name,如果dirPath最后一个字母不是斜杠或者dirPath是空的,则会添加斜杠再将dirPath和name连接

public File(String dirPath, String name) {
        if (name == null) {
            throw new NullPointerException();
        }
        if (dirPath == null || dirPath.isEmpty()) {
            this.path = fixSlashes(name);
        } else if (name.isEmpty()) {
            this.path = fixSlashes(dirPath);
        } else {
            this.path = fixSlashes(join(dirPath, name));
        }
    }

使用指定的URI路径创建新的文件。注意这里是URI不是URL!!两者是不同的概念。URI,统一资源标识符,用于标识抽象或物理资源,且唯一的。

uri:不能为空,用于构造该文件的统一资源标识符。备注:uri可以由路径转换。

checkURI(uri):检查uri是否符合规范。

public File(URI uri) {
        // check pre-conditions
        checkURI(uri);
        this.path = fixSlashes(uri.getPath());
    }

2. 其它方法

返回文件系统的所有根目录,在Android和Unix系统中,只有一个根目录。就一个斜杠。

public static File[] listRoots() {
        return new File[] { new File("/") };
    }

测试File是否可使用或者进程是否可执行该File。这个方法不一定可靠,还要以实际操作为主,且使用此方法必须是API1.6以后的,也就是说minSdkVersion的值要大于等于9。

doAccess(X_OK)实际上是调用了Libcore.os.access(path, mode);这个方法是用来判断某个目录/文件是否存在、是否可读、是否可写、是否执行。

 public boolean canExecute() {
        return doAccess(X_OK);
    }

返回当前File和另外一个File的相对路径的排序/字典顺序,排序方式根据操作系统来。路径一样返回0,返回负值则表示小于参数路径,大于0表示大于参数路径。
public int compareTo(File another) {
        return this.getPath().compareTo(another.getPath());
    }


当前环境是否允许读该文件,或者说该文件是否可读
  public boolean canRead() {
        return doAccess(R_OK);
    }

当前环境是否允许写入该文件,或者说该文件是否可写

 public boolean canWrite() {
        return doAccess(W_OK);
    }


删除该文件,如果删除成功,返回true,反之为false。注意:删除失败不会抛出IOException,所以要check返回值。

 public boolean delete() {
        try {
            Libcore.os.remove(path);
            return true;
        } catch (ErrnoException errnoException) {
            return false;
        }
    }

当虚拟机终止时自动删除该目录/文件。注意:在Androd上应用程序的生命周期不包括虚拟机的终止。所以调用此方法不一定会删除文件。为了确保文件/目录被删除,最好是调用delete方法,且在finally代码块里手动调用。或者自己写一套删除File的方法,并且在合适的应用生命周期中调用。
public void deleteOnExit() {
        DeleteOnExit.getInstance().addFile(getAbsolutePath());
    }


Object类和该目录/文件比较,如果一样则返回true。实际上比较的是path。

@Override
    public boolean equals(Object obj) {
        if (!(obj instanceof File)) {
            return false;
        }
        return path.equals(((File) obj).getPath());
    }


在文件系统中该目录/文件是否存在,如果存在则返回true,反之false

public boolean exists() {
        return doAccess(F_OK);
    }


获取该目录/文件的绝对路径,绝对路径起始于文件系统的根目录。在Android操作系统里,只有一个根目录,就是 "/"。

源码中isAbsolute()方法是判断其是判断该目录/文件是否是绝对路径。如果当前File的路径为空,则返回用户当前的工作目录,如果当前目录/文件的路径不为空,则返回用户当前的工作目录和路径的连接。

public String getAbsolutePath() {
        if (isAbsolute()) {
            return path;
        }
        String userDir = System.getProperty("user.dir");//系统属性,获取用户当前的工作目录
        return path.isEmpty() ? userDir : join(userDir, path);
    }

判断该目录/文件是否是绝对路径,如果是则返回true,反之为false。判断标准是根据path是否是斜杠开头。因为Android的根目录就是"/"。所以只要判断其父目录是否是根目录。
public boolean isAbsolute() {
        return path.length() > 0 && path.charAt(0) == separatorChar;
    }

通过当前上下文的绝对路径构造的新的目录/文件。本质上是new File(this.getAbsolutePath())
public File getAbsoluteFile() {
        return new File(getAbsolutePath());
    }

获取当前目录/文件的规范路径。绝对路径是开始于文件系统的根目录,而规范路径是绝对路径和连接符号。例如C:\Program Files\..\test.txt。如果一个路径元素不存在或者不可被搜索,就会有转换成规范化文本的冲突,例如a/../b,如果a不存在或者不可被搜索,则规范路径就变成了“b”。所以还是建议使用绝对路径getAbsolutePath()。一个规范的路径的计算成本比较大,不建议使用。对于规范路径的主要用途是通过比较规范路径来判断两个路径是否指向同一个文件。

 public String getCanonicalPath() throws IOException {
        return realpath(getAbsolutePath());
    }

通过当前目录/文件的规范化路径创建一个新的目录/文件。File another = file.getCanonicalFile();

public File getCanonicalFile() throws IOException {
        return new File(getCanonicalPath());
    }

返回当前目录/文件的名称。先获得path中斜杠最后出现的位置。如果位置的值小于0,说明path中没有斜杠,则可知path就是目录/文件名称。反则,则分割path中最后一个斜杠出现的位置的下一个值到path长度的区间的字符串。substring分割的区间是[a,b)。file.getName()

 public String getName() {
        int separatorIndex = path.lastIndexOf(separator);
        return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
    }

返回该目录/文件的父目录/上一级目录。但不包括根目录,根目录的父目录是null,因为根目录是所有目录的父节点。file.getParent()

public String getParent() {
        int length = path.length(), firstInPath = 0;
        if (separatorChar == '\\' && length > 2 && path.charAt(1) == ':') {//标准路径
            firstInPath = 2;
        }
        int index = path.lastIndexOf(separatorChar);//获取斜杠或者反斜杠在路径中最后出现的位置
        if (index == -1 && firstInPath > 0) {//判断separatorChar是斜杠还是反斜杠。如果是反斜杠,说明是标准化路径。
            index = 2;//separatorChar是斜杠,例如"c:\\",separatorChar="/",该等式成立
        }
        if (index == -1 || path.charAt(length - 1) == separatorChar) {//根目录返回null,因为根目录是最大父节点
            return null;
        }
        if (path.indexOf(separatorChar) == index
                && path.charAt(firstInPath) == separatorChar) {//只有一级子目录
            return path.substring(0, index + 1);
        }
        return path.substring(0, index);//例如:path="/a/b/c.txt";返回"/a/b/c"
    }

从该File的父目录创建一个新的File。注意,这里是从父目录new一个新的File,不是得到父目录的File。file.getParentFile()

 public File getParentFile() {
        String tempParent = getParent();
        if (tempParent == null) {//先判断当前File是否有父目录,因为根目录的父目录是null,new File(null)是错误的
            return null;
        }
        return new File(tempParent);
    }

返回该File的路径。从前面可知,在调用File的构造函数时,实际上就是初始化path的值,这个方法就是path的get方法。file.getPath()

 public String getPath() {
        return path;
    }

获得一个整数哈希码。两个对象如果是equal的,则这两个对象的hashCode值也是相等的。file.hashCode()

 @Override
    public int hashCode() {
        return getPath().hashCode() ^ 1234321;
    }

判断当前File的路径是否是绝对路径。在Android系统中,绝对路径的起始是"/"。因为"/"是根目录!!file.isAbsolute()

public boolean isAbsolute() {
        return path.length() > 0 && path.charAt(0) == separatorChar;
    }

判断File是目录还是文件。如果是目录,返回true,反之返回false。file.isDirectory()

public boolean isDirectory() {
        try {
            return S_ISDIR(Libcore.os.stat(path).st_mode);
        } catch (ErrnoException errnoException) {
            // The RI returns false on error. (Even for errors like EACCES or ELOOP.)
            return false;
        }
    }

判断File是文件还是目录。如果是文件,返回true,反之false。file.isFile();

public boolean isFile() {
        try {
            return S_ISREG(Libcore.os.stat(path).st_mode);
        } catch (ErrnoException errnoException) {
            // The RI returns false on error. (Even for errors like EACCES or ELOOP.)
            return false;
        }
    }

判断该文件是否是隐藏文件。如果是返回true,反之false。file.isHidden();

public boolean isHidden() {
        if (path.isEmpty()) {
            return false;
        }
        return getName().startsWith(".");
    }

返回一个文件最后一件被修改的时间,如果返回0,则文件不存在。返回的数值是一个长整型毫秒数。获取失败会抛ErrnoException异常。file.lastModified();

public long lastModified() {
        try {
            return Libcore.os.stat(path).st_mtime * 1000L;
        } catch (ErrnoException errnoException) {
            // The RI returns 0 on error. (Even for errors like EACCES or ELOOP.)
            return 0;
        }
    }

设置文件最后更新的时间。传入参数是一个长整型毫秒数。需要注意的是,调用失败不会抛异常,所以要通过返回值来判断是否设置成功。返回true为设置成功,反之为false。

如果参数time小于0,会抛出非法参数异常。但是该方法不会抛出ErrnoException。

  public boolean setLastModified(long time) {
        if (time < 0) {
            throw new IllegalArgumentException("time < 0");
        }
        return setLastModifiedImpl(path, time);
    }

设置File属性为只读。

 public boolean setReadOnly() {
        return setWritable(false, false);
    }


设定文件属性为可执行文件或不可执行。实际上执行的是file.setExecutable(true, true);

public boolean setExecutable(boolean executable, boolean ownerOnly) {
        return doChmod(ownerOnly ? S_IXUSR : (S_IXUSR | S_IXGRP | S_IXOTH), executable);
    }


设置执行文件的权限。第一个参数设置文件是否可执行,第二参数设置该执行权利的对象是当前的调用还是对于整个文件系统。如果第二个参数设置为true,则表示当前权限只针对当前用户或当前调用,否则针对每个人或者之后的每次调用。如果底层文件系统不区分所有者,那第二个参数设置就没有意义。当且仅当操作成功时返回true。如果用户对该File没有访问权限,该方法的调用将失败。如果底层文件系统不支持该File的读权限且该File不可读,这个操作也会失败。注意:调用该方法失败不会抛IOException异常,所以设置成功与否要根据返回值确认。

public boolean setExecutable(boolean executable, boolean ownerOnly) {
        return doChmod(ownerOnly ? S_IXUSR : (S_IXUSR | S_IXGRP | S_IXOTH), executable);
    }

实际是调用setReadable(readable, true)方法

public boolean setReadable(boolean readable) {
        return setReadable(readable, true);
    }

设置该File是否为可写。第一个参数设置为true表示允许对该File的写入,反则为不允许。如果用户没有权限修改该File的操作权限,该调用失败。

 public boolean setWritable(boolean writable, boolean ownerOnly) {
        return doChmod(ownerOnly ? S_IWUSR : (S_IWUSR | S_IWGRP | S_IWOTH), writable);
    }

实际是调用setWritable(writable,true)方法。不同的是,设置File是否可写属性只针对当前用户。

 public boolean setWritable(boolean writable) {
        return setWritable(writable, true);
    }



返回此文件中的字节长度。如果文件不存在,返回0 。该方法对于目录没有意义,如果File是目录的话。
public long length() {
        try {
            return Libcore.os.stat(path).st_size;
        } catch (ErrnoException errnoException) {
            // The RI returns 0 on error. (Even for errors like EACCES or ELOOP.)
            return 0;
        }
    }

如果该File是一个目录,则返回当前目录中的所有文件的文件名数组。如果该File是一个文件,该方法无意义。
public String[] list() {
        return listImpl(path);
    }


过滤器功能。先获取该文件表示的目录中的所有文件列表,这个列表再通过FilenameFilter过滤器返回符合条件的文件名数组。如果FilenameFilter是空,则返回整个文件列表的文件名数组,如果该File是文件,不是一个目录,则该方法无意义,返回null;

 public String[] list(FilenameFilter filter) {
        String[] filenames = list();
        if (filter == null || filenames == null) {
            return filenames;
        }
        List<String> result = new ArrayList<String>(filenames.length);
        for (String filename : filenames) {
            if (filter.accept(this, filename)) {
                result.add(filename);
            }
        }
        return result.toArray(new String[result.size()]);
    }

返回file目录下的所有文件数组,如果file是文件则返回null。如果该file是绝对路径,那数组里的File之间是相对路径关系。该方法实际执行filenamesToFiles(list())方法,该方法是私有的。list()获取该File目录里的所有文件名数组,然后通过文件名数组创建new File的数组

public File[] listFiles() {
        return filenamesToFiles(list());
    }

将一个包含文件名的字符串数组转换成文件数组,且文件名不能有斜杠。

 private File[] filenamesToFiles(String[] filenames) {
        if (filenames == null) {
            return null;
        }
        int count = filenames.length;
        File[] result = new File[count];
        for (int i = 0; i < count; ++i) {
            result[i] = new File(this, filenames[i]);
        }
        return result;
    }


通过文件名过滤器filter得到该文件目录下符合条件的所有File,并以数组的形式返回。先调用lis(filter))得到所有符合条件的文件名并以字符串形式返回,再用文件名数组入参filenamesToFiles方法,上面讲到,这个方法就是遍历文件名数组,创建新的File,并将创建的File放在数组里保存并返回。如果参数filter是null或者调用该方法的File不是目录而是文件,则返回的结果均为null。

public File[] listFiles(FilenameFilter filter) {
        return filenamesToFiles(list(filter));
    }

通过文件过滤器filter得到该文件目录下符合条件的所有File。不同的是,该方法先得到该目录下的所有文件名并通过文件名创建新的File。之后再对这些文件进行过滤。新创建后过滤。而上面的方法,listFiles(filter)是先过滤再创建,且是根据文件名过滤,本方法是通过路径过滤。
public File[] listFiles(FileFilter filter) {
        File[] files = listFiles();//得到当前目录下的所有文件
        if (filter == null || files == null) {
            return files;
        }
        List<File> result = new ArrayList<File>(files.length);
        for (File file : files) {
            if (filter.accept(file)) {//当前目录下的文件是否包括在一个路径列表里
                result.add(file);
            }
        }
        return result.toArray(new File[result.size()]);
    }

创建该File目录。如果该File没有父目录,则可以调用mkdirs()创建,当然,根目录是不存在父目录/上级目录的。该方法调用失败不会抛IO异常,所以通过判断返回值来判断是否创建成功。如果该目录是存在的,也会返回false。该方法是调用mkdirErrno方法来实现目录的创建。且调用该方法需要用户授权写入权限。

public boolean mkdir() {
        try {
            mkdirErrno();
            return true;
        } catch (ErrnoException errnoException) {
            return false;
        }
    }

底层操作系统进行文件/目录的创建。

private void mkdirErrno() throws ErrnoException {
        // On Android, we don't want default permissions to allow global access.
        Libcore.os.mkdir(path, S_IRWXU);
    }


创建File的上级目录。由mkdirs(false)方法可知,如果直接创建目录失败,则根据错误类型调用对应方法。如果父目录不存在,会再一次调用getParenFile方法,如果父目录存在,返回false。
public boolean mkdirs() {
        return mkdirs(false);
    }

  private boolean mkdirs(boolean resultIfExists) {
        try {
            // Try to create the directory directly.
            mkdirErrno();
            return true;
        } catch (ErrnoException errnoException) {
            if (errnoException.errno == ENOENT) {
                // If the parent was missing, try to create it and then try again.
                File parent = getParentFile();
                return parent != null && parent.mkdirs(true) && mkdir();
            } else if (errnoException.errno == EEXIST) {
                return resultIfExists;
            }
            return false;
        }
    }


FileDescriptor:一个Unix文件描述符。

根据存储在文件中的路径信息创建一个文件系统上新的空的File。如果创建成功返回true,如果文件已经存在,返回false。注意,即使该文件是一个目录,也会返回false。该方法不常用,一般来说创建临时文件用createTempFile,对于文件的读写操作使用FileOutputStream和FileInputStream或者RandomAccessFile,这些方法都可以用于创建文件。注意:如果文件已经存在,该方法不会抛出IO异常,所以要通过返回值来判断是否创建成功,或者调用isFile来判断是否创建成功。

public boolean createNewFile() throws IOException {
        FileDescriptor fd = null;
        try {
            // On Android, we don't want default permissions to allow global access.
            fd = Libcore.os.open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
            return true;
        } catch (ErrnoException errnoException) {
            if (errnoException.errno == EEXIST) {
                // The file already exists.
                return false;
            }
            throw errnoException.rethrowAsIOException();
        } finally {
            IoUtils.close(fd); // TODO: should we suppress IOExceptions thrown here?
        }
    }


使用给定的perfix前缀和suffix后缀作为文件名的一部分,创建一个临时文件。如果参数suffix后缀是null,则临时文件的后缀为.tmp。

 public static File createTempFile(String prefix, String suffix) throws IOException {
        return createTempFile(prefix, suffix, null);
    }


使用给定的前缀和后缀创建一个临时文件。如果参数suffix是null,则默认后缀为.tmp,如果参数directory是null,则调用createTempFile(prefix,suffix)创建临时文件。注意:使用该方法创建临时文件,不能回调deleteOnExit方法。参数directory是临时文件被写入的路径,如果参数null,则默认路径,由"java.io.tmpdir"的系统属性设置,且该默认路径是可读写的。
public static File createTempFile(String prefix, String suffix, File directory)
            throws IOException {
        // Force a prefix null check first
        if (prefix.length() < 3) {//前缀的长度至少3位
            throw new IllegalArgumentException("prefix must be at least 3 characters");
        }
        if (suffix == null) {//后缀默认是.tmp
            suffix = ".tmp";
        }
        File tmpDirFile = directory;
        if (tmpDirFile == null) {
            String tmpDir = System.getProperty("java.io.tmpdir", ".");
            tmpDirFile = new File(tmpDir);
        }
        File result;
        do {
            result = new File(tmpDirFile, prefix + Math.randomIntInternal() + suffix);//前缀加随机数加后缀命名的临时文件
        } while (!result.createNewFile());
        return result;
    }


将此File重命名为newPath,此方法对目录/文件都有效。有可能造成重命名失败的原因:旧的路径和新的路径都要有写权限;对两个路径的所有父目录都有搜索权限即新旧路径的父目录都要可被搜索;两个路径都要在同一个安装点上,在Android中,最有可能在SD卡和内部存储之间有这点的限制。注意:该方法调用失败不会抛出IO异常,所以调用成功与否要通过返回值判断。
public boolean renameTo(File newPath) {
        try {
            Libcore.os.rename(path, newPath.path);
            return true;
        } catch (ErrnoException errnoException) {
            return false;
        }
    }

返回该File的简洁的,可读的描述符字符串。其实就是输出该File的路径。
 @Override
    public String toString() {
        return path;
    }


每个File都对应一个唯一的Uri值。Uri是依赖于系统的,与操作系统和文件系统不同的表示。

public URI toURI() {
        String name = getAbsoluteName();
        try {
            if (!name.startsWith("/")) {
                // start with sep.
                return new URI("file", null, "/" + name, null, null);
            } else if (name.startsWith("//")) {
                return new URI("file", "", name, null); // UNC path
            }
            return new URI("file", null, name, null, null);
        } catch (URISyntaxException e) {
            // this should never happen
            return null;
        }
    }


返回该File对应的URL。但是该方法不推荐使用。URL同样依赖于系统。如果File转换成URL失败,抛MalformedURLException异常。
@Deprecated
    public URL toURL() throws java.net.MalformedURLException {
        String name = getAbsoluteName();//得到File的绝对路径
        if (!name.startsWith("/")) {
            // start with sep.
            return new URL("file", "", -1, "/" + name, null);
        } else if (name.startsWith("//")) {
            return new URL("file:" + name); // UNC path
        }
        return new URL("file", "", -1, name, null);
    }

转换成绝对路径
 // TODO: is this really necessary, or can it be replaced with getAbsolutePath?
    private String getAbsoluteName() {
        File f = getAbsoluteFile();
        String name = f.getPath();
        if (f.isDirectory() && name.charAt(name.length() - 1) != separatorChar) {
            // Directories must end with a slash
            name = name + "/";
        }
        if (separatorChar != '/') { // Must convert slashes.
            name = name.replace(separatorChar, '/');
        }
        return name;
    }


返回该File所在分区的总字节大小。如果File的路径不存在,返回0。

 public long getTotalSpace() {
        try {
            StructStatVfs sb = Libcore.os.statvfs(path);
            return sb.f_blocks * sb.f_bsize; // total block count * block size in bytes.总字块*字节大小
        } catch (ErrnoException errnoException) {
            return 0;
        }
    }

返回该File所在分区的可用字节大小。如果File的路径不存在,返回0。但是该方法的计算值是乐观估计,实际上要比这个值小,所以不能作为程序可写入的大小。在Android系统以及其他基于Unix系统上,该方法的计算使适用于手机未root用户。

 public long getUsableSpace() {
        try {
            StructStatVfs sb = Libcore.os.statvfs(path);
            return sb.f_bavail * sb.f_bsize; // non-root free block count * block size in bytes.non-root可用字块*字节大小
        } catch (ErrnoException errnoException) {
            return 0;
        }
    }

返回该File所在分区的未使用字节大小。如果File的路径不存在,返回0。这是一个理论上的计算,实际上的可使用值比返回的值要小。该方法适用于不论手机是否root用户。

 public long getFreeSpace() {
        try {
            StructStatVfs sb = Libcore.os.statvfs(path);
            return sb.f_bfree * sb.f_bsize; // free block count * block size in bytes.
        } catch (ErrnoException errnoException) {
            return 0;
        }
    }





作者:qq_27570955 发表于2016/10/2 22:11:08 原文链接
阅读:28 评论:0 查看评论

Android-自定义图片

$
0
0

知识概览

  • BitmapDrawable 位图图片
  • ClipDrawable 裁剪图片
  • StateListDrawable 状态图片
  • TransitionDrawable 过渡图片
  • LayerListDrawable 层叠图片
  • LevelListDrawable 层级图片
  • InsetDrawable 插图图片
  • ShapeDrawable 形状图片
  • Nine-Patch File 9Patch图

BitmapDrawable

Bitmap图片主要是用Xml来创建一个位图文件,Xml能为Bitmap添加一些额外的属性 如“布局重力” 平铺模式。

语法:

<?xml version="1.0" encoding="utf-8"?>
<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@[package:]drawable/drawable_resource"
    android:antialias=["true" | "false"]
    android:dither=["true" | "false"]
    android:filter=["true" | "false"]
    android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
                      "fill_vertical" | "center_horizontal" | "fill_horizontal" |
                      "center" | "fill" | "clip_vertical" | "clip_horizontal"]
    android:mipMap=["true" | "false"]
    android:tileMode=["disabled" | "clamp" | "repeat" | "mirror"] />

元素解释

  • gravity:定义了图片的重力倾向,当图片比容器还小的时候,其指示了图片在容器中的位置。
  • tileMode:平铺模式
    • clamp 拉伸的是图片最后的那一个像素;横向的最后一个横行像素,不断的重复,纵项的那一列像素,不断的重复;
    • repeat 复制平铺
    • mirror 镜面平铺

示例

  1. 定义一张平铺的图片(该图片可以节省内存开销)
    这里写图片描述

ClipDrawable

ClipDrawable就是用来裁剪其他图片的位图 你可以通过控制裁剪宽高来决定裁剪

语法:

<?xml version="1.0" encoding="utf-8"?>
<clip
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/drawable_resource"
    android:clipOrientation=["horizontal" | "vertical"]
    android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
                     "fill_vertical" | "center_horizontal" | "fill_horizontal" |
                     "center" | "fill" | "clip_vertical" | "clip_horizontal"] />

元素解释

  • drawable 需要被处理的图片源
  • clipOrientation 裁剪的方向 horizontal从左到右 vertical从上到下
  • gravity 设置裁剪的开始位置
    • top 最上面开始
    • center 中间向两边扩张
    • bottom 从底部开始
    • left 从左边开始
    • right 从右边开始

示例:如下演示了一张图片如何从下到下慢慢展示的效果

  • 在res/drawable下创建my_clip.xml

    <?xml version="1.0" encoding="utf-8"?>
    <clip xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/round"
        android:clipOrientation="vertical"
        android:gravity="top"  />
    
  • 在layout布局中引用

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/my_clip" /> 
    
  • 在Activity中直接控制裁剪的层级

    private ImageView mImageview;
    private Handler mHandler=new Handler(){
    
        public void handleMessage(android.os.Message msg) {
            ClipDrawable drawable = (ClipDrawable) mImageview.getBackground();
            if (drawable.getLevel()!=10000) {
                drawable.setLevel(drawable.getLevel() + 500);
            }
        }
    
    };
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        mImageview = (ImageView) findViewById(R.id.image);
        new Thread(){
                public void run() {
                    while (true) {
                        SystemClock.sleep(50);
                        mHandler.sendEmptyMessage(0);   
                    }
                }
        }.start();
    
    }
    

StateListDrawable

StateListDrawable是一个定义在xml的图片用来在不同状态展示同一张图片的不同效果,如一个按钮有按下 聚焦等状态 你可以为每一个状态附带一张图片

你可以在XML文件中使用一系列的状态列表,每一个状态表现通过元素表现,该元素定义在元素内,每一个属性描述了某一个状态和对应的图片

在状态改变的过程中,系统会循环遍历每一个状态的,以寻求最好的匹配。注意,这里在遍历时只会取去当前状态相匹配的第一个Item,而不是最匹配的那一个。

语法:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:constantSize=["true" | "false"]
    android:dither=["true" | "false"]
    android:variablePadding=["true" | "false"] >
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:state_pressed=["true" | "false"]
        android:state_focused=["true" | "false"]
        android:state_hovered=["true" | "false"]
        android:state_selected=["true" | "false"]
        android:state_checkable=["true" | "false"]
        android:state_checked=["true" | "false"]
        android:state_enabled=["true" | "false"]
        android:state_activated=["true" | "false"]
        android:state_window_focused=["true" | "false"] />
</selector>

元素解释

  • drawable 每一个状态需要设置的图片
  • state_pressed 按下
  • state_focused 聚焦
  • state_selected 选择
  • state_checkable 勾选中
  • state_enabled 可用

示例

  • xml文件保存为res/drawable/button.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="true"
              android:drawable="@drawable/platlogo" /> <!-- pressed -->
        <item android:state_focused="true"
              android:drawable="@drawable/platlogo" /> <!-- focused -->
        <item android:drawable="@drawable/platlogo_alt" /> <!-- default -->
    </selector>
    
  • 在布局中应用

    <Button
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:background="@drawable/button" />
    

TransitionDrawable

  • 一个TransitionDrawable是一个特殊的Drawable对象,可以实现两个drawable资源之间淡入淡出的效果。
  • 每个drawable被定义在元素的标签中,最多只能有两个,如果向前过渡 调用startTransition()。如果向后过渡,调用reverseTransition()。

语法:

<?xml version="1.0" encoding="utf-8"?>
<transition
xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:id="@[+][package:]id/resource_name"
        android:top="dimension"
        android:right="dimension"
        android:bottom="dimension"
        android:left="dimension" />
</transition>

示例:有的时候你想做一个渐变的动画 比如你想让一张图片慢慢展现出来 此时你会想到使用Animation。但是这样循环调用会导致一开始图片就展示出来 这样就出现了问题。

  1. XML文件保存为:res/drawable/my_transaction.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <transition xmlns:android="http://schemas.android.com/apk/res/android" >
        <item android:drawable="@android:color/transparent" />
        <item android:drawable="@drawable/p6" />
    </transition>
    
  2. 在layout文件中使用:

    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/my_transaction"/>
    
  3. 如下代码可以让动画从第一张图片慢慢渐变到第二张图片 时间为1000毫秒

    ImageView iv = (ImageView) findViewById(R.id.iv);
    TransitionDrawable drawable = (TransitionDrawable) iv.getDrawable();
    drawable.startTransition(1000);
    

LayerListDrawable

该图片可将多张图片叠加在一起。最常用的地方就是Progress进度条的使用。

语法

<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:id="@[+][package:]id/resource_name"
        android:top="dimension"
        android:right="dimension"
        android:bottom="dimension"
        android:left="dimension" />
</layer-list>

元素解释

  • drawable引用的图片
  • top顶部偏移量
  • right bottom left 其余三个边的偏移量

示例

这里写图片描述

  1. XML文件保存为:res/drawable/my_layerlist.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@drawable/p2" />
        <item
            android:drawable="@drawable/p2"
            android:left="30dp"
            android:top="30dp"
            android:right="-30dp"
            android:bottom="-30dp"/>
        <item
            android:drawable="@drawable/p2"
            android:left="50dp"
            android:top="50dp"
            android:right="-50dp"
            android:bottom="-50dp"/>
    </layer-list>
    
  2. 在layout文件中使用:(因为前面的图片超过了原图右下边50个像素 所以这里的大小要比原图宽高分别多50dp)

    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingBottom="50dp"
        android:paddingRight="50dp"
        android:src="@drawable/my_layerlist" />
    

LevelListDrawable

LevelList是一个可以管理一批交替的位图,每一个位图通过android:maxLevel属性分配一个最大限度的数字。当调用LevelList的setLevel()方法的时候,他会从上到下匹配合适的图片并显示出来.

语法:

<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:drawable="@drawable/drawable_resource"
        android:maxLevel="integer"
        android:minLevel="integer" />
</level-list>

元素解释

  • 只能包含一个或者以上的
  • 定义一个位图在某一层级
    • android:maxLevel 该item的最高限度
    • android:minLevel 该item的最低限度

示例

  1. 在drawable文件夹下创建一个my_levellist图片

    <?xml version="1.0" encoding="utf-8"?>
    <level-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@drawable/p0" android:maxLevel="0"/>
        <item android:drawable="@drawable/p1" android:maxLevel="1"/>
        <item android:drawable="@drawable/p2" android:maxLevel="2"/>
        <item android:drawable="@drawable/p3" android:maxLevel="3"/>
        <item android:drawable="@drawable/p4" android:maxLevel="4"/>
        <item android:drawable="@drawable/p5" android:maxLevel="5"/>
        <item android:drawable="@drawable/p6" android:maxLevel="6"/>
        <item android:drawable="@drawable/p7" android:maxLevel="7"/>
        <item android:drawable="@drawable/p8" android:maxLevel="8"/>
    </level-list>
    
  2. 在布局文件下做一个引用

    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingBottom="50dp"
        android:paddingRight="50dp"
        android:src="@drawable/my_levellist" />
    
  3. 在代码中,每次点击一下则修改图片,当然 可以根据具体业务而定

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        mIv = (ImageView) findViewById(R.id.iv);
        mIv.setOnClickListener(this);
    }
    
    @Override
    public void onClick(View v) {
        LevelListDrawable drawable = (LevelListDrawable) mIv.getDrawable();
        mIndex=drawable.getLevel();
        mIndex++;
        if (mIndex>8){
            mIndex=0;
        }
        drawable.setLevel(mIndex);
    }
    

InsetDrawable

表示一个drawable嵌入到另外一个drawable内部,并且在内部留一些间距,这一点很像drawable的padding属性,区别在于 padding表示drawable的内容与drawable本身的边距,insetDrawable表示两个drawable和容器之间的边距。当控件需要的背景比实际的图片边框小的时候比较适合使用InsetDrawable。

语法:

<?xml version="1.0" encoding="utf-8"?>
<inset
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/drawable_resource"
    android:insetTop="dimension"
    android:insetRight="dimension"
    android:insetBottom="dimension"
    android:insetLeft="dimension" />

元素解释

  • 定义InsetDrawable的根元素
    • android:drawable 需要被扩展的图片
    • android:insetTop=”dimension” 在该插图上面设置一些间距 其他属性类似

示例:

这里写图片描述

ShapeDrawable

一个定义如矩形 椭圆 线 环形的图片。一般我们的圆角输入框就是它来定义的。

语法:

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape=["rectangle" | "oval" | "line" | "ring"]
    android:innerRadius="dimension"
    android:innerRadiusRatio="inetger"
    android:thickness="dimension"
    android:thicknessRatio="integer"
    android:useLevel="boolean" >
    <corners
        android:radius="integer"
        android:topLeftRadius="integer"
        android:topRightRadius="integer"
        android:bottomLeftRadius="integer"
        android:bottomRightRadius="integer" />
    <gradient
        android:angle="integer"
        android:centerX="float"
        android:centerY="float"
        android:centerColor="integer"
        android:endColor="color"
        android:gradientRadius="integer"
        android:startColor="color"
        android:type=["linear" | "radial" | "sweep"]
        android:useLevel=["true" | "false"] />
    <padding
        android:left="integer"
        android:top="integer"
        android:right="integer"
        android:bottom="integer" />
    <size
        android:width="integer"
        android:height="integer" />
    <solid
        android:color="color" />
    <stroke
        android:width="integer"
        android:color="color"
        android:dashWidth="integer"
        android:dashGap="integer" />
</shape>

元素解释

  • 一个形状图形的根元素

    • android:shape 形状 可以是rectangle矩形 oval椭圆形 line线 ring环形
    • android:innerRadius=”dimension” 环形可用 定义内部的原型半径
    • android:thickness=”dimension” 环形厚度
    • android:innerRadiusRatio=”inetger” 环形内部半径比率 使用此属性要先定义 如果size宽度是100dp, 而该值写5,那么厚度为20dp
    • android:thicknessRatio=”integer” 环形厚度比例 计算跟上面一样
    • android:useLevel 是否在levellistDrawable中使用。 一般是false
  • corners圆角

    • android:radius定义圆角半径 这里定义的是四个圆角半径
    • topLeftRadius topRightRadius bottomLeftRadius bottomRightRadius 分别定义四个角的圆角半径
  • size 定义内容的大小

  • solid 定义填充色
  • gradient 定义渐变颜色
    • startColor centerColor endColor 开始中间结束的渐变颜色
    • angle 定义渐变的角度
    • type= linear线性 radial放射式 sweep扫过
  • stroke
    • android:width 形状线的粗细
    • android:color 形状线的颜色
  • padding 内边距

示例:环形

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:innerRadiusRatio="3.3"
    android:shape="ring"
    android:thicknessRatio="5"
    android:useLevel="false" >

    <gradient
        android:angle="45"
        android:startColor="#FFFF0000"
        android:centerColor="#FF00FF00"
        android:endColor="#FF0000FF"
        android:type="sweep" />

    <stroke
        android:width="1dp"
        android:color="@android:color/black" />

    <size
        android:height="50dp"
        android:width="50dp" />

</shape>

示例:圆角背景

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

    <stroke
        android:width="1dp"
        android:color="@android:color/black" />

    <solid android:color="@android:color/white" />

    <corners android:radius="4dp" />

</shape>

Nine-Patch File

Nine-Patch File

文件位置

res/drawable/filename.9.png

示例

With an image saved at res/drawable/myninepatch.9.png, this layout XML applies the Nine-Patch to a View:

<Button
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:background="@drawable/myninepatch" />
作者:qq285016127 发表于2016/10/2 22:26:55 原文链接
阅读:82 评论:0 查看评论

Android MVP

$
0
0

前言

前段时间,公司由个同事分享的时候,提到了MVP模式,自己之前也了解过,但是真正在自己的编码过程中使用的非常少。最近在帮助一个朋友做毕业设计,心想这是一个很好的机会练习一把。网上也找了很多有关MVP的博客,说的也都差不多,就想找一个比较权威的,当然应该是google官网啦,就找到了Google在Github上开源项目,真找到了MVP例子,就记一篇博文,慢慢回味。

Android MVP

MVP(MVP模式):一种软件设计模式
全称为Model-View-Presenter,Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理。MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。
goto 维基百科
goto 百度百科
MVP模式,一方面是体现单一职责原则,降低Android中类之间逻辑的耦合,使之高内聚低耦合;二是让代码更清晰,更简洁,但是简洁并不代表代码少,更易扩展等。
#Android MVP(Google Samples/aandroid-architecture)
前言中提到过,Android官方提供了MVP Sample,这也是Google发现问题,给开发者提供的一种思路吧。
goto Google Sample
点击上述链接,可以看到Google官方为大家推荐的应用开发框架,Android 架构蓝图,当然有心的你还会从Google Github发现更多有用的开源项目。

概述

这里写图片描述
简单翻译下(如有不妥,欢迎评论提意见哈)
在组织和构建Android app时,虽然Android框架提供了很大的灵活性,这种灵活性,非常有价值,但是也可能导致app出现非常多的类,命名不一致,缺乏整体测试架构,难以维护和扩展等问题,
所有提供开发者一些设计架构模式,解决这些问题。安卓架构蓝图项目是为了证明可能的方式来帮助解决这些常见的问题。在这个项目中,提供使用不同的架构和工具实现相同的应用程序。
官方只是将这些作为参考,具体是否合适大家放入项目中,就要具体情况具体分析了,重点是代码结构,体系结构,方便测试和可维护,可拓展性。

Samples

在android-architecture项目下,提供了7个例子[稳定版],分别对应项目不同的分支下,大家可以下载下来,跑一跑,看一看。
1. todo-mvp/ - Basic Model-View-Presenter architecture.
2. todo-mvp-loaders/ - Based on todo-mvp, fetches data using Loaders.
3. todo-databinding/ - Based on todo-mvp, uses the Data Binding Library.
4. todo-mvp-clean/ - Based on todo-mvp, uses concepts from Clean Architecture.
5. todo-mvp-dagger/ - Based on todo-mvp, uses Dagger2 for Dependency Injection.
6.todo-mvp-contentproviders/ - Based on todo-mvp-loaders, fetches data using Loaders and uses Content Providers.
7.todo-mvp-rxjava/ - Based on todo-mvp, uses RxJava for concurrency and data layer abstraction.
8.todo-mvp-tablet/
很清晰的可以看出,Sample1就是单纯的介绍MVP设计模式的,2-7是在1的基础上,添加了一些快速开发的一些框架,本文主要记录MVP,其他将后续跟进; 8官方正在开发中,应该还不算稳定版本。

TODO-MVP

todo-mvp

如何下载,以及运行参考官方文档即可,这个也是学习的一个过程。
它展示了一个简单的MVP模式,没有使用任何框架和架构。它使用手动依赖注入,以提供一个本地和远程数据源的存储库。异步任务处理回调。
首先看下整体框架,左半边是Model,右边是View和Presenter,官方善意的注释了下,图片中的VIEW并不是android框架中的View,是MVP场景中的一个抽象,在代码中就能体会到。途中蓝色字体相当于抽象概念。
之所以使用Fragment:

  • Activity与fragment的分离,非常符合MVP的实现:Activity是创建并连接Views和Presenter的整体控制器
  • Table layout和多视图界面使用Fragment框架将非常有优势
    图1

依赖

  • Common Android support libraries (com.android.support.*)
  • Android Testing Support Library (Espresso, AndroidJUnitRunner…)
  • Mockito
  • Guava (null checking)

源码分析

首先看下整体的源码结构:
这里写图片描述

从上图可以很清晰的看出整个app的功能模块,包括(Tasks, AddEditTask, TaskDetail, Statistics)
每个功能包含四大文件:Activity, Fragment, Contract, Presenter
就像上述提及的Activity相当于一个控制器;Fragment相当于MVP中的View;Contract是一个功能中View和Presenter间的协议,通俗的理解就是,这两个是协同工作的;而Presenter就是具体的业务逻辑实现代码。首先会根据当前的功能模块进行分解成多个业务逻辑,然后制定View和Presenter的协议。

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

以Task功能来分析下具体的MVP模式的实现:
TasksContract:主要是定义相关的业务逻辑接口,还有UI的更新接口,可以很清晰的查看该功能界面的具体业务功能,不过当页面比较复杂时,这个文件会不会很大???或者说我们就不该把一个页面搞太复杂。网上看过不少例子,都没有提到这个文件,个人感觉这个Contract还是很重要的!!!

/**
 * 指定View和Presenter之间的协议
 */
public interface TasksContract {

    interface View extends BaseView<Presenter> {

        void setLoadingIndicator(boolean active);

        void showTasks(List<Task> tasks);

        void showAddTask();

        void showTaskDetailsUi(String taskId);

        void showTaskMarkedComplete();

        void showTaskMarkedActive();

        void showCompletedTasksCleared();

        void showLoadingTasksError();

        void showNoTasks();

        void showActiveFilterLabel();

        void showCompletedFilterLabel();

        void showAllFilterLabel();

        void showNoActiveTasks();

        void showNoCompletedTasks();

        void showSuccessfullySavedMessage();

        boolean isActive();

        void showFilteringPopUpMenu();
    }

    interface Presenter extends BasePresenter {

        void result(int requestCode, int resultCode);

        void loadTasks(boolean forceUpdate);

        void addNewTask();

        void openTaskDetails(@NonNull Task requestedTask);

        void completeTask(@NonNull Task completedTask);

        void activateTask(@NonNull Task activeTask);

        void clearCompletedTasks();

        void setFiltering(TasksFilterType requestType);

        TasksFilterType getFiltering();
    }
}

TasksActivity:主要是创建View(TasksFragment), 创建Presenter(TasksFragment), 并将View在Presenter构造过程中传递过去。

        TasksFragment tasksFragment =
                (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
        if (tasksFragment == null) {
            // Create the fragment
            tasksFragment = TasksFragment.newInstance();
            ActivityUtils.addFragmentToActivity(
                    getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
        }

        // Create the presenter
        mTasksPresenter = new TasksPresenter(
                Injection.provideTasksRepository(getApplicationContext()), tasksFragment);

TasksFragment:进行Presenter的初始化, 根据用户交互,调用View接口定义的相关协议接口,执行Presenter中定义的逻辑接口

/**
 * Display a grid of {@link Task}s. User can choose to view all, active or completed tasks.
 */
public class TasksFragment extends Fragment implements TasksContract.View {

    private TasksContract.Presenter mPresenter;

    ...

    @Override
    public void onResume() {
        super.onResume();
        mPresenter.start();
    }

    @Override
    public void setPresenter(@NonNull TasksContract.Presenter presenter) {
        mPresenter = checkNotNull(presenter);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        mPresenter.result(requestCode, resultCode);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        mNoTaskAddView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showAddTask();
            }
        });
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.addNewTask();
            }
        });

        // Set up progress indicator
        final ScrollChildSwipeRefreshLayout swipeRefreshLayout =
                (ScrollChildSwipeRefreshLayout) root.findViewById(R.id.refresh_layout);
        swipeRefreshLayout.setColorSchemeColors(
                ContextCompat.getColor(getActivity(), R.color.colorPrimary),
                ContextCompat.getColor(getActivity(), R.color.colorAccent),
                ContextCompat.getColor(getActivity(), R.color.colorPrimaryDark)
        );
        // Set the scrolling view in the custom SwipeRefreshLayout.
        swipeRefreshLayout.setScrollUpChild(listView);

        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                mPresenter.loadTasks(false);
            }
        });

        setHasOptionsMenu(true);

        return root;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_clear:
                mPresenter.clearCompletedTasks();
                break;
            case R.id.menu_filter:
                showFilteringPopUpMenu();
                break;
            case R.id.menu_refresh:
                mPresenter.loadTasks(true);
                break;
        }
        return true;
    }
    @Override
    public void showFilteringPopUpMenu() {
        PopupMenu popup = new PopupMenu(getContext(), getActivity().findViewById(R.id.menu_filter));
        popup.getMenuInflater().inflate(R.menu.filter_tasks, popup.getMenu());

        popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.active:
                        mPresenter.setFiltering(TasksFilterType.ACTIVE_TASKS);
                        break;
                    case R.id.completed:
                        mPresenter.setFiltering(TasksFilterType.COMPLETED_TASKS);
                        break;
                    default:
                        mPresenter.setFiltering(TasksFilterType.ALL_TASKS);
                        break;
                }
                mPresenter.loadTasks(false);
                return true;
            }
        });

        popup.show();
    }

    /**
     * Listener for clicks on tasks in the ListView.
     */
    TaskItemListener mItemListener = new TaskItemListener() {
        @Override
        public void onTaskClick(Task clickedTask) {
            mPresenter.openTaskDetails(clickedTask);
        }

        @Override
        public void onCompleteTaskClick(Task completedTask) {
            mPresenter.completeTask(completedTask);
        }

        @Override
        public void onActivateTaskClick(Task activatedTask) {
            mPresenter.activateTask(activatedTask);
        }
    };

    ...

    @Override
    public void showTasks(List<Task> tasks) {
        mListAdapter.replaceData(tasks);

        mTasksView.setVisibility(View.VISIBLE);
        mNoTasksView.setVisibility(View.GONE);
    }

    @Override
    public void showNoActiveTasks() {
        showNoTasksViews(
                getResources().getString(R.string.no_tasks_active),
                R.drawable.ic_check_circle_24dp,
                false
        );
    }

    @Override
    public void showNoTasks() {
        showNoTasksViews(
                getResources().getString(R.string.no_tasks_all),
                R.drawable.ic_assignment_turned_in_24dp,
                false
        );
    }

    @Override
    public void showNoCompletedTasks() {
        showNoTasksViews(
                getResources().getString(R.string.no_tasks_completed),
                R.drawable.ic_verified_user_24dp,
                false
        );
    }

    @Override
    public void showSuccessfullySavedMessage() {
        showMessage(getString(R.string.successfully_saved_task_message));
    }

    private void showNoTasksViews(String mainText, int iconRes, boolean showAddView) {
        mTasksView.setVisibility(View.GONE);
        mNoTasksView.setVisibility(View.VISIBLE);

        mNoTaskMainView.setText(mainText);
        mNoTaskIcon.setImageDrawable(getResources().getDrawable(iconRes));
        mNoTaskAddView.setVisibility(showAddView ? View.VISIBLE : View.GONE);
    }

    @Override
    public void showActiveFilterLabel() {
        mFilteringLabelView.setText(getResources().getString(R.string.label_active));
    }

    @Override
    public void showCompletedFilterLabel() {
        mFilteringLabelView.setText(getResources().getString(R.string.label_completed));
    }

    @Override
    public void showAllFilterLabel() {
        mFilteringLabelView.setText(getResources().getString(R.string.label_all));
    }

    @Override
    public void showAddTask() {
        Intent intent = new Intent(getContext(), AddEditTaskActivity.class);
        startActivityForResult(intent, AddEditTaskActivity.REQUEST_ADD_TASK);
    }

    @Override
    public void showTaskDetailsUi(String taskId) {
        // in it's own Activity, since it makes more sense that way and it gives us the flexibility
        // to show some Intent stubbing.
        Intent intent = new Intent(getContext(), TaskDetailActivity.class);
        intent.putExtra(TaskDetailActivity.EXTRA_TASK_ID, taskId);
        startActivity(intent);
    }

    @Override
    public void showTaskMarkedComplete() {
        showMessage(getString(R.string.task_marked_complete));
    }

    @Override
    public void showTaskMarkedActive() {
        showMessage(getString(R.string.task_marked_active));
    }

    @Override
    public void showCompletedTasksCleared() {
        showMessage(getString(R.string.completed_tasks_cleared));
    }

    @Override
    public void showLoadingTasksError() {
        showMessage(getString(R.string.loading_tasks_error));
    }

    private void showMessage(String message) {
        Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show();
    }

    @Override
    public boolean isActive() {
        return isAdded();
    }
    ...
}

TasksPresenter:监听用户的UI操作,实现Presenter协议中定义的业务逻辑,和Model层进行交换,然后更新UI。

/**
 * Listens to user actions from the UI ({@link TasksFragment}), retrieves the data and updates the
 * UI as required.
 */
public class TasksPresenter implements TasksContract.Presenter {
    ...
    public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
        mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");

        ...

其中TasksContract.View实现了BaseView,实现setPresenter接口
TasksContract.Presenter实现了BasePresenter,实现了start接口

MyMVP

//TODO

参考

1.Android官方资料

作者:libing1991_ 发表于2016/10/2 22:30:19 原文链接
阅读:66 评论:0 查看评论

Android动画深入分析

$
0
0

动画分类

Android动画可以分3种:View动画,帧动画和属性动画;属性动画为API11的新特性,在低版本是无法直接使用属性动画的,但可以用nineoldAndroids来实现(但是本质还是viiew动画)。学习本篇内容主要掌握以下知识:

1,View动画以及自定义View动画。
2,View动画的一些特殊使用场景。
3,对属性动画做了一个全面的介绍。
4,使用动画的一些注意事项。


view动画

  • View动画的四种变换效果对应着Animation的四个子类:TranslateAnimation(平移动画)、ScaleAnimation(缩放动画)、RotateAnimation(旋转动画)和AlphaAnimation(透明度动画),他们即可以用代码来动态创建也可以用XML来定义,推荐使用可读性更好的XML来定义。
  • <set>标签表示动画集合,对应AnimationSet类,它可以包含若干个动画,并且他的内部也可以嵌套其他动画集合。android:interpolator 表示动画集合所采用的插值器,插值器影响动画速度,比如非匀速动画就需要通过插值器来控制动画的播放过程。
    android:shareInterpolator表示集合中的动画是否和集合共享同一个插值器,如果集合不指定插值器,那么子动画就需要单独指定所需的插值器或默认值。
  • Animation通过setAnimationListener方法可以给View动画添加过程监听。
  • 自定义View动画只需要继承Animation这个抽象类,并重写initialize和applyTransformation方法,在initialize方法中做一些初始化工作,在applyTransformation中进行相应的矩形变换,很多时候需要采用Camera来简化矩形变换过程。
  • 帧动画是顺序播放一组预先定义好的图片,类似电影播放;使用简单但容易引发OOM,尽量避免使用过多尺寸较大的图片。

  • view动画应用场景

    LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,当他的子元素出场的时候都会具有这种动画,ListView上用的多,LayoutAnimation也是一个View动画。
    代码实现:

    <?xml version="1.0" encoding="utf-8"?>
    <layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
     android:animationOrder="normal"
     android:delay="0.3" android:animation="@anim/anim_item"/>
    
    //--- animationOrder 表示子元素的动画的顺序,有三种选项:
    //normal(顺序显示)、reverse(逆序显示)和random(随机显示)。
    
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
     android:duration="300"
     android:shareInterpolator="true">
     <alpha
         android:fromAlpha="0.0"
         android:toAlpha="1.0" />
     <translate
         android:fromXDelta="300"
         android:toXDelta="0" />
    </set>
    第一种,在布局中引用LayoutAnimation
    <ListView
         android:id="@+id/lv"
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1"
         android:layoutAnimation="@anim/anim_layout"/>
    第二种,代码种使用
    Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
    LayoutAnimationController controller = new LayoutAnimationController(animation);
    controller.setDelay(0.5f);
    controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
    listview.setLayoutAnimation(controller);

    帧动画

      逐帧动画(Frame-by-frame Animations)从字面上理解就是一帧挨着一帧的播放图片,类似于播放电影的效果。不同于View动画,Android系统提供了一个类AnimationDrawable来实现帧动画,帧动画比较简单,我们看一个例子就行了。
    <?xml version="1.0" encoding="utf-8"?>
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="false">
    
        <item
            android:drawable="@mipmap/lottery_1"
            android:duration="200" />
      // ...省略很多
        <item
            android:drawable="@mipmap/lottery_6"
            android:duration="200" />
    
    </animation-list>

    然后
    imageView.setImageResource(R.drawable.frame_anim);
    AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
    animationDrawable.start();//启动start,关闭stop

    属性动画

    属性动画是Android 3.0新加入(api 11)的功能,不同于之前的view动画(看过的都知道,view动画比如实现的位移其实不是真正的位置移动,只是实现了一些简单的视觉效果)。属性动画对之前的动画做了很大的拓展,毫不夸张的说,属性动画可以实现任何动画效果,因为在作用的对象是属性(对象),属性动画中有几个概念需要我们注意下,

    ValueAnimator、ObjectAnimator、AnimatorSet等。

    属性动画作用属性

    1,属性动画可以对任意对象的属性进行动画而不仅仅是View,属性动画默认间隔300ms,默认帧率10ms/帧。
    2,看一段代码
    <set
      android:ordering=["together" | "sequentially"]>
    
        <objectAnimator
            android:propertyName="string"
            android:duration="int"
            android:valueFrom="float | int | color"
            android:valueTo="float | int | color"
            android:startOffset="int"
            android:repeatCount="int"
            android:repeatMode=["repeat" | "reverse"]
            android:valueType=["intType" | "floatType"]/>
    
        <animator
            android:duration="int"
            android:valueFrom="float | int | color"
            android:valueTo="float | int | color"
            android:startOffset="int"
            android:repeatCount="int"
            android:repeatMode=["repeat" | "reverse"]
            android:valueType=["intType" | "floatType"]/>
    
        <set>
            ...
        </set>
    </set>

    <set>
    它代表的就是一个AnimatorSet对象。里面有一个 ordering属性,主要是指定动画的播放顺序。

    <objectAnimator> 
    它表示一个ObjectAnimator对象。它里面有很多属性,我们重点需要了解的也是它。
    android:propertyName -------属性名称,例如一个view对象的”alpha”和”backgroundColor”。
    android:valueFrom   --------变化开始值
    android:valueTo ------------变化结束值
    android:valueType -------变化值类型 ,它有两种值:intType和floatType,默认值floatType。
    android:duration ---------持续时间
    android:startOffset ---------动画开始延迟时间
    android:repeatCount --------重复次数,-1表示无限重复,默认为-1
    android:repeatMode 重复模式,前提是android:repeatCount为-1 ,它有两种值:”reverse”和”repeat”,分别表示反向和顺序方向。

    <animator>
    它对应的就是ValueAnimator对象。它主要有以下属性。
    android:valueFrom
    android:valueTo
    android:duration
    android:startOffset
    android:repeatCount
    android:repeatMode
    android:valueType

    定义了一组动画之后,我们怎么让它运行起来呢?
    AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
        R.anim.property_animator);
    set.setTarget(myObject);//myObject表示作用的对象
    set.start();

    插值器和估值器

    时间插值器(TimeInterpolator)的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有LinearInterpolator(线性插值器:匀速动画),AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快),DecelerateInterpolator(减速插值器:动画越来越慢)。

    注:这里的插值器很多,可以翻看我之前关于插值器的讲解。

    估值器(TypeEvaluator)的作用是根据当前属性改变的百分比来计算改变后的属性值。系统预置有IntEvaluator 、FloatEvaluator 、ArgbEvaluator。

    举个简单的例子吧
    public class IntEvaluator implements TypeEvaluator<Integer> {
     public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
         int startInt = startValue;
         return (int)(startInt + fraction * (endValue - startInt));
     }
    }
    上述代码就是计算当前属性所占总共的百分百。

    插值器和估值器除了系统提供之外,我们还可以自定义实现,自定义插值器需要实现Interpolator或者TimeInterpolator;自定义估值器算法需要实现TypeEvaluator。

    属性动画监听器

    属性动画监听器用于监听动画的播放过程,主要有两个接口:AnimatorUpdateListener和AnimatorListener 。
    AnimatorListener 
    public static interface AnimatorListener {
        void onAnimationStart(Animator animation); //动画开始
        void onAnimationEnd(Animator animation); //动画结束
        void onAnimationCancel(Animator animation); //动画取消
        void onAnimationRepeat(Animator animation); //动画重复播放
    }
    AnimatorUpdateListener
    public static interface AnimatorUpdateListener {
        void onAnimationUpdate(ValueAnimator animator);
    }

    应用场景

    这里我们先提一个问题:给Button加一个动画,让Button在2秒内将宽带从当前宽度增加到500dp,也行你会说,很简单啊,直接用view动画就可以实现,view动画不是有个缩放动画,但是你可以试试,view动画是不支持对宽度和高度进行改变的。Button继承自TextView,setWidth是对TextView的,所以直接对Button做setWidth是不行的。那么要怎么做呢?
    针对上面的问题,官网api给出了如下的方案:
    • 给你的对象加上get和set方法,如果你有权限的话
    • 用一个类来包装原始对象,间接提高get和set方法
    • 采用ValueAnimator,监听动画执行过程,实现属性的改变

    有了上面的说明,我们大致明白了,要实现开始说的这个问题的效果,我们需要用一个间接的类来实现get和set方法或者自己实现一个ValueAnimator。
    第一种,自己封装一个类实现get和set方法,这也是我们常用的,拓展性强

    public class ViewWrapper {
     private View target;
     public ViewWrapper(View target) {
         this.target = target;
     }
     public int getWidth() {
         return target.getLayoutParams().width;
     }
     public void setWidth(int width) {
         target.getLayoutParams().width = width;
         target.requestLayout();
     }
    }

    第二种,采用ValueAnimator,监听动画过程。

    private void startValueAnimator(final View target, final int start, final int end) {
       ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
       valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
           private IntEvaluator mEvaluation = new IntEvaluator();//新建一个整形估值器作为临时变量
    
           @Override
           public void onAnimationUpdate(ValueAnimator animation) {
               //获得当前动画的进度值 1~100之间
               int currentValue = (int) animation.getAnimatedValue();
               //获得当前进度占整个动画过程的比例,浮点型,0~1之间
               float fraction = animation.getAnimatedFraction();
               //调用估值器,通过比例计算出宽度 
               int targetWidth = mEvaluation.evaluate(fraction, start, end);
               target.getLayoutParams().width = targetWidth;
               //设置给作用的对象,刷新页面
               target.requestLayout();
           }
       });
    }

    属性动画的工作原理

    属性动画的工作原理,主要是对作用的对象不断的调用get/set方法来改变初始值和最终值,然后set到动画属性上即可。然后通过系统的消息机制(Handler和Looper去将动画执行出来)。比如我们调用ObjectAnimator.start()
    private void start(boolean playBackwards) {
            if(Looper.myLooper() == null) {
                throw new AndroidRuntimeException("Animators may only be run on Looper threads");
            } else {
                this.mPlayingBackwards = playBackwards;
                this.mCurrentIteration = 0;
                this.mPlayingState = 0;
                this.mStarted = true;
                this.mStartedDelay = false;
                ((ArrayList)sPendingAnimations.get()).add(this);
                if(this.mStartDelay == 0L) {
                    this.setCurrentPlayTime(this.getCurrentPlayTime());
                    this.mPlayingState = 0;
                    this.mRunning = true;
                    if(this.mListeners != null) {
                        ArrayList animationHandler = (ArrayList)this.mListeners.clone();
                        int numListeners = animationHandler.size();
    
                        for(int i = 0; i < numListeners; ++i) {
                            ((AnimatorListener)animationHandler.get(i)).onAnimationStart(this);
                        }
                    }
                }
    
                ValueAnimator.AnimationHandler var5 = (ValueAnimator.AnimationHandler)sAnimationHandler.get();
                if(var5 == null) {
                    var5 = new ValueAnimator.AnimationHandler(null);
                    sAnimationHandler.set(var5);
                }
    
                var5.sendEmptyMessage(0);
            }
        }

     private static final ThreadLocal<ArrayList<ValueAnimator>> sAnimations = new ThreadLocal() {
            protected ArrayList<ValueAnimator> initialValue() {
                return new ArrayList();
            }
        };
        private static final ThreadLocal<ArrayList<ValueAnimator>> sPendingAnimations = new ThreadLocal() {
            protected ArrayList<ValueAnimator> initialValue() {
                return new ArrayList();
            }
        };
        private static final ThreadLocal<ArrayList<ValueAnimator>> sDelayedAnims = new ThreadLocal() {
            protected ArrayList<ValueAnimator> initialValue() {
                return new ArrayList();
            }
        };
        private static final ThreadLocal<ArrayList<ValueAnimator>> sEndingAnims = new ThreadLocal() {
            protected ArrayList<ValueAnimator> initialValue() {
                return new ArrayList();
            }
        };
        private static final ThreadLocal<ArrayList<ValueAnimator>> sReadyAnims = new ThreadLocal() {
            protected ArrayList<ValueAnimator> initialValue() {
                return new ArrayList();
            }
        };

    这里就不做具体的分析了,大家可以去看看源码。

    使用属性动画需要注意的事项

  • 使用帧动画时,当图片数量较多且图片分辨率较大的时候容易出现OOM,需注意,尽量避免使用帧动画。
  • 使用无限循环的属性动画时,在Activity退出时即使停止,否则将导致Activity无法释放从而造成内存泄露
  • View动画是对View的影像做动画,并不是真正的改变了View的状态,因此有时候会出现动画完成后View无法隐藏(setVisibility(View.GONE)失效),这时候调用view.clearAnimation()清理View动画即可解决。
  • 不要使用px,使用px会导致不同设备上有不同的效果。
  • View动画是对View的影像做动画,View的真实位置没有变动,也就导致点击View动画后的位置触摸事件不会响应,属性动画不存在这个问题。
  • 使用动画的过程中,使用硬件加速可以提高动画的流畅度。
  • 动画在3.0以下的系统存在兼容性问题,特殊场景可能无法正常工作,需做好适配工作。












  • 作者:xiangzhihong8 发表于2016/10/2 23:09:54 原文链接
    阅读:153 评论:0 查看评论

    FTP连接,上传,下载,删除文件方法

    $
    0
    0

    解决的两个问题
    1.设置连接超时时间,如下:
    ftp.setConnectTimeout(3*1000);//设置连接ftp超时时间3秒

    2.文件下载后,出现文件中中文乱码的情况,解决办法是,设置编码格式,如下:
    ftp.setFileType(FTP.BINARY_FILE_TYPE);//定义编码格式 防止文件中的中文出现乱码

    以下列出测试可用的源码:
    LoadConfigs

    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.Reader;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    
    public class LoadConfigs {
        /**
         * 系统的配置文件类路径
         */
        public static final String CONFIG_PATH = "/conf/config.properties";
    
        private static Properties configs=new Properties();;
    
        private static String propertiesPath = ResourceUtil.getAbsolutePath(CONFIG_PATH);
    
        private static void initConfigs(){
            try {
                Reader inStream = new InputStreamReader(new FileInputStream(propertiesPath), "UTF-8");
                configs.load(inStream);
            }catch (IOException e){
                System.err.println("load config.properties error");
            }
        }
    
        public static Map getConfig(){
            initConfigs();
            Map<String,String> configMap = new HashMap<String, String>();
            configMap.put("ftpServer.backup.host.ip",configs.getProperty("ftpServer.backup.host.ip"));
            configMap.put("ftpServer.backup.host.port",configs.getProperty("ftpServer.backup.host.port"));
            configMap.put("ftpServer.backup.host.account",configs.getProperty("ftpServer.backup.host.account"));
            configMap.put("ftpServer.backup.host.pwd",configs.getProperty("ftpServer.backup.host.pwd"));
            configMap.put("ftpServer.backup.host.path",configs.getProperty("ftpServer.backup.host.path"));
            configMap.put("linuxOS.backup.local.path",configs.getProperty("linuxOS.backup.local.path"));
            configMap.put("winOS.backup.local.path",configs.getProperty("winOS.backup.local.path"));
            return configMap;
        }
    
        public static String getConfig(String key){
            return configs.getProperty(key);
        }
    
        public enum OS {
            WIN, LINUX, OTHER
        }
    
        public static OS checkOSType() {
            String osName = System.getProperty("os.name");
            if (osName.toLowerCase().startsWith("win")) {
                return OS.WIN;
            } else if (osName.toLowerCase().startsWith("lin")) {
                return OS.LINUX;
            } else {
                return OS.OTHER;
            }
        }
    
    }

    ResourceUtil

    import java.io.*;
    import java.net.URISyntaxException;
    import java.net.URL;
    
    public class ResourceUtil {
    
        /**
         * 工程内资源到文件系统的拷贝。
         * 从类路径到文件系统路径
         *
         * @param resourceAbsoluteClassPath 绝对类路径
         * @param targetFile                目标文件
         * @throws java.io.IOException
         */
        public static void copyResourceToFile(String resourceAbsoluteClassPath,
                                              File targetFile) throws IOException {
            InputStream is = ResourceUtil.class.getResourceAsStream(resourceAbsoluteClassPath);
            if (is == null) {
                throw new IOException("Resource not found! " + resourceAbsoluteClassPath);
            }
            OutputStream os = null;
            try {
                os = new FileOutputStream(targetFile);
                byte[] buffer = new byte[2048];
                int length;
                while ((length = is.read(buffer)) != -1) {
                    os.write(buffer, 0, length);
                }
                os.flush();
            } finally {
                try {
                    is.close();
                    if (os != null) {
                        os.close();
                    }
                } catch (Exception ignore) {
                    // ignore
                }
            }
        }
    
        public static String getAbsolutePath(String classPath) {
            URL configUrl = ResourceUtil.class.getResource(classPath);
            if (configUrl == null) {
                return null;
            }
            try {
                return configUrl.toURI().getPath();
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }
        }
    
    }

    config.properties

    #ftp日志备份相关配置
    ftpServer.backup.host.ip=192.168.20.170
    ftpServer.backup.host.port=21
    ftpServer.backup.host.account=kkk
    ftpServer.backup.host.pwd=kkkkkk
    ftpServer.backup.host.path=/backup/log/
    linuxOS.backup.local.path=/usr/local/backup/log/
    winOS.backup.local.path=e:\\

    FTPTool

    import java.io.*;
    import java.util.Map;
    
    import org.apache.commons.net.ftp.FTP;
    import org.apache.commons.net.ftp.FTPClient;
    import org.apache.commons.net.ftp.FTPClientConfig;
    import org.apache.commons.net.ftp.FTPFile;
    import org.apache.commons.net.ftp.FTPReply;
    import wst.phone.util.LoadConfigs;
    
    public class FtpTool {
    
        private static LoadConfigs.OS osType = LoadConfigs.checkOSType();
    
        //    //ftp常量
        private static Map configMap = LoadConfigs.getConfig();
    
        private static String FTP_BACKUP_HOST_IP = (String) configMap.get("ftpServer.backup.host.ip");
        private static String FTP_BACKUP_HOST_PORT = (String) configMap.get("ftpServer.backup.host.port");
        private static String FTP_BACKUP_HOST_ACCOUNT = (String) configMap.get("ftpServer.backup.host.account");
        private static String FTP_BACKUP_HOST_PWD = (String) configMap.get("ftpServer.backup.host.pwd");
        private static String FTP_BACKUP_HOST_PATH = (String) configMap.get("ftpServer.backup.host.path");
        private static String LINUX_BACKUP_LOCAL_PATH = (String) configMap.get("linuxOS.backup.local.path");
        private static String WIN_BACKUP_LOCAL_PATH = (String) configMap.get("winOS.backup.local.path");
        private static String BACKUP_LOCAL_PATH = (osType == LoadConfigs.OS.LINUX ? LINUX_BACKUP_LOCAL_PATH : WIN_BACKUP_LOCAL_PATH);
    
        /**
         * 获得连接-FTP方式
         *
         * @param hostIp   FTP服务器地址
         * @param port     FTP服务器端口
         * @param userName FTP登录用户名
         * @param passWord FTP登录密码
         * @return FTPClient
         */
        public FTPClient getConnectionFTP(String hostIp, int port, String userName, String passWord) throws Exception{
            //创建FTPClient对象
            FTPClient ftp = new FTPClient();
            ftp.setConnectTimeout(3*1000);//设置连接ftp超时时间3秒
            try {
                //连接FTP服务器
                ftp.connect(hostIp, port);
    
                //下面三行代码必须要,而且不能改变编码格式,否则不能正确下载中文文件
                ftp.setControlEncoding("UTF-8");
                FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_NT);
                conf.setServerLanguageCode("zh");
                //登录ftp
                ftp.login(userName, passWord);
                if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
                    ftp.disconnect();
                    System.out.println("Connect FTP Server failed.");
                }
                System.out.println("Connect FTP Server success.");
            } catch (Exception e){
                e.printStackTrace();
                throw e;
            }
            return ftp;
        }
    
        /**
         * FTP参数固定方式,参数在配置文件中
         *
         * @return FTPClient
         */
        public FTPClient getConnectionFTP() throws Exception{
            return getConnectionFTP(FTP_BACKUP_HOST_IP, Integer.valueOf(FTP_BACKUP_HOST_PORT),
                    FTP_BACKUP_HOST_ACCOUNT, FTP_BACKUP_HOST_PWD);
        }
    
    
        /**
         * 关闭连接-FTP方式
         *
         * @param ftp FTPClient对象
         * @return boolean
         */
        public boolean closeFTP(FTPClient ftp){
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                    System.out.println("Ftp connection closed.");
                    return true;
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
            return false;
        }
    
        /**
         * 上传文件-FTP方式
         *
         * @param ftp                  FTPClient对象
         * @param ftpPath              FTP服务器上传路径
         * @param localFileIncludePath 本地文件全路径
         * @param inputStream          输入流
         * @return boolean
         */
        public boolean uploadFile(FTPClient ftp, String ftpPath, String localFileIncludePath, InputStream inputStream) throws Exception{
            boolean success = false;
            try {
                ftp.changeWorkingDirectory(ftpPath);//转移到指定FTP服务器目录
                FTPFile[] fs = ftp.listFiles();//得到目录的相应文件列表
                localFileIncludePath = FtpTool.changeName(localFileIncludePath, fs);
                localFileIncludePath = new String(localFileIncludePath.getBytes("UTF-8"), "UTF-8");
                ftpPath = new String(ftpPath.getBytes("UTF-8"), "UTF-8");
                //转到指定上传目录
                ftp.changeWorkingDirectory(ftpPath);
                //将上传文件存储到指定目录
                ftp.setFileType(FTP.BINARY_FILE_TYPE);
                //如果缺省该句 传输txt正常 但图片和其他格式的文件传输出现乱码
                ftp.storeFile(localFileIncludePath, inputStream);
                //关闭输入流
                inputStream.close();
                //退出ftp
                ftp.logout();
                //表示上传成功
                success = true;
                System.out.println("Upload success.");
            } catch (Exception e){
                e.printStackTrace();
                throw e;
            }
            return success;
        }
    
        /**
         * 上传文件到FTP服务器的固定路径,路径见配置文件
         *
         * @param ftp
         * @param fileName
         * @param inputStream
         * @return
         */
        public boolean uploadFile(FTPClient ftp, String fileName, InputStream inputStream) throws Exception{
            return uploadFile(ftp, FTP_BACKUP_HOST_PATH, BACKUP_LOCAL_PATH + fileName, inputStream);
        }
    
        /**
         * 删除文件-FTP方式
         *
         * @param ftp         FTPClient对象
         * @param ftpPath     FTP服务器上传地址
         * @param ftpFileName FTP服务器上要删除的文件名
         * @return
         */
        public boolean deleteFile(FTPClient ftp, String ftpPath, String ftpFileName) throws Exception{
            boolean success = false;
            try {
                ftp.changeWorkingDirectory(ftpPath);//转移到指定FTP服务器目录
                ftpFileName = new String(ftpFileName.getBytes("UTF-8"), "UTF-8");
                ftpPath = new String(ftpPath.getBytes("UTF-8"), "UTF-8");
                ftp.deleteFile(ftpFileName);
                ftp.logout();
                success = true;
            } catch (Exception e){
                e.printStackTrace();
                throw e;
            }
            return success;
        }
    
        /**
         * 从ftp删除文件
         * @param ftp
         * @param ftpFileName
         * @return
         */
        public boolean deleteFile(FTPClient ftp, String ftpFileName) throws Exception{
            return deleteFile(ftp, FTP_BACKUP_HOST_PATH, ftpFileName);
        }
    
        /**
         * 下载文件-FTP方式
         *
         * @param ftp           FTPClient对象
         * @param ftpPath       FTP服务器路径
         * @param ftpFileName   FTP服务器文件名
         * @param localSavePath 本里存储路径
         * @return boolean
         */
        public boolean downFile(FTPClient ftp, String ftpPath, String ftpFileName, String localSavePath) throws Exception{
            boolean success = false;
            try {
                ftp.changeWorkingDirectory(ftpPath);//转移到FTP服务器目录
                FTPFile[] fs = ftp.listFiles(); //得到目录的相应文件列表
                for (FTPFile ff : fs) {
                    if (ff.getName().equals(ftpFileName)) {
    //                    File localFile = new File(localSavePath + "\\" + ff.getName());
                        File localFile = new File(localSavePath + ff.getName());
                        OutputStream outputStream = new FileOutputStream(localFile);
                        //将文件保存到输出流outputStream中
                        ftp.setFileType(FTP.BINARY_FILE_TYPE);//定义编码格式 防止文件中的中文出现乱码
                        ftp.retrieveFile(new String(ff.getName().getBytes("UTF-8"), "UTF-8"), outputStream);
                        outputStream.flush();
                        outputStream.close();
                        System.out.println("Download success.");
                    }
                }
                ftp.logout();
                success = true;
            } catch (Exception e){
                e.printStackTrace();
                throw e;
            }
            return success;
        }
    
        /**
         * 从ftp下载文件,固定参数见配置文件
         *
         * @param ftp
         * @param ftpFileName
         * @return
         */
        public boolean downFile(FTPClient ftp, String ftpFileName) throws Exception{
            return downFile(ftp, FTP_BACKUP_HOST_PATH, ftpFileName, BACKUP_LOCAL_PATH);
        }
    
        /**
         * 判断是否有重名文件
         *
         * @param fileName
         * @param fs
         * @return
         */
        public static boolean isFileExist(String fileName, FTPFile[] fs) {
            for (int i = 0; i < fs.length; i++) {
                FTPFile ff = fs[i];
                if (ff.getName().equals(fileName)) {
                    return true; //如果存在返回 正确信号
                }
            }
            return false; //如果不存在返回错误信号
        }
    
        /**
         * 根据重名判断的结果 生成新的文件的名称
         *
         * @param fileName
         * @param fs
         * @return
         */
        public static String changeName(String fileName, FTPFile[] fs) {
            int n = 0;
    //      fileName = fileName.append(fileName);
            while (isFileExist(fileName.toString(), fs)) {
                n++;
                String a = "[" + n + "]";
                int b = fileName.lastIndexOf(".");//最后一出现小数点的位置
                int c = fileName.lastIndexOf("[");//最后一次"["出现的位置
                if (c < 0) {
                    c = b;
                }
                StringBuffer name = new StringBuffer(fileName.substring(0, c));//文件的名字
                StringBuffer suffix = new StringBuffer(fileName.substring(b + 1));//后缀的名称
                fileName = name.append(a) + "." + suffix;
            }
            return fileName.toString();
        }
    }
    作者:gycool21 发表于2016/10/2 23:31:03 原文链接
    阅读:119 评论:0 查看评论

    仿知乎FloatingActionButton浮动按钮动画效果实现(二)

    $
    0
    0

    上文 仿知乎FloatingActionButton浮动按钮动画效果实现(一)介绍了知乎的FloatingActionButton的点击动画效果,以及遮布的实现。

    本文主要实现的是随着RecycleView的上下滑动,FloatingActionButton随之消失或出现的动画效果。

    效果图如下:



    实现过程:

    1、引入Google的desin包:

    compile 'com.android.support:design:23.0.0'


    2、布局配置:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/spfresh_recommend"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <android.support.v7.widget.RecyclerView
                android:id="@+id/rv_recommend"
                android:background="#f2f2f2"
                android:scrollbars="vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </android.support.v4.widget.SwipeRefreshLayout>
    
        <android.support.design.widget.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@android:drawable/presence_video_online"
            app:backgroundTint="#04d17f"
            app:rippleColor="#5504d17f"
            app:fabSize="normal"
            android:clickable="true"
            app:layout_anchor="@id/rv_recommend"
            app:layout_anchorGravity="bottom|center_horizontal"
            android:layout_marginBottom="24dp"
            app:layout_behavior="com.jzn.hdlive.view.widget.ScrollAwareFABBehavior"
            />
    </android.support.design.widget.CoordinatorLayout>

    这里要注意:
    app:layout_anchor="@id/rv_recommend"
    很关键,通过这行代码将floatingActionButton和RecycleView的动作监听关联起来。

     app:layout_behavior="com.jzn.hdlive.view.widget.ScrollAwareFABBehavior"
    通过设置behavior让floatingActionButton获得自定义的动画效果行为。


    3、ScrollAwareFABBehavior类

    /**
     * Created by jiangzn on 16/10/2.
     */
    public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
    
        private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
        private boolean mIsAnimatingOut = false;
    
        public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
            super();
        }
    
        @Override
        public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
                                           final View directTargetChild, final View target, final int nestedScrollAxes) {
            // Ensure we react to vertical scrolling
            return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
                    || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
        }
    
        @Override
        public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
                                   final View target, final int dxConsumed, final int dyConsumed,
                                   final int dxUnconsumed, final int dyUnconsumed) {
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
            if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
                // User scrolled down and the FAB is currently visible -> hide the FAB
                animateOut(child);
            } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
                // User scrolled up and the FAB is currently not visible -> show the FAB
                animateIn(child);
            }
        }
    
        // Same animation that FloatingActionButton.Behavior uses to hide the FAB when the AppBarLayout exits
        private void animateOut(final FloatingActionButton button) {
            if (Build.VERSION.SDK_INT >= 14) {
                ViewCompat.animate(button).scaleX(0.0F).scaleY(0.0F).alpha(0.0F).setInterpolator(INTERPOLATOR).withLayer()
                        .setListener(new ViewPropertyAnimatorListener() {
                            public void onAnimationStart(View view) {
                                ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
                            }
    
                            public void onAnimationCancel(View view) {
                                ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
                            }
    
                            public void onAnimationEnd(View view) {
                                ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
                                view.setVisibility(View.GONE);
                            }
                        }).start();
            } else {
                Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_out);
                anim.setInterpolator(INTERPOLATOR);
                anim.setDuration(200L);
                anim.setAnimationListener(new Animation.AnimationListener() {
                    public void onAnimationStart(Animation animation) {
                        ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
                    }
    
                    public void onAnimationEnd(Animation animation) {
                        ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
                        button.setVisibility(View.GONE);
                    }
    
                    @Override
                    public void onAnimationRepeat(final Animation animation) {
                    }
                });
                button.startAnimation(anim);
            }
        }
    
        // Same animation that FloatingActionButton.Behavior uses to show the FAB when the AppBarLayout enters
        private void animateIn(FloatingActionButton button) {
            button.setVisibility(View.VISIBLE);
            if (Build.VERSION.SDK_INT >= 14) {
                ViewCompat.animate(button).scaleX(1.0F).scaleY(1.0F).alpha(1.0F)
                        .setInterpolator(INTERPOLATOR).withLayer().setListener(null)
                        .start();
            } else {
                Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_in);
                anim.setDuration(200L);
                anim.setInterpolator(INTERPOLATOR);
                button.startAnimation(anim);
            }
        }
    }

    该类的实现也比较简单,在onNestedScroll中判断动作是上滑还是下滑,然后执行相应的动画方法。


    4、动画资源xml:R.anim.fab_out 和 R.anim.fab_in

    fab_in:

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:anim/decelerate_interpolator"
        android:zAdjustment="top">
        <scale android:fromXScale="1.0" android:toXScale=".5"
            android:fromYScale="1.0" android:toYScale=".5"
            android:pivotX="50%p" android:pivotY="50%p"
            android:duration="@android:integer/config_mediumAnimTime" />
        <alpha android:fromAlpha="1.0" android:toAlpha="0"
            android:duration="@android:integer/config_mediumAnimTime"/>
    </set>
    fab_out:

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:anim/decelerate_interpolator">
        <scale android:fromXScale="2.0" android:toXScale="1.0"
            android:fromYScale="2.0" android:toYScale="1.0"
            android:pivotX="50%p" android:pivotY="50%p"
            android:duration="@android:integer/config_mediumAnimTime" />
    </set>  


    这里我实现的是缩放消失动画,设置其他的动画直接在anim这两个xml文件里修改就可以了。


    ********************************************************************************************************************************


    国庆节还是在一直努力写代码啊QAQ虽然有点累的,

    但是

    现在还远没有到可以清闲的时候,要加油,不断地加油~












    作者:qq_22770457 发表于2016/10/2 23:40:05 原文链接
    阅读:266 评论:1 查看评论

    android中IPC机制学习

    $
    0
    0

    今天这篇博客,我将会深入学习android中的IPC多进程之间的通信机制。

    Android中的多进程模式

    在android中,我们可以通过给四大组件指定”android:process”属性,就可以开启多进程模式了。

    开启多进程模式

    在android中开启多进程,可以给android中的四大组件在AndroidManifest.xml中指定”android:process”属性。
    这里写图片描述
    上面的代码,我们分别为SecondActivity和ThirdActivity指定了不同的进程。当SecondActivity和ThirdActivity启动的时候,系统会为他们分别创建一个不同的进程,我们可以通过下面的命令来查看当前的进程:
    adb shell ps | grep -n “相关进程的包名”
    这里写图片描述

    可以看到上面我们分别使用了”:remote”和”com.example.myapplication.remote”来指定当前的进程名字,其实这两种指定方式还有有区别的:

    • “:”这种方式,系统会自动在其前面加上当前的包名。
    • 进程名称以”:”开头的属于当前应用的私有进程,其他应用组件不能和它运行在同一个进程中。而不以”:”开头的进程,属于全局进程,其他应用可以通过SharedUID和它运行在同一个进程中。

    android多进程需要注意的地方

    可以看到,我们可以通过在AndroidManifest.xml中指定”android:process”属性就可以轻松的开启多进程模式,但是,多进程还有很多我们预料不到的结果,先看看下面的栗子:

    1. 我们先创建一个MyStaticData类,包含一个静态变量。
    2. 在MainActivity中改变MyStaticData类中静态变量的值。
    3. 在SecondActivity中获取该静态变量的值。
    public class MyStaticData {
        public static int count = 0;
    }

    这里写图片描述
    可以看到,虽然静态变量是可以内存共享的,可是由于MainActivity和SecondActivity是运行在不同的进程中的,所以是运行在不同的虚拟机的。

    一般情况下,我们使用多进程,可能会遇到下面问题:

    • 静态成员和単例模式失效
    • 线程同步机制失效
    • SharedPreference不是很可靠
    • Application会多次创建
      下面我们创建一个 MyApplication类,来验证这样的问题
    public class MyApplication extends Application {
    
        private String TAG = MyApplication.class.getSimpleName();
    
        @Override
        public void onCreate() {
            super.onCreate();
            String processName = "";
            int pid = android.os.Process.myPid();
            ActivityManager manager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
            for (ActivityManager.RunningAppProcessInfo process: manager.getRunningAppProcesses()) {
                if(process.pid == pid)
                {
                    processName = process.processName;
                }
            }
            Log.d(TAG,"CREATE MyApplication, processName is :"+processName);
        }
    }
    

    另外需要配置我们自定义的application

    <application
            android:name=".MyApplication">
    </application

    可以看到,当三个Activity都启动的时候,会分别调用 的oncreate方法。
    这里写图片描述

    IPC基础学习

    Binder

    Binder是android中的一个类,它实现了IBinder接口,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁。

    下面为了更深入了解Binder的学习,我们新建一个demo。
    Person.java

    public class Person implements Parcelable {
    
        private int personId;
        private String personName;
        private String personPass;
        private int personAge;
    
        public int getPersonId() {
            return personId;
        }
    
        public void setPersonId(int personId) {
            this.personId = personId;
        }
    
        public String getPersonName() {
            return personName;
        }
    
        public void setPersonName(String personName) {
            this.personName = personName;
        }
    
        public String getPersonPass() {
            return personPass;
        }
    
        public void setPersonPass(String personPass) {
            this.personPass = personPass;
        }
    
        public int getPersonAge() {
            return personAge;
        }
    
        public void setPersonAge(int personAge) {
            this.personAge = personAge;
        }
    
        public Person(int personId, String personName, String personPass,
                int personAge) {
            this.personId = personId;
            this.personName = personName;
            this.personPass = personPass;
            this.personAge = personAge;
        }
    
        @Override
        public int describeContents() {
            // TODO Auto-generated method stub
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int arg1) {
            // TODO Auto-generated method stub
            dest.writeInt(personId);
            dest.writeString(personName);
            dest.writeString(personPass);
            dest.writeInt(personAge);
        }
    
        public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
    
            @Override
            public Person createFromParcel(Parcel arg0) {
                // TODO Auto-generated method stub
                Person person = new Person(arg0.readInt(),arg0.readString(),arg0.readString(),arg0.readInt());
                return person;
            }
    
            @Override
            public Person[] newArray(int arg0) {
                // TODO Auto-generated method stub
                return new Person[arg0];
            }
        };
    }
    

    Person.aidl

    parcelable Person; 

    IBookManager.aidl

    package com.example.aidlservice;
    
    import java.util.List;
    import com.example.aidlservice.Person;
    
    interface IPerson
    {
        List<Person> getAllPerson();
        Person getPersonById(in int personId);
    }

    此时系统会自动为我们生成IPerson.java

    package com.example.aidlservice;
    public interface IPerson extends android.os.IInterface
    {
        /** Local-side IPC implementation stub class. */
        public static abstract class Stub extends android.os.Binder implements com.example.aidlservice.IPerson
        {
            private static final java.lang.String DESCRIPTOR = "com.example.aidlservice.IPerson";
            /** Construct the stub at attach it to the interface. */
            public Stub()
            {
                this.attachInterface(this, DESCRIPTOR);
            }
            /**
             * Cast an IBinder object into an com.example.aidlservice.IPerson interface,
             * generating a proxy if needed.
             */
            public static com.example.aidlservice.IPerson asInterface(android.os.IBinder obj)
            {
                if ((obj==null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin!=null)&&(iin instanceof com.example.aidlservice.IPerson))) {
                    return ((com.example.aidlservice.IPerson)iin);
                }
                return new com.example.aidlservice.IPerson.Stub.Proxy(obj);
            }
            @Override public android.os.IBinder asBinder()
            {
                return this;
            }
            @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
            {
                switch (code)
                {
                case INTERFACE_TRANSACTION:
                {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getAllPerson:
                {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<Person> _result = this.getAllPerson();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_getPersonById:
                {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    Person _result = this.getPersonById(_arg0);
                    reply.writeNoException();
                    if ((_result!=null)) {
                        reply.writeInt(1);
                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    }
                    else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                }
                return super.onTransact(code, data, reply, flags);
            }
            private static class Proxy implements com.example.aidlservice.IPerson
            {
                private android.os.IBinder mRemote;
                Proxy(android.os.IBinder remote)
                {
                    mRemote = remote;
                }
                @Override public android.os.IBinder asBinder()
                {
                    return mRemote;
                }
                public java.lang.String getInterfaceDescriptor()
                {
                    return DESCRIPTOR;
                }
                @Override public java.util.List<Person> getAllPerson() throws android.os.RemoteException
                {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    java.util.List<Person> _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_getAllPerson, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.createTypedArrayList(Person.CREATOR);
                    }
                    finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
                @Override public Person getPersonById(int personId) throws android.os.RemoteException
                {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    Person _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeInt(personId);
                        mRemote.transact(Stub.TRANSACTION_getPersonById, _data, _reply, 0);
                        _reply.readException();
                        if ((0!=_reply.readInt())) {
                            _result = Person.CREATOR.createFromParcel(_reply);
                        }
                        else {
                            _result = null;
                        }
                    }
                    finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
            }
            static final int TRANSACTION_getAllPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
            static final int TRANSACTION_getPersonById = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        }
        public java.util.List<Person> getAllPerson() throws android.os.RemoteException;
        public Person getPersonById(int personId) throws android.os.RemoteException;
    }

    系统自动为我们生成了Iperson.java,下面我们对其内部属性做必要解释:

    • DESCRIPTOR
      当前Binder的唯一标识,一般是当前的全类名
    • asInterface(android.os.IBinder obj)
      用于将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象,如果客户端和服务端是在同一个进程中,则此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。

    • asBinder()
      返回当前Binder对象

    • onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
      这个方法运行咋服务端的Binder线程池中,当客户端发起跨进程请求时,最终会有该方法来进行处理,需要注意的是,如果该方法返回的是false,那么客户端的请求就会失败,因此我们可以使用这个特性来做权限验证。

    需要注意的是:
    1.如果需要传递自定义的类型:需要实现Parcelable接口
    2.服务端定义的service必须在Manifest.xml文件中声明
    3.如果需要传递自定义类型,还需要将自定义类型声明为aidl文件
    4.在服务端声明的aidl文件,在客户端必须要有一份相同的文件存在,并且,包名必须相同。这是因为aidl文件就相当于调用的接口,包名必须相同,通过ServiceConnection绑定该接口,然后,通过该aidl生成的接口调用对应的服务端的方法。

    可以看到系统根据IBookManager.aidl为我们生成了IBookManager.java类,它继承自IInterface接口,同时它还声明了两个整型id用于标识不同的方法,接着还有一个Stub的内部类,这个Stub也是一个Binder类,当客户端和服务端都位于同一个进程的时候,会返回一个本地的Binder对象,当两者位于不同进程的时候,服务端最终调用的方法有Stub类的内部代理类Proxy来完成。

    Binder有两个很重要的方法:

     public void linkToDeath(DeathRecipient recipient, int flags) {
     }
    
    public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
            return true;
    }

    由于Binder是运行在服务端进程的,如果服务端进程由于某种原因被异常终止,此时称之为Binder死亡,此时客户端的调用则会收到影响,好在Binder中提供了两个配对的linkToDeath和unlinkToDeath方法,通过linkToDeath方法,我们可以给Binder设置一个死亡代理,当Binder死亡的时候,我们就会收到通知,这个时候就可以重新发起连接请求从而恢复连接。

    设置死亡代理

    • 声明一个 对象, 是一个接口,其内部有一个 方法,我们需要实现这个方法,当Binder死亡的时候,系统就会回调 方法,我们可以在该方法中移除之前的绑定的binder,并重新绑定到远程服务。

    其次,需要在客户端绑定成功以后,给Binder设置死亡代理:

    其中 的第二个参数是一个标记为。另外我们也可以通过Binder的isBinderAlive来判断当前Binder是否死亡,我们可以在客户端里设置该代理:

        private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    
            @Override
            public void binderDied() {
                if (iPerson == null) {
                    return;
                }
                iPerson.asBinder().unlinkToDeath(mDeathRecipient, 0);
                iPerson = null;
                //重新绑定远程服务
                //......
                Log.d(TAG,"haha remote service has died");
                Intent intent2 = new Intent();
                intent2.setAction("com.action.action.myPerson");
                bindService(intent2, conn2, Service.BIND_AUTO_CREATE);
            }
        };
    
        private ServiceConnection conn2 = new ServiceConnection() {
    
            @Override
            public void onServiceDisconnected(ComponentName arg0) {
                Log.d(TAG,"haha onServiceDisconnected");
                iPerson = null;
            }
    
            @Override
            public void onServiceConnected(ComponentName arg0, IBinder service) {
                Log.d(TAG,"haha onServiceConnected");
                iPerson = IPerson.Stub.asInterface(service);
                try {
                    service.linkToDeath(mDeathRecipient,0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        };

    此时,如果我们手动将远程服务进程杀掉,会看到在客户端可以监听到服务端死亡的行为,并且可以重新绑定服务。
    这里写图片描述

    另外我们还可以在onServiceDisconnected方法中重新连接远程服务,区别在于onServiceDisconnected是在客户端的UI线程中被回调,而binderDied是在客户端的线程池中被回调的。即在binderDied方法中不能访问UI.

    不能再服务端执行耗时操作

    客户端调用远程服务端方法,被调用的方法运行在服务端的Binder线程池中,同时客户端也会被挂起,这个时候如果服务端执行比较耗时的话,就会导致客户端长时间的阻塞,如果此时是客户端的UI线程,则很有可能会出现ANR
    我们修改服务端的方法,为耗时方法

    public List<Person> getAllPerson() throws RemoteException {
                SystemClock.sleep(90000);
                return lists;
    }

    在客户端不断调用该方法,则会出现ANR
    这里写图片描述
    为了避免这种ANR出现,我们可以开启一个新的线程,将调用耗时的操作放到该线程中去执行即可。

    AIDL权限验证

    默认情况下,我们的远程服务是所有人都可以连接的, 但这绝不是我们想要看到的。所以需要添加权限验证的功能,验证失败则无法调用,常用的有两种方法:

    1. 在onBind方法中进行验证,验证失败,则直接返回null。比如添加permission来验证
     <permission android:name="com.test.permission.aidl"
            android:protectionLevel="normal"
            >
     </permission>
    
    
    @Override
    public IBinder onBind(Intent arg0) {
    int check = checkCallingOrSelfPermission("com.test.permission.aidl");
    Log.d("testaidl","check is :"+check+"  PERMISSION_DENIED is :"+PackageManager.PERMISSION_DENIED);
    if (check == PackageManager.PERMISSION_DENIED) {
        return null;
    }
        return personBinder;
    }

    此时一个应用来绑定我们的服务时候,会验证这个应用的权限,如果它没有使用这个权限,在onBind方法中会直接返回null。如果客户端需要绑定到该权限,则需要使用上面定义的权限

     <uses-permission android:name="com.test.permission.aidl"/>

    2.我们可以在服务端的onTransact方法中进行验证,如果验证失败直接返回false,这样服务端就不会执行AIDL中的方法。

    使用Messenger实现进程间通信

    Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以方便的进行进程间通信。实现Messenger通信:

    1. 服务端进程
      我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind方法中返回这个Messenger对象底层的Binder即可。

    2. 客户端进程
      客户端进程需要首先绑定服务端的service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息,类型为Messenger对象。

    服务端

    在服务端创建一个service用来处理客户端的连接请求。

    public class MessengerService extends Service {
    
        private static final String TAG = "MessengerService";
    
        private Messenger mMessenger = new Messenger(new Handler() {
    
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                case 1:// from client
                    Log.d(TAG,"receive msg from client :"+msg.getData().getString("msg"));
                    break;
                default:
                    super.handleMessage(msg);
                }
            }
        });
    
        @Override
        public IBinder onBind(Intent arg0) {
            return mMessenger.getBinder();
        }
    }

    注册该service

    <service android:name="com.example.aidlservice.MessengerService">
                 <intent-filter>
                    <action android:name="com.action.action.messenger"/>
                </intent-filter>
    </service>

    客户端

    private Messenger mService;
        private ServiceConnection conn = new ServiceConnection() {
    
            @Override
            public void onServiceDisconnected(ComponentName arg0) {
                mService = null;
            }
    
            @Override
            public void onServiceConnected(ComponentName arg0, IBinder service) {
                mService = new Messenger(service);
                Message msg = Message.obtain(null,1);
                Bundle data = new Bundle();
                data.putString("msg","hello i am client");
                msg.setData(data);// 发送数据到服务端
                try {
                    mService.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        };
    
    
    // 绑定服务端service
    Intent intent3 = new Intent();
    intent3.setAction("com.action.action.messenger");
    bindService(intent3, conn, Service.BIND_AUTO_CREATE);

    此时,先运行我们的服务端进程,然后运行客户端进程,在客户端进程中,当绑定服务成功以后,则客户端会发送消息给到服务端。
    这里写图片描述

    上面代码虽然实现了客户端发送消息给到服务端,可是如果服务端想要回复消息给到客户端,其实代码和客户端发送时一样的。

    messenger双向通信服务端

    这里写图片描述

    messenger双向通信客户端

    为了接受服务端的消息,客户端同样需要准备一个接收消息的Messenger和Handler
    这里写图片描述
    需要注意的是,当客户端发送消息的时候,需要把接收服务端回复的Messenger通过message.replyTo参数传递给到服务端。
    这里写图片描述

    此时先运行服务端,在运行客户端,可以看到,当客户端绑定服务成功以后,会发送一条消息给到服务端,当服务端接收到这条消息,会返回一条消息在到客户端。
    这里写图片描述

    Binder连接池

    到现在为止,我们已经能够很熟练的使用AIDL来实现进程之间的通信了,可是如果业务模块很多的话,如果按照这种方式,就需要很多个AIDL文件,创建很多个service,我们需要减少service的数量,将所有的AIDL放在同一个Service中去管理。
    这里写图片描述
    从上图可以看出,在这种模式下,每个业务模块创建自己的AIDL接口并实现此接口,此时不同业务模块之间不能有耦合性,所有的实现细节都要单独开来,然后想服务端提供自己的唯一标示和其对应的Binder对象。

    对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder给他们,不同业务模块拿到锁需要的Binder对象后就可以进行远程方法调用了。

    Binder线程池的实现

    下面我们创建两个业务模块,即创建两个不同的AIDL文件。
    ICat.aidl

    package com.example.aidlservice;
    
    interface ICat
    {
        String getColor();
        double getWeight();
    }

    IPerson.aidl

    package com.example.aidlservice;
    
    import java.util.List;
    import com.example.aidlservice.Person;
    
    interface IPerson
    {
        List<Person> getAllPerson();
        Person getPersonById(in int personId);
    }

    为了演示问题,我们的服务端这两个业务模块的实现也比较简单

    public class IpersonImpl extends com.example.aidlservice.IPerson.Stub{
    
        private List<Person>lists = new ArrayList<Person>();
    
        public IpersonImpl() {
            for (int i = 0; i < 4; i++) {
                lists.add(new Person(i,"names","pass",i));
            }
        }
    
        @Override
        public List<Person> getAllPerson() throws RemoteException {
            return lists;
        }
    
        @Override
        public Person getPersonById(int personId) throws RemoteException {
            for (Person per : lists) {
                if (per.getPersonId() == personId) {
                    return per;
                }
            }
            return null;
        }
    }
    
    
    public class IcatImpl extends com.example.aidlservice.ICat.Stub {
    
        @Override
        public String getColor() throws RemoteException {
            return "red";
        }
    
        @Override
        public double getWeight() throws RemoteException {
            return Math.random() * 10;
        }
    }

    到现在为止,我们各个业务模块的AIDL接口定义和实现都已经完成了,注意这里并没有为每个模块的AIDL单独创建Service

    为Binder连接池创建AIDL接口IBinderPool.aidl

    package com.example.aidlservice;
    
    interface IBinderPool {
        IBinder queryBinder(int binderCode);
    }

    为Binder连接池创建远程Service并实现IBinderPool,下面是queryBinder的具体实现。

    public class BinderPoolService extends Service {
    
        private Binder mBinder = new BinderPoolManager();
    
        public static class BinderPoolManager extends IBinderPool.Stub {
            @Override
            public IBinder queryBinder(int binderCode) throws RemoteException {
                IBinder binder = null;
                switch (binderCode) {
                    case BinderPool.BINDER_CAT:
                        binder = new IcatImpl();
                        break;
                    case BinderPool.BINDER_PERSON:
                        binder = new IpersonImpl();
                        break;
                }
                return binder;
            }
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    }

    可以看到这里我们绑定服务的时候是根据传入的参数来返回不同的服务端代理的。注意在AndroidManifest.xml中声明该服务

    <service android:name="com.example.aidlservice.BinderPoolService"
                android:process=":remote">
    </service>

    这里我们创建一个BinderPool类,用来绑定和监听当前连接的状态:

    public class BinderPool {
    
        public static final int BINDER_CAT = 0;
    
        public static final int BINDER_PERSON = 1;
    
        private Context context = null;
    
        private IBinderPool mBinderPool = null;
        /**
         * CountDownLatch
         * 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。
         * 由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。
         * 这种现象只出现一次——计数无法被重置。 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行
         */
        private CountDownLatch mConnectBinderPoolCountDownLatch = null;
    
        private static BinderPool sInstance = null;
    
        private BinderPool(Context ctx) {
            this.context = ctx.getApplicationContext();
            connectBinderPoolService();
        }
    
        public static BinderPool getInstance(Context ctx) {
            synchronized (BinderPool.class) {
                if(sInstance == null) {
                    sInstance = new BinderPool(ctx);
                }
            }
            return sInstance;
        }
    
        private synchronized void connectBinderPoolService() {
            mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
            Intent intent = new Intent(context, BinderPoolService.class);
            context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    
            try {
                mConnectBinderPoolCountDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        private ServiceConnection mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mBinderPool = IBinderPool.Stub.asInterface(service);//这里为mBinderPool赋值为BinderPoolService
                try {
                    mBinderPool.asBinder().linkToDeath(mBinderPoolRecipient, 0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                mConnectBinderPoolCountDownLatch.countDown();
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
        private IBinder.DeathRecipient mBinderPoolRecipient = new IBinder.DeathRecipient() {
            @Override
            public void binderDied() {
                mBinderPool.asBinder().unlinkToDeath(mBinderPoolRecipient, 0);
                mBinderPool = null;
                connectBinderPoolService();
            }
        };
    
        public IBinder queryBinder(int binderCode) {
            if(mBinderPool != null && mBinderPool.asBinder().isBinderAlive()) {
                try {
                    return mBinderPool.queryBinder(binderCode);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }

    这里我们通过CountDownLatch将bindService异步操作转换成了同步操作,这就意味着它可能是好事的,binder方法调用也可能是耗时的,因此不建议放在主线程中去执行。

    客户端调用

    private ICat catService;
    private IPerson iPerson;
    
    new Thread(new Runnable() {
                @Override
                public void run() {
    
                    IBinder binder = BinderPool.getInstance(MainActivity.this).queryBinder(BinderPool.BINDER_CAT);
                    ICat cat= ICat.Stub.asInterface(binder);
                    catService = ICat.Stub.asInterface(binder);
                    try {
                        catService.getColor();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
    
                    binder = BinderPool.getInstance(MainActivity.this).queryBinder(BinderPool.BINDER_PERSON);
                    IPerson person = IPerson.Stub.asInterface(binder);
                    iPerson = IPerson.Stub.asInterface(binder);
                    try {
                        iPerson.getAllPerson();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
    
                }
            }).start();

    好了,关于android中的AIDL和Messenger进程间通信就先到这里了,算是自己的一个学习记录吧。

    作者:mockingbirds 发表于2016/10/3 10:36:34 原文链接
    阅读:148 评论:0 查看评论
    Viewing all 5930 articles
    Browse latest View live


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