Sloppy's Blog


  • Home

  • About

  • Archives
Sloppy's Blog

C结构体内存对齐规则

Posted on 2017-01-20

今天回顾一下,C Struct的对齐规则,至于为什么要对齐,这里就不详细说了。我们只需要了解其中的规则就可以。了解一个struct在内存中的长度。定义结构体如下:

struct UserData
{
    char sex;
    double money;
    int age;
};

在上面的结构体中。第一个字段sex,实际占用内存是一个字节,由于后面的money字段是8个字节,到第二个字段是,sex可移动的是1,不是sizeof(double)的倍数,因此第一个字段,1会加上7,自动扩充到sizeof(8),然后第二个字段是8个字段,到第三个字段是age,是sizeof(int)=4,1+7+8=16,是4的倍数,所以最后占用内存是1+7+8+4=20,但是由于总长度不是字段中最长的字段sizeof(double)的倍数,因此再补4个字节,因此最后总长是:1+7+8+4+4=24

写个测试程序在VC中测试下:sizeof(UserData).查看。确实是24

Sloppy's Blog

IOS NSURLConnect忽略HTTPS不被认证的证书访问

Posted on 2017-01-17

背景

最近由于IOS需要HTTPS的协议,公司所有的HTTP协议全部改成HTTPS了,可是本地的DEBUG测试环境。是自己创建的HTTPS证书。不是被认证机构认证的。而IOS通信中的NSURLConnection如果是不被认证的证书,会通信失败。这里为了,兼容DEBUG环境下,需要在DEBUG环境忽略相关的URL接口,需要在通信的时候,实现NSURLConnectionDelegate,需要实现几个方法,如下:

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
        if ([trustedHosts containsObject:challenge.protectionSpace.host])
            [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    NSInteger responseCode = [(NSHTTPURLResponse *)response statusCode];
    NSLog(@"response length=%lld statecode%ld", [response expectedContentLength],(long)responseCode);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    if (mData == nil) {
        mData = [[NSMutableData alloc] initWithData:data];
    } else {
        [mData appendData:data];
    }
    NSString* aStr=[[NSString alloc ]initWithData:mData encoding:NSUTF8StringEncoding];
    NSLog(@"response connection%@",aStr);
    NSLog(@"response connection");
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    NSLog(@"connectionDidFinishLoading connection");
    if(mData!=nil){
        [mData release];
        mData = nil;
    }
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    NSLog(@"connection didFailWithError:%@",error);
}

这里作下说明:trustedHosts是一个NSArray,可以初始下一下:trustedHosts = [[NSArray alloc] initWithObjects:@”10.10.10.160”, nil];,其中的地址就是需要忽略证书的域名或者IP

我这里写了一个简单的测试例子,源码下载:http://www.nikoer.com/data/NSURLConnectionTest.zip

