天天看点

cocos 获取通讯录联系人并发送短信

项目推广的时候,运营希望游戏里面越多可以散发的地方越好,于是有了直接在游戏里面发短信邀请通讯录好友打游戏的需求,下面记录一下。

在cocos项目里面做这种手机系统的功能,思路就是跨平台分别调用各自系统的接口来实现获取通讯录和发送短信。cocos工程里面的跨平台写法大致跟我之前写的复制功能差不多,但是具体实现的时候,获取通讯录和发短信稍微麻烦一点,需要获取权限,有操作回调,有界面的一些处理,所以需要根据不同项目做不同的处理。

跟复制功能一样,为了方便,我直接写在了Application里面。win平台的接口实现跟复制功能差不多,就不说了,主要说一下ios和安卓。

在ApplicationProtocol里面添加接口

//发短信,这里的参数name实际在安卓和苹果的发短信函数里面暂时都用不到,是之前设计接口的时候多写了的,就当做预留参数了,亲们写的时候没有特别的需求不用传name字段。
    virtual bool sendMsgFromContactPicker(const std::string &name, const std::string &phoneNum, const std::string &msgContent)= ;
//获取通讯录
    virtual  std::string getContactPicker()
           

获取通讯录的接口的返回值设计成字符串类型是因为,在获取到系统的通讯录之后,我把所有数据存成了json返回给c++处理界面,这样操作就不管ios和安卓系统本身拿到的数据是神马类型,都方便和win下c++传递数据。

安卓在jni文件夹下Java_org_cocos2dx_lib_Cocos2dxHelper文件里实现jni的接口:

//获取通讯录
extern std::string getContactPickerJNI() {

    JniMethodInfo t;
    std::string ret("");

    if (JniHelper::getStaticMethodInfo(t, CLASS_NAME, "getContactPicker", "()Ljava/lang/String;")) {
        jstring str = (jstring)t.env->CallStaticObjectMethod(t.classID, t.methodID);
        t.env->DeleteLocalRef(t.classID);
        ret = JniHelper::jstring2string(str);
        t.env->DeleteLocalRef(str);
    }

    return ret;
}
//发送短信
extern bool sendMsgFromContactPickerJNI(const char* name, const char* phoneNum, const char* msgContent) {
    JniMethodInfo t;

    bool ret = false;
    if (JniHelper::getStaticMethodInfo(t, CLASS_NAME, "sendMsgFromContactPicker", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z")) {
        jstring stringName = t.env->NewStringUTF(name);
        jstring stringPhoneNum = t.env->NewStringUTF(phoneNum);
        jstring stringMsgContent = t.env->NewStringUTF(msgContent);
        ret = t.env->CallStaticBooleanMethod(t.classID, t.methodID, stringName, stringPhoneNum, stringMsgContent);

        t.env->DeleteLocalRef(t.classID);
        t.env->DeleteLocalRef(stringName);
        t.env->DeleteLocalRef(stringPhoneNum);
        t.env->DeleteLocalRef(stringMsgContent);
    }

    return ret;
}
           

在CCApplication-android里写调用jni的接口:

std::string Application::getContactPicker()
{
    return getContactPickerJNI();
}

bool Application::sendMsgFromContactPicker(const std::string &name, const std::string &phoneNum, const std::string &msgContent)
{
    return sendMsgFromContactPickerJNI(name.c_str(), phoneNum.c_str(), msgContent.c_str());
}
           

中间接口写完了,就要在java文件里面实现具体的功能。

找到安卓工程里面对应的Helper文件Cocos2dxHelper.java:

public static String getContactPicker() throws JSONException{
      //安卓之后部分特别重要的权限是需要单独申请的,而不是在安装的时候同意就好 
        int currentVersion = android.os.Build.VERSION.SDK_INT;
        if (currentVersion >= ) {
             if (ContextCompat.checkSelfPermission(sActivity, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) 
          {        ActivityCompat.requestPermissions(sActivity, new String[]{Manifest.permission.READ_CONTACTS}, );//申请权限  

          } 
          else 
          {
              //拥有当前权限  
          }  
        }

        ContentResolver resolver = sActivity.getContentResolver();
        String[] cols = {ContactsContract.PhoneLookup.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER};
        Cursor cursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,cols, null, null, null);
        //List<Map<String, String>> list = new ArrayList<Map<String, String>>();
        //Map<String, List<Map<String, String>>> info = new HashMap<String, List<Map<String, String>>>();

        JSONArray root = new JSONArray();
        for (int i = ; i < cursor.getCount(); i++) {
              //Map<String, String> map = new HashMap<String, String>();
            cursor.moveToPosition(i);
            int nameFieldColumnIndex = cursor.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME);
            int numberFieldColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
            String name = cursor.getString(nameFieldColumnIndex);
            String number = cursor.getString(numberFieldColumnIndex);

            JSONObject onePerson = new JSONObject();
            onePerson.put("name", name);
            onePerson.put("phonenum", number);

            root.put(onePerson);

            /*map.put("name", name);
            map.put("phonenum", number);
            list.add(map);*/
        }
        //info.put("info", list);
        //JSONObject json = JSONObject.fromObject( info ); 
        JSONObject writeJson = new JSONObject();
        writeJson.put("info", root);
        return writeJson.toString();
    }
           

