Block--方便而危险

Context

Block真的很好用对不对?记得很早以前用ASIHttp来做网络请求的时候,得用delegate,然后每次请求的
delegate回调中都要做一下判断,才能知道这个回调是回应了具体哪一次请求,这还是蛮麻烦的.

不过Block出现了,对网络请求的response操作,就可以直接写在block中,不用去判断这个response谁否对应与当前这个request.

除了网络请求,Alert的回调啊,asyncsocket啊,之类的异步操作,用了block都特别的方便.

Situation

然而,有一天我们在炫轮iOS端的DIY界面中发现了一个bug,而这个bug预示着,
我有实例没有成功释放.

Reason

于是我就一行一行的找,感觉没有什么地方出现.但是我发现了一个现象:

我在使用SCLAlertView了之后,就出现了这个bug,所以我就重点查看SCLAlertView的相关代码,
经过排查,我觉得问题只能出现在dismissBlock上.查看了dismissBlock的声明代码,他是个strong~

我想,这里应该是出现了一个循环引用…block中的内容,在声明的时候,会copy一份当时的数据.当AlertView 消失的时候,
调用了dismissBlock,然而,dismissBlock中的内容有引用当前的ViewController,所以.

他们互相引用了!

所以只能在dismissBlock的最后加一行,把这个dismissBlock设置为nil.解决了~

End

这次要不是遇到了这个坑,说不定永远也不会去仔细看Block实现的原理~

关键就是这个copy,所以在block中如果引用了(强引用了该block的)类,就一定要注意,检查会不会出现循环引用!

2016 To Be Continued

这是第7年了

去年今天,差不多刚考完研,和公司的小伙伴们一起垮了个年. 然后早上起来,装着两副炫轮的自行车就被偷了.

不知道,今天早上起来会发生什么~

今年,老实说,没怎么学习…学校里面的东西.

我对我不喜欢的东西,真的是一点儿兴趣都提不起来……所以,好不容易对最优化理论感兴趣了,好好地复习了几个通宵..结果考成这副鬼样子……

从2012年来到电子科大,到现在2017年,差不多快5年了.感觉其中的4年都很辛苦.今年更是尤其得累.

都说程序员死得早,这话倒不是假的,一天从早到晚被显示器辐射,在凳子上可以坐一天,饿了吃点儿垃圾食品,真是又刺激又心酸.

都说程序员容易猝死,这话也不算假,看看这糟糕的作息,真是和自己的命在闹着玩儿呀…

之前的几年,每年年末,我都喜欢用cloc来统计一下,当年写了多少行代码,一共写了多少行代码.而今年,我放弃了…今年建立了太多的工程,要用cloc去统计每个工程中
我自己写的部分,确实很累.所以就不统计了,反正很多就是了~

什么东西都略懂一点,生活才会更精彩

这是诸葛亮金城武说的一句话,我特别喜欢.

大二的时候我爹就开始嘲讽我”你整天就会写个iOS,有什么用”

我当时觉得很有道理,而且我也很喜欢金城武那句台词,所以从那时开始,就打算多学一些东西.

但是,学一个东西,最好的方法就是实践.

本科阶段没有让我实践的资源.每次学习新东西,都和闹着玩儿一样,写写停停.过了一阵,其实就忘光了.

不过,今年机会来了不少.创业公司人手和资金都不足,每个人都得被当作好几个人来用.所以,今年好好地过了一把瘾,写了服务器,Android,网页,公众号,Windows程序.

确实,这些东西,只有搞一下,才知道套路,才能摸着一些门道.

迟来的反思

记得刚加入炫轮团队的时候,我还是纯粹的一个iOS开发者,对Android,Windows都特别的不友好.所以一心想用自己iOS版本的软件碾压Android版本的软件.这样大家就都会觉得iPhone比较爽……

然而,现在看来,觉得自己特别的幼稚.都是自家的产品,即使自己做的东西不丢人,整体有问题,还是很丢人.

一头狼,可能打不过一只老虎,一头狮子;但是一群狼,就可以天下无敌

自己一味的炫技,并没有多强大.带领整个团队,向外界炫技,才是强大的表现.

创业

这真是个沉重的话题.

2014年,我们初出茅庐,充满激情和热情地把第一款产品推向世界.

如今,已经是两年了.我们还幸运的存活着.

这两年遇到了很多挫折,软件问题,硬件问题,销售问题.不断地挖坑填坑,发现问题解决问题,提出这种各样的方法,像吵架一样的讨论方案.

很辛苦,但是也很刺激.

辛苦,是肯定的.晚上睡不着,早上醒得早,缺乏运动,长期久坐,吃饭不规律.每个创业团队都遭遇着这样的挫折.