简单调用:[[ErayInterface getInstance] connnect:@”https://10.10.10.160:8080/off/user_charge“];

Sloppy's Blog

IOS IAP的一注意点

Posted on 2017-01-05

问题背景

最近公司的项目在AppleStore上审核通过了。由于之前接入了内购,在调试的时候。一点问题都没有。今天突然发现所有的IAP的商品一个沙盒测试账号只能购买一次。就不能购买第二次,并且直接卡在充值界面。由于重新打开APP会有恢复交易的功能,所以第二次充值虽然卡住,但是依然到账。但是这个问题,比较麻烦,由于测试阶段是好的。商品审核过了。这次测试以为是通过后的商品只能充值一次了。

问题出在哪里

搜索了各种资料都没有找到答案,昨天拿了一下最新的版本代码。竟然是可以的。而旧版本的代码是不行的。查了老半天。原来是服务器返回的数据格式突然不对了,可能是合并代码时出问题了。造成的。因为每次的交易结束(finishTransaction)依据服务器返回的code为0状态的时候才结束。而新的代码是不管code为多少都会结束交易。而苹果的IAP,如果上个交易没有Finish,下个交易当前是不会触发交易完成或者失败的。所以一直以为是苹果不给游戏回调造成的。

解决方案。

所以要注意的点是,每一次交易都要结束掉。不然不能连续充值。另外,同一个transactionReceipt,提交给服务器,让服务器去找IOS的充值进行校验都是可以通过的。可以服务器端,需要做标记。之前我们服务器端没有做标记,是否充值成功过,如果成功过。就不能再给游戏账户充值了。导致后来可以反复刷金币的BUG

Sloppy's Blog

Cocos2d-x文件的压缩

Posted on 2016-12-30

公司最近的游戏上了AppleStore,打开游戏的详细页面。策划突然发现游戏好大啊。怎么显示的是347M,之前上的另外一款游戏,我们没有注意这块,只是光顾着玩了。而实际我们做出来的IPA只有140多M,咨询几个超级资深的IOS开发人员,AppleStore显示的是IPA展开后的大小。但是玩家不这么认为啊。以为下载的游戏这么大。为了减小大小,看来只要压缩原始文件大小了,经过分析,特做出以下的方法:

  • 图片文件,声音文件,不压缩,因为压缩比几乎没有。
  • 压缩相关的文本文件,Plist,Json文件。尤其是动画文件,我们用的是ExportJson格式。大的惊人

在这里,做了一下简单的文件格式定义,文件由三部分构成,如下

  • 在每个压缩的文件头部加eraygames做压缩标记(9字节长),(用来判断文件是否压缩)
  • 然后是四个字节描述压缩前的文件原始长度 (用来解压)
  • 然后是文件压缩后的内容

资源的压缩,可以用Python脚本语言,在项目打包之前做运行一下命令即可,下面主要修改Cocos2d-x的引擎部分CCFileUtils,因为所有的文件加载全部在这个类里。而最终的入口在FileUtils::getContents方法里。当然其他平台,在对应的CCFileUtils-Android,CCFileUtils-win32,里。修改如下,在CCFileUtils.h头文件添加方法如下:

protected:
    enum COMPRESS_CODE
    {
        noCompress = 0,
        unCompressOK,
        unCompressError,
    };
    COMPRESS_CODE checkIsCompressFile(ResizableBuffer* buffer,int size);
private:
    int m_ITagLen;
    int m_ISizeLen;
CCFileUtils.cpp如下:
FileUtils::COMPRESS_CODE FileUtils::checkIsCompressFile(ResizableBuffer* buffer, int size) {
    if (size <= m_itaglen="" +="" m_isizelen)="" {="" return="" nocompress;="" }="" unsigned="" char="" *="" sdata="new" char[m_itaglen="" 1];="" memcpy(sdata,="" buffer-="">buffer(), m_ITagLen);
    sData[m_ITagLen] = '\0';
    std::string tag((char*)sData);
    if (tag.find("eraygames") != std::string::npos) {
        CC_SAFE_DELETE_ARRAY(sData);
        sData = new unsigned char[m_ISizeLen];
        memcpy(sData, (unsigned char*)buffer->buffer() + m_ITagLen, m_ISizeLen);
        std::reverse(&sData[0], &sData[m_ISizeLen]);
        uLong fileLen = 0;
        memcpy(&fileLen, sData, m_ISizeLen);
        CC_SAFE_DELETE_ARRAY(sData);
        int compressSize = size - m_ITagLen - m_ISizeLen;
        sData = new unsigned char[compressSize];
        memcpy(sData, (unsigned char*)buffer->buffer() + m_ITagLen + m_ISizeLen, compressSize);
        unsigned char* fileData = new unsigned char[fileLen];
        bool isCompress = true;
        do
        {
            if (uncompress(fileData, &fileLen, sData, compressSize) != Z_OK) {
                isCompress = false;
                break;
            }
            buffer->setBuffer(fileData, fileLen);
        } while (0);
        CC_SAFE_DELETE_ARRAY(sData);
        CC_SAFE_DELETE_ARRAY(fileData);
        if (!isCompress) {
            CCLOG("UnCompress Error");
            return unCompressError;
        }
        return unCompressOK;
    }
    return noCompress;
}

然后修改对应的getContents方法。在方法最后一句的前面添加,我修改CCFileUtils-win32.cpp

FileUtils::Status FileUtilsWin32::getContents(const std::string& filename, ResizableBuffer* buffer)
{
      // 此处省略其他代码
    if (!successed) {
        CCLOG("Get data from file(%s) failed, error code is %s", filename.data(), std::to_string(::GetLastError()).data());
        buffer->resize(sizeRead);
        return FileUtils::Status::ReadFailed;
    }
    FileUtils::COMPRESS_CODE code = checkIsCompressFile(buffer, size);
    if (code == FileUtils::COMPRESS_CODE::unCompressError) {
        return FileUtils::Status::ReadFailed;
    }
    return FileUtils::Status::OK;
}

最近项目升级引擎到3.13.1,以上所有的研究代码基于此版本

Sloppy's Blog

Android BLE作为外设的实例

Posted on 2016-12-02

之前的代码研究,Android主要是做为中央设备存在的,而且官方的例子好象也是基于Central的,抽一个时间研究了一下作为Peripheral的情况下,是如何编写代码的,Google查找了一番,发现作为Peripheral相对更简单多了,只是做个简单的广播,下面就几个点说一下,

  • 权限设置同之前的一样,不变,
  • 用之前IOS或者Android写的Central进行连接,我用的是IOS,Android没有测试,我相信应该是可以的,

这里面主要是一个MockServerCallBack的逻辑处理,可以参考后面的代码参考,主要的Activity的代码,参考如下:

private void initBroadCast(){
    final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    mBluetoothAdapter = bluetoothManager.getAdapter();
    if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){
         Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    }
    //打开蓝牙的套路
    if ((mBluetoothAdapter == null) || (!mBluetoothAdapter.isEnabled())) {
        Toast.makeText(this, R.string.unknown_service, Toast.LENGTH_SHORT).show();
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        this.startActivityForResult(enableBtIntent,0);
    }
    if (mBluetoothAdvertiser == null) {
        mBluetoothAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
    }
    mMockServerCallBack = new MockServerCallBack();
    mGattServer = bluetoothManager.openGattServer(this, mMockServerCallBack);
    try {
        mMockServerCallBack.setupServices(mGattServer);
        mBluetoothAdvertiser.startAdvertising(createAdvSettings(true, 0), createFMPAdvertiseData(),mAdvCallback);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

public static AdvertiseSettings createAdvSettings(boolean connectable, int timeoutMillis) {
    AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder();
    //设置广播的模式,应该是跟功耗相关
    builder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED);
    builder.setConnectable(connectable);
    builder.setTimeout(timeoutMillis);
    builder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
    return builder.build();
}

//设置一下FMP广播数据
public static AdvertiseData createFMPAdvertiseData() {
    AdvertiseData.Builder builder = new AdvertiseData.Builder();
    builder.setIncludeDeviceName(true);
    AdvertiseData adv = builder.build();
    return adv;
}

//发送广播的回调,onStartSuccess/onStartFailure很明显的两个Callback
private AdvertiseCallback mAdvCallback = new AdvertiseCallback() {
    public void onStartSuccess(android.bluetooth.le.AdvertiseSettings settingsInEffect) {
        if (settingsInEffect != null) {
            Log.d(TAG, "onStartSuccess TxPowerLv="+ settingsInEffect.getTxPowerLevel()+ " mode=" + settingsInEffect.getMode()+ " timeout=" + settingsInEffect.getTimeout());
        } else {
            Log.d(TAG, "onStartSuccess, settingInEffect is null");
        }
    }

    public void onStartFailure(int errorCode) {
        Log.d(TAG, "onStartFailure errorCode=" + errorCode);
    };
};

具体的源码下载:代码查看下载

入口Activity:BLEPeripheral

Sloppy's Blog

蓝牙BLE模式下,IOS与Android系统的配对与通信

Posted on 2016-12-02

前一文章里,研究了,Android,IOS各自的蓝牙实现方式,今天花了点时间研究蓝牙4.0,BLE协议下,Android与IOS两系统开发的APP实现用蓝牙的通信,

  • IOS 6.0+
  • Android 5.0+

我们这里把IOS做为外设,Android做为中央设备,IOS作为外设的代码,请参考上篇教程的源码,一模一样。这篇记录着重关系Android BLE的开发,下面是准备工作:

添加AndroidManifest.xml中的权限

记得要加uses-feature,参考源代码中的配置文件

添加一个Service负责蓝牙的通信

这里我就不给出代码了,请自行从参考网站中,下载,我只增加了一个写内容的方法


public boolean writeValueData(BluetoothGattCharacteristic characteristic, byte[] data) {
boolean ret = false;

if (data == null) {
    return false;
}
int length = data.length;
if (length <= dataBufferSize) {
    characteristic.setValue(data);
    ret = mBluetoothGatt.writeCharacteristic(characteristic);
} else {
    int count = 0;
    int offset = 0;
    while (offset < length) {

        if ((length - offset) < dataBufferSize) {
            count = length - offset;
        } else {
            count = dataBufferSize;
        }
        byte tempArray[] = new byte[count];
        System.arraycopy(data, offset, tempArray, 0, count);

        characteristic.setValue(data);
        ret = mBluetoothGatt.writeCharacteristic(characteristic);

        if (!ret) {
            return ret;
        }

        offset = offset + count;
    }
}
return ret;

}

修改直接相联蓝牙

教程中,各种数据,各种UI,我简化了。在拿到Service后直接相关,修改了其中的方法,代码如下:


private void displayGattServices(List gattServices) {
if (gattServices == null) return;
// Loops through available GATT Services.
for (BluetoothGattService gattService : gattServices) {
String uuid = gattService.getUuid().toString();
if(BluetoothLeService.UUID_HEART_RATE_MEASUREMENT.equals(gattService.getUuid())){
Log.d(“gattService”, uuid);
if(BluetoothGattCharacteristic.PROPERTY_READ>0){
List gattCharacteristics =gattService.getCharacteristics();
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
String charUDID = gattCharacteristic.getUuid().toString();
if(charUDID.equalsIgnoreCase(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)){
Log.d(“charUDID”, charUDID);
for(BluetoothGattDescriptor descriptor:gattCharacteristic.getDescriptors()){
String desUDID = descriptor.getUuid().toString();
Log.d(“desUDID”, desUDID);
}
mCharacteristic = gattCharacteristic;
mBluetoothLeService.setCharacteristicNotification(gattCharacteristic, true);
}
}
}
}
}
}