代码虽然不难,但是有几个细节,不熟悉安卓直接来写的话可能会遇到的问题:

  1. 权限:安卓6.0之后要手动申请权限,不然调用获取通讯录代码的时候是不会询问用户授权的。在申请授权的时候又出现了引入包的问题,

    ContextCompat.checkSelfPermission()

    ActivityCompat.requestPermissions()

    需要

    import android.support.v4.app.ActivityCompat

    import android.support.v4.content.ContextCompat

    ,就需要自己安装一下v4的兼容包,根据我网上找到的资料更新兼容包,v4的包一直更新报错,没办法,只能直接自己去找v4的jar放进工程。(添加兼容包链接)另外一个要注意的地方是checkSelfPermission和requestPermissions的参数都要填一个activity,网上的例子大多直接写在一个activity里面的,这个参数就直接填写this。而Cocos2dxHelper没有extends神马activity,但在初始化的时候有给一个静态的activity赋值,所以参数填这个activity就好。
    cocos 获取通讯录联系人并发送短信
  2. 通讯录数据转json。在获取通讯录数据部分的代码里面,我注释掉的代码就是之前的思路,用map和list封装好之后直接转json,然后在map转json的时候编译报错,应该是各种合适的json库没装,所以直接换成用jsonobject来做,方便多了。

拿到了用户的通讯录就可以在自己的游戏里面绘制通讯录界面了:

cocos 获取通讯录联系人并发送短信

有了电话号码,又可以传回java里面发送短信:

public static boolean sendMsgFromContactPicker(String name, String phoneNum, String msgContent) { 
        boolean ret = false;
        try {
             PendingIntent pi=PendingIntent.getBroadcast(sActivity, , new Intent(), );
               SmsManager sms = SmsManager.getDefault();
               sms.sendTextMessage(phoneNum, null, msgContent, pi, null);
               ret = true;
             } catch (Exception e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
             }   
        return ret;
    }
           

安卓里面发送短信有两种,有一种是有回调函数的,可以知道发送短信的各种状态,上面代码这种是没有回调的。不会调起系统的发短信界面,悄悄咪咪就发了。如果用这种方法觉得体验感不好的话,可以自己在判断返回值为true的情况下客户端做一下弹框,按钮置灰等处理。另外,要记得添加权限:

<!--添加联系人的权限-->
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.SEND_SMS" />
           

IOS获取通讯录在网上随便一搜就能得到很全面的资料,主要有ios9之前和之后两个框架。做得仔细一点要判断一下系统来分前后写。这里主要记录一下9之后的方法。

先添加框架:ContactsUI.framework和Contacts.framework 。一个是提供UI界面一个是没提供UI界面的,我们项目只是需要拿到数据自己绘制界面,所以用不提供UI的就可以了。然后iOS10 需要在Info.plist配置NSContactsUsageDescription。

获取之前要判断一下是否有授权:

CNAuthorizationStatus authorizationStatus = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
   if (authorizationStatus == CNAuthorizationStatusNotDetermined)
   {
      CNContactStore *contactStore = [[CNContactStore alloc] init];
      [contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error)
       {
        if (granted)
        {       
        }
        else
        {      
        }
       }
     ];
    }
           

这里可以根据是否获取权限做一些体验感更好的处理,安卓也一样,比如说,在没有权限的时候询问一下是否跳转到系统打开权限的界面去重新开启权限神马的。有权限的时候就可以获取通讯录了。通讯录里面有很多信息,比如头像,地址,邮件神马的,由于我们游戏只需要展示姓名,就拿姓名和手机就好。

if ([CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized)  
    {  
        NSMutableDictionary * getDic = [[NSMutableDictionary alloc] initWithCapacity:];
        NSMutableArray *dicArray=[NSMutableArray array];
        NSArray *keysToFetch = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
        CNContactFetchRequest *fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:keysToFetch];
        CNContactStore *contactStore = [[CNContactStore alloc] init];
        [contactStore enumerateContactsWithFetchRequest:fetchRequest error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop)
          {
            NSLog(@"-------------------------------------------------------");
            NSString *givenName = contact.givenName;
            NSString *familyName = contact.familyName;
            //NSLog(@"givenName=%@, familyName=%@", givenName, familyName);
            NSString *stringName = [familyName stringByAppendingString:givenName];

            NSArray *phoneNumbers = contact.phoneNumbers;
            for (CNLabeledValue *labelValue in phoneNumbers) {
                NSMutableDictionary * tempDicName = [[NSMutableDictionary alloc] initWithCapacity:];
                NSString *label = labelValue.label;
                CNPhoneNumber *phoneNumber = labelValue.value;
                [tempDicName setObject:stringName forKey:@"name"];
                [tempDicName setObject:phoneNumber.stringValue forKey:@"phonenum"];
                [dicArray addObject:tempDicName];
                NSLog(@"label=%@, phone=%@", label, phoneNumber.stringValue);
                break;
            }
          }
        ];
     [getDic setObject:dicArray forKey:@"info"];
     NSError *error = nil;
     NSData *jsonData = [NSJSONSerialization dataWithJSONObject:getDic options:NSJSONWritingPrettyPrinted error:&error];
     NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
     std::string getJson = [jsonString UTF8String];
     return getJson;
   }
           