刺激,看着自己的孩子不断地完善,被市场认可.也是每个创业团队在做的事情.

所以,多累,都不会抱怨.

这一章真矫情

广告

那么,今年都干了些什么呢?

花了三个月,重写了一遍炫轮.上一版在后台显示崩溃数量一大把,这次经过改版,不仅功能强大了,界面好看了,连崩溃都少了很多了.

定位器是个好东西,我觉得算得上是一种刚性需求.所以我们给一个鞋子公司做了一款定位器,用户购买了鞋子之后,可以通过微信公众号,看到这个鞋子的位置.真是小孩防丢,老人防丢,(出轨取证),犯人防丢的必备神器.

如果说炫轮可以装逼给两边的行人看,那么尾灯就是装逼给后面的骑友看了.这是一个向后面人刷存在感的神器.这是一个可以换图,可以告诉后面人我要左转还是右转的神器.

今年一共丢了2辆自行车,真的好气啊.下次你们再偷,我要把你们找到,然后暴打一顿!不过小偷们也很聪明,如果车上有那么些看起来像跟踪器一样的东西,一定会把他们砸了再偷走的.所以,我们需要一个隐蔽性极强的跟踪装置~007追踪水壶架!长得很漂亮,漂亮的不像实力派.
当你的爱车被坏人移动的时候,系统会记录位置,并且通知到你的手机app上面.小偷往哪儿走,一目了然.关键是,他可以用半年充一次电!

这可真是硬广

收藏图片

今年还参与了一个眉山检察院的信息录入项目的开发,至于为什么这一段要叫做收藏图片,只有一起干活儿的小伙伴们才知道了.嘿嘿嘿.

眉山检察院的食堂真好吃…是自助的!吃的真爽.

还是广告

如果你不是在 vkwk.space 上面看的这篇文章,那这个广告就没得打了……

else

这个博客是使用 hexo 搭建的,这可真是节约时间! 博客重要的是内容 ,如果我花很多时间去考虑如何设计博客的服务器逻辑怎么写,那我估计还得再写两年~

结束语

2016 很累 但是 很值得 (这是本来的标题)

2016 一切 都 还没有结束

2017.1.1

VikingWarlock

蛋疼的GATT权限

Context

定位水壶架App的iOS端已经完成了80%,在考完矩阵之前,只要把android端的网络和蓝牙交互做完,就可以没什么太大的压力咯.

然而,在搞android端的时候,还是遇到了很多很多问题的.

第一坑

几天前,当我把iOS端蓝牙配对的代码,翻译为Java版本之后,本来想,肯定会轻轻松松,正正常常的运行了.

但是,在连接上定位器之后,总是会卡在搜索服务或者写入xxx这个阶段,然后就断线了.

当时,我觉得,可能是一些小问题,可能是我写漏了什么东西,所以就卡住了.

也怀疑过是Google的亲儿子Nexus蓝牙硬件不给力~

然后就痛苦的去复习了几天矩阵理论…………

但是,就在两个小时之前,我一步一步调试的时候,发现了一个奇怪的现象.

public void onServicesDiscovered(BluetoothGatt gatt, int status);

BluetoothGattCallback中,搜索到service的时候,竟然没有我们自己的service!

所以for循环就那么做完了……什么service都没有找到,最后就被关闭连接了.

这时候,我开始怀疑人生了

难道说,这蓝牙是为iOS设计的!?

赶紧整了一个锤子过来测测.发现锤子是可以找到所有的service的……

难道说,这蓝牙不是为Google设计的!?

这时候,我突然想到了一个问题.和之前几天的实验,可能有点儿关系.

很久很久的几天前

添哥找我说,蓝牙可以设置一个加密密钥,这样子传输数据,就是加密的了.(虽然说,蓝牙和WiFi一样,是要连接之后才能通信的,但是,无线嘛,肯定是要被窃听的)

于是我们就试验了一下,但是蓝牙加密之后,需要手动在手机上点击配对按钮,才可以进行加密通信.

由于android端,这个配对不是特别的明显,不像iOS那么粗暴地弹出来.所以我们最后没有使用这个方案.

这里还可以顺便说说加密

小时候,以为加密的意思就是,给数据加了一把锁.只要用密码把锁打开了,就可以访问数据了.(真单纯)

原来,加密是把数据都给翻译了一遍.即使知道加密解密的算法,使用错误的密钥,可以得到数据,但是这数据,肯定是错的.

当蓝牙加密通信的时候,会分配一个密钥.咱们配对蓝牙鼠标,蓝牙键盘的时候,也常常看到,需要输入个什么pin码,才能配对.一旦这配对了,之后通信就是加密的了.