这里有个小问题。还没有弄明白,为什么,IOS跟Android的Descriptor的UDID不一样吗?我重新定义了下,相关的UDID,参考:SampleGattAttributes.

PS:在发送数据时,记得在IOS端输入Android端提示的Code,蓝牙连接密码

相关源码在:代码查看下载

入口为: BLEActivity

参考:https://developer.android.com/guide/topics/connectivity/bluetooth-le.html

Sloppy's Blog

蓝牙技术的初步探究

Posted on 2016-12-01

前两天,BOSS问我Cocos2d-x适合开发APP吗。我说不太适合。当然跨平台的APP开发框架也有很多种,聊下来之后,发现UI的表现只是其次,主要是可能需要涉及到手机通过蓝牙技术,控制设备啊,之类的。象目前的车载设备,穿载设备(智能手环),跟手机之间的通信,目前基本上都是在蓝牙通信基础之上(当然很多车载设备,智能设备也装有网卡,不过好象目前不是主流)。经过两天的研究,弄明白了一些概念,

经典蓝牙

也就是蓝牙4.0之前,俗称经典蓝牙阶段

蓝牙4.0

这里面有一个重要的概念叫低功耗蓝牙(Bluetooth Low Energ),这种架构的芯片,有单模跟双模,双模即可以跟4.0阶段的蓝牙进行通信,也可以跟经典的蓝牙阶段进行通信.