这里有几个地方需要注意一下。一个是一个联系人信息是一个数组,意思是一个联系人可能有加入家庭,公司神马的很多个电话和地址,具体怎么处理要看具体需求,比如我做得很简单,就拿第一个电话就可以了,需要拿到整个数组可以自己处理一下。再一个就是,在OC中要转成Json的处理。先设计好你的json,再根据你的json来处理数据。比如说我这里设计的,最外层是一个“info”的关键字带上一个大数组。在oc中,就可以用字典来封装。里层用一个字典来存姓名和电话,然后用一个数组来存放这个小字典。这个数组就是key为info的数组了。最后把这个字典转成NSData,再把NSData转成字符串,最后把字符串转成Json。

拿到联系人的信息之后,再来处理发短信的事情。

bool Application::sendMsgFromContactPicker(const std::string &name, const std::string &phoneNum, const std::string &msgContent)
{
    NSString* body = [NSString stringWithCString:msgContent.c_str() encoding:NSUTF8StringEncoding];
    NSString* num = [NSString stringWithCString:phoneNum.c_str() encoding:NSUTF8StringEncoding];
    Class messageClass = (NSClassFromString(@"MFMessageComposeViewController"));
    if(messageClass != nil){
    NSString *nsName= [NSString stringWithUTF8String:name.c_str()];
    NSString *nsPhoneNum= [NSString stringWithUTF8String:phoneNum.c_str()];
    NSString *nsMsgContent= [NSString stringWithUTF8String:msgContent.c_str()];
    MFMessageComposeViewController *messageVC=[[MFMessageComposeViewController alloc]init];
        messageVC.recipients =[NSArray arrayWithObjects:num, nil];
        messageVC.body = body;
        //messageVC.messageComposeDelegate = self;
        [kRootViewController presentViewController:messageVC animated:YES completion:nil];
        [[[[messageVC viewControllers] lastObject] navigationItem] setTitle:@""];
    } 
    return ;
}
           

发短信的功能本身很简单,就我的需求而言,就需要一个电话和一个发送短信内容。但是,在oc里面这个接口是需要实现一个代理和要重写一下一个发送短信后的状态回调。按照.mm文件混编来写,这里的是c++的基本语法,对oc不熟悉的我不知道怎么直接在CCApplication-ios.mm和.h文件里面继承那些需要继承的delegate。开始的时候,我想我干脆不要回调,不设置代理了。所以如上代码所示,messageVC.messageComposeDelegate = self这句是我注释掉的,这样写之后会造成,虽能能编过能运行能发短信,但是!!!调起的系统发短信界面导航条的后退和取消按钮不能点击,整个发送短信界面关闭不了。没办法,只能把发短信的功能单独写出来一个文件。

.h文件如下:

cocos 获取通讯录联系人并发送短信

.m文件如下:

cocos 获取通讯录联系人并发送短信

用个单例把发短信的功能单独写出来,赶脚有点蠢,但是这个功能保证只有这里用到,所以这么写也没猫病。在CCApplication调用发短信:

bool Application::sendMsgFromContactPicker(const std::string &name, const std::string &phoneNum, const std::string &msgContent)
{
  [[CCShowContactBridge sharedInstance] sendMsg:num withMsgBody:body];
  return ;
}
           

到这里,两个平台最基本的功能就完成了。这个功能细做的话还有很多可以完善的,比如我之前说的在没获取权限的时候提示是否跳转系统打开。拿到了通讯录信息之后可以按输入搜索出联系人。还有联系人的头像神马的也可以多处理一下。

p.s.因为之前获取玩家地里位置的功能是直接用的SDK里面带的功能,所以做之前我象征性的在群里问了一下,有没有谁用过通讯录的sdk啊,大家统一回复我,自己写啊,就两句代码,简单得很。其实,这样回复我的人,其实都没自己做过。最后,sdk没问到,还被一个妹纸要去了复制的代码,囧。做的时候呢,觉得大家又都等着我出版本,我又不熟悉其他几个平台,能不能按时做完有点忐忑。做的时候啊这个库没有,那个设置不知道在哪里,磕磕盼盼,写代码没用到几分钟,搞环境设置神马的搞了半天。做完了现在回顾一下才感觉,做的时候有千言万语要记录下来,现在一写,真就是几句代码的事情。。。好吧,每次做神马没做过的东东,做的时候都感觉好多好多事情要记录啊,完了一回顾发现就那点东东,不造大家是不是也这样。囧囧囧。

继续阅读