(假设蓝牙键盘不用配对,不加密通信……)那就是无线的按键记录器啊!

总而言之

这搜索不出我们需要的service就是这个配对的,当我进入系统蓝牙设置,把这个设备的配对给取消了之后,就正常了~.~

第二坑的Context

BLE的GATT的Characteristic有权限控制.

大概就是Read,Write,WriteWithoutResponse,Notify这四种常见权限.

他们用一个8位的flag来控制.因为不是重点,所以懒得讲了.

第二坑

在我解决了Service Discover的问题了之后,我觉得,这下一定可以顺利地跑下去了!

结果卡在了一个写入之后的读取上…

本来,在写入一个数据之后,需要去读取一个反馈的.但是不知道为什么,没有去读取这个数据.

仔细阅读Monitor里面的log.可以发现,之前的读取,都会调用系统的readCharacteristic()函数,而这一次的读取却没有读取,这是为啥子嘞?

权限!

我们有一个Characteristic在写入之后,需要读取反馈.所以需要Read的权限.

然而,在这个Characteristic没有Read的权限的情况下,iOS端竟然还是可以正确的读取到数据!

而Android直接忽略的readCharacteristic()这个函数调用……

虽然说,这硬件是我们自己设计的,讲道理的话,在连接设备之后,并不需要去检查各种权限的.(除非这个硬件是被山寨的)

但是,加上一个权限验证的话,或许可以提高一些软件的稳健性呢?说不定可以让这个系统可以在遇到山寨货的情况下,可以继续正常的工作下去呢~

End

2016马上要结束了,每年都能更加深入的了解一些蓝牙,真的是好刺激!

初用Volley

Context

水壶架iOS端终于撸完了

由于我Android的界面写的烂的一逼,所以只能拜托xiaopo来帮我撸界面了.

首要目标是干掉蓝牙类,不过蓝牙绑定设备的时候会需要网络请求.

那就顺便把网络也给撸掉吧~

基本用法

1.需要一个请求队列

RequestQueue mQueue = Volley.newRequestQueue(context);

2.好了直接撸请求吧

Form-Data这么搞

StringRequest stringRequest = new StringRequest("一个URL",new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
               Log.d("Network OK", response);
        }
        }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
               Log.e("Network Fail", error.getMessage(), error);
        }
});

JSON这么搞

JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html", null,
    new Response.Listener<JSONObject>() {
        @Override
        public void onResponse(JSONObject response) {
            Log.d("Network OK", response.toString());
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e("Network Fail", error.getMessage(), error);
        }
    });

Q1 请求是form-data 响应是json

不过这里遇到了一个问题.

我们服务器大佬坑我,请求只能解析Form-Data的,返回的响应是Json

于是只能手动转换了

private static StringRequest FormDataRequest(String url, int method, Map parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock) {
    StringRequest request=new StringRequest(method, url, new Response.Listener<String>() {

        @Override
        public void onResponse(String response) {
            try {
                JSONObject jsonResponse = new JSONObject(response);
                if (jsonResponse.getInt("status") == 10086) {
                    //认证失败
                    failBlock.requestFail(new Exception("SessionExpired"));
                } else {
                    succeedBlock.getResponse(jsonResponse);
                }
            } catch (JSONException e) {
                e.printStackTrace();
                failBlock.requestFail(e);
            }
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            if (error.networkResponse.statusCode == 10000) {
                //认证失败
                failBlock.requestFail(new Exception("SessionExpired"));
            } else
                failBlock.requestFail(error);
        }}){
        @Override
        protected Map<String, String> getParams() throws AuthFailureError {
            return parameter;
        }
    };
    queue().add(request);
    return request;
}

用JSONObject自带的牛逼的初始化方法来解析响应

好不容易,把网络请求跑通了.

才发现,一个劲的403.

原来,Volley本身不处理Cookie的!!

啊席巴

于是就要手动处理