在经典蓝牙阶段,IOS是拒绝不信任的设备接入的,这也就是,很多的Android设备的蓝牙不能跟IOS设备进行通信了。那Android跟IOS设备如果要通信怎么办,幸好,IOS6.0+支持BLE,Android5.0+也支持BLE的开发了,那两者如果要通信,每个系统的设备的应用,可以在这基础上进行开发通信了。

下面是这两天做的简单研究,代码部分,借鉴了网上很多朋友的,我在这里做一下规整与调试,并排除了代码中的一些错误:

IOS BLE协议下的代码例子:代码查看下载

Android蓝牙经典阶段的代码例子:代码查看下载

接下来,我花点时间研究下,Android BLE协议下的蓝牙开发,并实现与IOS的通信。

Sloppy's Blog

NDK编译出现make(**文件名过长)的解决方案

Posted on 2016-11-23

随着项目文件的变多,最近在Windows上编译Android版本的时候,会出现make(**206,文件名过长的问题),google了一下,发现Windows的命令行的限制,以下是摘自Android开发网站的解释(https://developer.android.com/ndk/guides/android_mk.html)

This feature can be useful on Windows, where the command line accepts a maximum of only of 8191 characters, which can be too small for complex projects. It also impacts the compilation of individual source files, placing nearly all compiler flags inside list files, too.

两种解决方案:

  • 一种是在Android.mk文件中,添加编译设置:LOCAL_SHORT_COMMANDS := true
  • 二种是在Application.mk文件中:APP_SHORT_COMMANDS:=true

第二种,可能会造成全部的编译慢一些

Sloppy's Blog

设备唯一标识及GUID的总结

Posted on 2016-10-25

DeviceID

在很多应用中,经常需要用到。获取设备的唯一标识。而且是不能变化的,一旦用户删除APP,重新安装时,下次获取的跟上次获取的UDID是一样的,以前在苹果上,很容易实现,不过现在的IOS有各种限制。

IOS解决方法:

OpenUDID

Android解决方法:

查看

GUID

当然,还有好多应用,需要生成种唯一标识符,这里也提供一下参考:crossguid

Sloppy's Blog

IOS自带的md5,sha1

Posted on 2016-10-24

相信很多在做校验的相关功能中,会用到md5,跟sha1的相关字符串校验,IOS中自带此功能,具体代码如下:

#include "CommonCrypto/CommonDigest.h"// 双引号替换成<>
static inline char hexChar(unsigned char c) {
    return c < 10 ? '0' + c : 'a' + c - 10;
}

static inline void hexString(unsigned char *from, char *to, NSUInteger length) {
    for (NSUInteger i = 0; i < length; ++i) {
        unsigned char c = from[i];
        unsigned char cHigh = c >> 4;
        unsigned char cLow = c & 0xf;
        to[2 * i] = hexChar(cHigh);
        to[2 * i + 1] = hexChar(cLow);
    }
    to[2 * length] = '\0';
}

NSString * md5(const char *string) {
    static const NSUInteger LENGTH = 16;
    unsigned char result[LENGTH];
    CC_MD5(string, (CC_LONG)strlen(string), result);

    char hexResult[2 * LENGTH + 1];
    hexString(result, hexResult, LENGTH);

    return [NSString stringWithUTF8String:hexResult];
}

NSLog(@"%@", md5("test"));

NSString * sha1(const char *string) {
    static const NSUInteger LENGTH = 20;
    unsigned char result[LENGTH];
    CC_SHA1(string, (CC_LONG)strlen(string), result);

    char hexResult[2 * LENGTH + 1];
    hexString(result, hexResult, LENGTH);

    return [NSString stringWithUTF8String:hexResult];
}

NSLog(@"%@", sha1("test"));
123…6
Sloppy

Sloppy

54 posts
7 tags
© 2017 Sloppy
Powered by Hexo
Theme - NexT.Muse