private static StringRequest FormDataRequest(String url, int method, Map parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock) {
    StringRequest request=new StringRequest(method, url, new Response.Listener<String>() {

        @Override
        public void onResponse(String response) {
           //这些你都看过了
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            //这些你都看过了
            }){
        @Override
        protected Map<String, String> getParams() throws AuthFailureError {
            return parameter;
        }

        @Override
        public Map<String, String> getHeaders() throws AuthFailureError {
            Map<String,String> header=super.getHeaders();
            String cookie=getCookie();
            if (cookie!=null){
                header.put("Set-Cookie",cookie);
                //手动从把Cookie放到Header中
            }
            return header;
        }

        @Override
        protected Response<String> parseNetworkResponse(NetworkResponse response) {
            refreshCookie(response.headers);
                //手动从Response的Header中获取Cookie
            return super.parseNetworkResponse(response);
        }
    };
    queue().add(request);
    return request;
}

这其中的Cookie持久化什么的,就不说了.它不是重点.

而重点是……

Final 我放弃了

当Response的Header中有多个Set-Cookie字段时,只会被解析出来一个!

如果需要把所有的Cookie都拿出来的话,需要修改Volley源码!

然而,我使用Gradle来添加Volley工程的,所以,你逗我吗!

于是乎,我就放弃了.

姜还是老的辣

大三的时候做精益防伪,使用了Android-Async-Http

这个很牛逼,功能很强大,还自动缓存了Cookie,真是替我省心.

还好前面撸接口的时候,封装的比较好,所以没有改动太多的东西.

Tip

封装了两个基本网络操作函数

public static void getMethodFormData(String url, Map<String,String> parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock){
    getViaLoopj(url,parameter,succeedBlock,failBlock);
}

public static void postMethodFormData(String url, Map<String,String> parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock){
    postViaLoopj(url,parameter,succeedBlock,failBlock);
}

再来一发Android-Async-Http的实例(封装过了)

public static void postViaLoopj(String url, Map<String,String> params,VKNetworkSucceedBlock succeedBlock,VKNetworkFailBlock failBlock) {
    client.post(url, new RequestParams(params), new JsonHttpResponseHandler(){
        @Override
        public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else
            {
                succeedBlock.getResponse(response);
            }
        }
        @Override
        public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
            super.onFailure(statusCode, headers, throwable, errorResponse);
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else{
                failBlock.requestFail(new Exception(errorResponse.toString()));
            }
        }
        @Override
        public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
            super.onFailure(statusCode, headers, responseString, throwable);
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else
                failBlock.requestFail(new Exception(responseString));
        }
    });
}

定义了两个interface

public interface VKNetworkSucceedBlock {
    void getResponse(JSONObject responseObject);
}

public interface VKNetworkFailBlock {
    void requestFail(Exception error);
}

于是乎做网络请求,就方便的很了

public static void mLogin(String email, String verify, boolean isChina, NetworkUtils.VKNetworkSucceedBlock succeed, NetworkUtils.VKNetworkFailBlock fail){
    HashMap<String,String> map=new HashMap<String, String>(){{
            put("hhh","123@163.com");
            put("jjj","3434");
            put("kkk",String.valueOf(1));
        }};
      NetworkUtils.postMethodFormData(UrlManager.URL_Login,map,succeed,fail);
}

最后网络的效果,就留到具体调用他的类去实现吧.

所以,封装了一遍就是好瞧把你能耐的,更换网络框架,不需要跑到每个Activity啊,Class里面去改.

response也是一套,

顶层完全不用去关注,底层的网络是如何实现的.(好像段老师还是郝老师这么说过)

当然我相信,来看这篇文章的大佬们,肯定早就知道这个了

用状态机来实现复杂操作

Context

这次新产品的蓝牙绑定过程有点儿复杂

与炫轮作比较

炫轮只有一个characteristic,不同的操作之间没有顺序关系,都是并列的

新产品有多个characteristic,每个characteristic有不同的意义和不同的指令,每个操作都有先后顺序

Solution

突然想起来,本科的微处理器,还是数字电路课,讲过状态机这个玩意儿.我觉得,可以拿来搞一搞.这样清晰明了,可以省去很多中间变量.

比如

BOOL isConnecting;
BOOL isDiscovering;
BOOL didWriteXXX;
BOOL didReadXXX;
.....

使用了状态机,并且规定好了状态转移的条件,那么,整个系统就一定在这个状态列表中滚.不会出现位置状态,导致程序跑飞.

因为从状态A–>状态B,只有当前条件是xxxx的时候才会实现.这时候不用再去管其他的变量,其他的事件.

使用了状态机,可以轻松地对已有逻辑进行扩充,因为只要拿出,状态转移图来.找到需要添加的地方,就能确定在代码中,有多少地方需要修改,如此一来,就不用在需要该需求的时候,重新阅读已有代码了!

使用了状态机,一般只需要一个记录状态的变量就够了,而且重写这个变量的Setter方法,还可以方便做一些其他的事情:

-(void)setStage:(VKLocatorPairState)stage{
    NSLog(@"pairing status %@",[PublicHelper localizedStringForPairStatus:stage]);
    //发送通知
    //如果是终止状态,发送完通知后,回到原始状态
    [[NSNotificationCenter defaultCenter]postNotificationName:kPairLocatorStatusUpdated object:@(stage)];
    if (stage==1) {  //透露太多了,要被周老板干掉的
        //透露太多了,要被周老板干掉的
    }
    if (stage==1) {  //透露太多了,要被周老板干掉的
        //透露太多了,要被周老板干掉的
    }
    if (stage==1) {  //透露太多了,要被周老板干掉的
        //透露太多了,要被周老板干掉的
    }
}

对,转移状态,和UI可以分开处理,免得以后要界面逻辑,还要去状态转移中找代码.

变量和类名都起得很不要脸啊,小伙子

总之

这一篇,看起来像硬广啊.

再总之

Android端也可以很轻松的做移植.

所以做Android的这个操作,差不多就是在做翻译工作,把Objective-C 翻译为 Java就好了.

Android上的蓝牙

Context

如果说Android和iOS一样,一出新版本,每个用户都回去升级,那我就不用写这篇咯

先说说iOS端的蓝牙扫描

@param serviceUUIDs A list of <code>CBUUID</code> objects representing the service(s) to scan for.
//serviceUUIDs是一个过滤NSArray
//根据蓝牙外设的广播信号中的Service UUID来过滤
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;

假定说,我们的app需要过滤两个产品的蓝牙信号.那么只要在serviceUUIDs里面填写他们特有Service UUID.

然后,在代理方法中做区分.

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI{
    // 从广播信号中获取Service UUID 的列表
    NSArray *serviceUUIDs=advertisementData[CBAdvertisementDataServiceUUIDsKey];
    //做一下判断
    if ([serviceUUIDs containsObject:[CBUUID UUIDWithString:@"FFFF"]]){
        //欧耶,这是我们的产品1
    }else if ([serviceUUIDs containsObject:[CBUUID UUIDWithString:@"FFFF"]]){
        //欧耶,这是产品2
        //直接else当然也可以.但是要保险一些嘛
    }
}

方便,真是方便

正题

Android中,貌似没有那么轻松

当然,这就要提起Context里面说的事情了.

BLE是android 4.3引入的,所以最早拥有蓝牙更能的版本,就是API 18.

大概是由于API 18中,蓝牙搜索的方法,有点儿蛋疼,所以,他们改了新的方法吧?

API 21

先看看API 21怎么搜索蓝牙的.

BluetoothAdapter的一个静态方法

void startScan (List<ScanFilter> filters,ScanSettings settings,ScanCallback callback)
参数 参数 解释
filters List ScanFilters for finding exact BLE devices.
settings ScanSettings Settings for the scan.
callback ScanCallback Callback used to deliver scan results.

其中ScanFilter很牛逼

Service UUIDs which identify the bluetooth gatt services running on the device.
Name of remote Bluetooth LE device.
Mac address of the remote device.
Service data which is the data associated with a service.
Manufacturer specific data which is the data associated with a particular manufacturer.

这过滤方法,可比iOS的搜索强多了

然而,这个是API 21才有的……

API 18

由于,咱们要覆盖所有的,拥有BLE功能的手机用户

所以,只能用API 18了.

boolean startLeScan (UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback);

这过滤,和iOS一样了,只能用serviceUUID来过滤.

不过,通过我的实际测试,我发现serviceUuids填写的不能是不同设备的service uuid.

这样做,总是会显示没有match uuid list (有机会再好好研究一下)

那过滤两种设备,可以选择,做两遍这个方法,给不同的callback,好像还省去了用if去判断设备这个工作.

但是,如果有一大排设备需要去过滤呢……

boolean startLeScan (BluetoothAdapter.LeScanCallback callback);

直接全部搜索吧……还过滤啥呀

手动解析广播数据,进行分类,做不同的操作

BluetoothAdapter.LeScanCallback bluetoothScanCallback = new BluetoothAdapter.LeScanCallback() {

    @Override
    public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes) {
        //已经搜索过的设备,就不用重新解析啦
        if (locatorList.containsKey(bluetoothDevice.getAddress())){
            LocatorPeripheral peripheral=locatorList.get(bluetoothDevice.getAddress());
            peripheral.updateInformation(i);
        }else if(keyList.containsKey(bluetoothDevice.getAddress())){
            KeyPeripheral peripheral=keyList.get(bluetoothDevice.getAddress());
            peripheral.updateInformation(i);
        }else if(ignoreList.contains(bluetoothDevice)){
            return;
        } else {
            //这里开始重新解析
            VKPeripheral peripheral = VKPeripheral.createPeripheral(i,bytes,bluetoothDevice);
            if (peripheral instanceof LocatorPeripheral){
                //这是设备1
                locatorList.put(bluetoothDevice.getAddress(),(LocatorPeripheral) peripheral);
            }else if (peripheral instanceof KeyPeripheral){
                //这是设备2
                keyList.put(bluetoothDevice.getAddress(),(KeyPeripheral) peripheral);
            }else {
                //这不是咱们的东西
                ignoreList.add(bluetoothDevice);
                return;
            }
            //通知UI

        }
    }
};

我水平有限,所以就是这样写的~

这里有个VKPeripheral 类名也是直接从iOS版移植过来了

用来根据广播信息来新建我自己的蓝牙设备实例.

他生成的KeyPeripheral和LocatorPeripheral都是他的子类.如果不是我们需要的设备,就加到忽略的列表中去.

还有几个小坑

1.从android 6.0开始,蓝牙扫描如果需要得到结果,就必须获得定位权限(你逗我的吧…凭什么呀)

所以要把这个加到Manifest里面去

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

2.还是android 6.0的锅. 由于6.0可以动态获取权限,所以上面这个权限,直接写在Manifest里面,说不定还没用呢…
还要手动的去获取权限

找个Activity加上这一段(不唯一)

if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION},1);
}else if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.BLUETOOTH)!=PackageManager.PERMISSION_GRANTED){
    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.BLUETOOTH,Manifest.permission.BLUETOOTH_ADMIN},1);
}else {
    //你是不是不想用这个App!!
}

End

哎,灵活的Android呀.开放的Android呀.

图片的小秘密

搞了一下凝聚最新的招新题目,说起来我也算是凝聚的大粉丝了,5年了,每年都做.

每年都有图片的隐写术.

都是套路

我也从最早的只知道改个图片后缀名,用zip/unrar把它解压掉,到了后来的用了各种各样的新奇工具,对图片进行处理.

去年和之前的套路感觉差不多,好像是

--图片
|
|
--压缩包
    |
    |
    --图片--答案

只不过最后那个图片我没看懂它什么意思罢了~(那时候在考研…)

扑通掉坑里

今年的,竟然不套路了!

如果你也想搞搞看的话,先下载图片下来,不要看后面的文章

先安利一个工具foremost,以前用的binwalk只能算出偏移量,然后用了ultraEditor手动的分离文件…这个好累(虽然直接用dd命令也可以做,但是那时候我还太嫩~)

foremost不仅可以得到一个文件里面各个其他文件的偏移量,还可以将他们直接分离成单独的文件

foremost

可以看到,这次的图片里面依然有个rar!

兴高采烈的解压缩了它,看到一个flag.txt,又兴高采烈的打开了它.里面写着:

is this the flag??

感觉被骗了.不不不,感觉被套路了.

一脸懵逼,没有思路的时候,突然发现这个图片的缩略图是一个穿着黑丝的MM的腿,而打开之后显示的却是:

displayed

Mac OS X的Preview就是好~Linux和Windows都没有,喔哈哈哈哈哈哈

难道说…

缩略图!

马上去网上查了一下,图片和缩略图的关系~

原来缩略图不是系统根据原始图片生成的,而是在图片的EXIF中存着缩略图信息.

然后又跑过去查EXIF,在EXIF中发现了一些小秘密.

标签号 定义
0x010E 图像描述
0x0112 方向
0xA002 EXIF宽度信息
0xA003 EXIF高度信息
0x011A 水平分辨率
0x011B 垂直分辨率

EXIF高度和垂直分辨率,到底应该改那个呢?试一下就是知道了!

再次使用ultraEditor打开图片,检索A0 03,找到了原始写着的高度为512的hex:02 00.改成了1280的hex:05 00.保存!

结果发现,只是缩略图变大了,原始图片没变大~

不过在缩略图里面发现了一些红色的小点点,估计是flag!

再次用ultraEditor打开图片,检索01 1B,依然是02 00,把他修改成了05 00.保存!

成功了,果然是个黑丝福利

总结一下

隐写术实在是有意思~当然今年这个题目也不是无缘无故的出来的,据说是在微博上面,9个图片写着1,2,3…9.点击看大图的时候,却是帅哥.

做技术真是有趣~.~

在 CSI:Cyber 里面也有很多差不多的东西,分析员们也会查看图片的meta data(元数据),在里面发现了很多秘密.

最早网络大侠们把种子藏在图片里面,现在把图片藏在缩略图后面,真是越来越有意思了.看看明年的题目是什么吧~

微信小网页的安全性

微信网页的安全性

这篇文章其实很早以前就可以写一下了,但是当时担心自己的方案不够安全,所以,没有敢写.

为啥子要做防御呢?

因为,我们这次的产品涉及到了比较隐私的信息,严重点儿说,还会涉及到用户的安全.

所以,至少在前端,不能让第三方可以轻松的获取到用户的信息.

由于这次做的网页,不是单网页程序,所以,每个页面所需要的初始数据,都是在URL中体现的,所以第一步需要对URL进行防御

当然,我们也不希望用户在微信外浏览网页.因为这样他们可以看到请求的参数,而且也不利于我们控制入口~

直接开始吧

一个网页需要获取到点击用户的openid,需要通过微信用户访问https://open.weixin.qq.com/connect/oauth2/authorize?appid=公众号ID&redirect_uri=回调URL

用户点击了授权,或者以前早就授权过了,才会访问跳转的URL,并且带着openid的参数

这一块好像没什么可以攻击的,所以我也没做什么处理

服务器收到了redirectURL的请求的时候,是可以获取到openid的.假设,直接根据openid来返回数据,那么微信所在的手机如果网络数据被抓包.就可以很轻松的知道了如何使用接口不使用HTTPS

那么伪造openid来获取他人的数据,也变得简单了~所以,我给他加了密,用了很多很多位的对称加密算法,把openid做了加密.每次请求都需要openid和eid(我称为加密的id…)
,配对正确,才可以正常的请求,否则就送一个403的大礼包.

这上面算是最后一道防线吧?

以前参与精益防伪项目的时候,学了好多的保密措施,感觉还是蛮有用的~

当然,这一套还存是在问题的.比如说现在的密钥是固定的,如果根据服务器的Mac来生成,或者再建立一个动态的密钥.就可以防止一个服务器被攻破之后,
另外的服务器也被轻松攻破的尴尬现象了.

当然咯

我们不希望用户在微信外防伪网页,最好的方法就是,阻止用户点击右上角的按钮,选择里面的复制链接,分享给朋友,用浏览器打开……

还好Wechat JS SDK里面就做了这个功能,可以控制微信浏览器,隐藏那些按钮~

各位大大如果找到了漏洞,一定要告诉我呀!这个关系到用户的人身安全的!

安卓初体验?

安卓初体验~

其实这并不算是初体验吧,毕竟大三的时候已经玩过android开发了,而且看起来也蛮像回事儿的.

只不过当时搞了NFC,业务逻辑和网络请求.剩下的最重要的界面和界面跳转,都是在鹅厂的望神帮我写的~

第一节课其实我没去

那一节课,据说老师讲了布局文件

然后好像就布置了一个计算器的作业,然后就没什么了.

反正,我就在界面编辑器里面拖控件.然后findViewById,之后给他们做了一些逻辑~

第二节课说是要做SD卡读取图片,并且显示出来

我一想,觉得有点儿难啊,于是就开始查官方文档去了,

整理了一下,获取文件夹的路径,文件夹内文件这些好像都不难.

选择一个内容显示估计也不难,但是如何做一个漂亮的选择页面,可能比较复杂.

我打算用GridView,做一个和相册一样.

Let’s do this

先搞了一个SDCardHelper的类(写iOS时候留下的习惯,各种Helper)

//外部储存是否可写
static public boolean isExternalStorageWritable()
//外部储存是否可读
static public boolean isExternalStorageReadable()
//储存路径
static public String storagePath()
//为GridView用的文件列表
static public List<GridItem> getFileList()

Android的GridView和iOS的UICollectionView好像差不太多

Android的GridView需要的适配器里面需要重写

public int getCount();
public Object getItem(int position);
public long getItemId(int position);
public View getView(int position, View convertView, ViewGroup parent);

iOS的UICollectionView需要实现DataSource协议的

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

这样就可以正常显示一个GridView(UICollectionView)

可是,我要自定义控件呀!不然用原始的GridView中每个item的View或者UICollectionView中原始的UICollectionViewCell
,那得多难看…

在iOS中,只要写一个继承于UICollectionViewCell的类,对它进行界面操作,数据操作,并且在Protocol中复用Cell并且返回,就可以完成目的

在Android中,貌似没有类似UICollectionViewCell的东西.所以得自己重写一个类,用来持有Item的xml布局文件中的各个控件

import android.widget.ImageView;
import android.widget.TextView;

/**
 * Created by vikingwarlock on 16-9-23.
 * This is the class for view
 */
public class GridItemView {
    public ImageView imageView;
    public TextView filename;
}

之前的图片列表,也专门写了一个类,用来存放这个数据

import java.io.Serializable;

/**
 * Created by vikingwarlock on 16-9-23.
 * This is a class that represent a grid item
 */
public class GridItem implements Serializable{
    public String filepath;
    public String filename;
}

这里用了一个Serializable,是为了后面Intent传递用的~

最后在Adapter类里面实现View

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    GridItemView item;
    GridItem data = gridItemList.get(position);
    if (convertView == null) {
        convertView = inflater.inflate(R.layout.gridviewitem, null);
        item = new GridItemView();
        item.filename = (TextView) convertView.findViewById(R.id.itemName);
        item.imageView = (ImageView) convertView.findViewById(R.id.itemImage);
        convertView.setTag(item);
    } else {
        item = (GridItemView) convertView.getTag();
    }
    ImageLoader.getInstance().displayImage("file://"+data.filepath,item.imageView);
    item.filename.setText(data.filename);
    return convertView;
}

界面就差不多搞定了~

至于点击效果,iOS依然是实现一个Protocol~就可以了

-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;

Android需要手动添加

GridView gv = (GridView) findViewById(R.id.gridView);
if (gv != null) {
    gv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
            GridItem item = datalist.get(position);
            Intent intent=new Intent();
            intent.setClass(SDView.this,MainActivity.class);
            Bundle bundle=new Bundle();
            bundle.putSerializable("result",item);
            intent.putExtras(bundle);
            setResult(2,intent);
            finish();
        }
    });

这里的gv是GridView的意思,不要想歪了.

做了一堆和Bundle有关的事情,主要是为了Intent中可以传递一个对象,而不仅仅是字符串.

setResult()用来传递一个Intent回上一个需要Result的Activity.

finish()就是结束当前Activity.

这个花了我最多时间的东西就这样做完了~

当然啦,后面还遇到了图片显示的问题.比如卡住啊,比如OOM啊~最后借助了universal-image-loader
都解决了!!

最后坐在旁边的同学跟我说,他们的代码,只有几十行.原来老师给的demo中获取图片是这样写的

@Override
public void onClick(View v) {
    Intent intent = new Intent();
    intent.setType("image/*");
    /* 使用Intent.ACTION_GET_CONTENT这个Action */
    intent.setAction(Intent.ACTION_GET_CONTENT);
    /* 取得相片后返回本画面 */
    startActivityForResult(intent, 1);
}

我好生气!说好的操作SD卡呢!

这样的写法,和iOS中的UIImagePickerViewController一样了~

Anyway,至少我学会了自定义控件的GridView,体验了一把OOM,还学习了SDCard的操作

还望各位大大指导我!

We do what we want to do

Solo

之前鼓励大学生创业.一开始我觉得这是很有趣的事情,觉得大学生终于可以抛开书本,抛开各种习题,去做一些有实际意义的事情了

然而,那一年死了很多创业团队;我觉得一来是因为扛不过大公司,二来是太浮躁.

我承认我在某些方面是很浮躁的一个人,很容易自我膨胀.所以我不适合去管理一个公司.

膨胀 Begin

不过在做产品方面,我还算是个静得下心得人,所以来整理整理功能模块,调整各部分的交互方式.我还是可以胜任的吧

膨胀 End

扯远了…这篇日记是来整理一下,开学第3(还是4)周,一边学习一边工作的心得.

This is tough

说真的,一边学习一边创业,真的是很难的事情.

我们周Boss,为了公司,牺牲了他的全部.完全没有上课,基本没有休息日,晚睡早起,同时处理很多部分的事情

过去公司在校内,下课去公司,几分钟就好了;现在由于规模扩大,公司搬到了校外的孵化园,虽然环境好了很多,网速也快了许多,周边生活设施也很便利,但是.

离学校太远了,离我的寝室更远!

我偷偷地承认一下,我也会翘一些一些课…

又扯远了…

This is tough, 最难的不是说我们做的东西做不出来.

而最难的是,我们能够高效快速的把我们想做的东西做出来,并且达到预期的效果

为了达到效果,一遍一遍的修改,一遍一遍的推翻重来,一遍一遍的联调修Bug…

I thought

曾今我优化代码,提升App性能,只有一个目的,让用户喜欢iOS,远离Android,因为我是个果粉.所以我喜欢偷偷擅自主张的添加一些小细节,来让用户使用的更加舒服,更加方便.

In fact

现在,我们为了共同的梦想在奋斗.我们想打造一个不一样的自行车世界,也想帮助更多的传统行业进入物联网时代.

为了这个梦想,我们可以牺牲娱乐,可以牺牲一些学业,可以牺牲睡眠.

可以通宵,可以不放假,可以没有薪水,可以一个人干多个人的活儿.

因为,我们愿意这样做

We want to do this.

Sorry

本来想写一篇鸡汤文,来鼓励爱浪的小朋友们,多学习多实践,多提升自己的能力,踏踏实实的度过大学时光.

结果变成了一篇给自己加油的日记了~

I hope I can fix the bugs tomorrow.