Android GCM 一台设备有多个registrationid

Posted

技术标签:

【中文标题】Android GCM 一台设备有多个registrationid【英文标题】:Android GCM one device has multiple registrationid 【发布时间】:2013-05-29 07:55:01 【问题描述】:

我的系统具有 php 服务器端和 android 客户端应用程序。 Android 通过 web 服务发送参数,PHP 处理 GCM 端。 PHP 发送推送通知,在发送之前,它会从数据库中获取所有注册 ID。问题是,同一个设备可能有两个或多个registrationid。因此,推送通知会向同一设备发送两次或更多次。有没有办法解决这个问题?

【问题讨论】:

【参考方案1】:

GCM 可以更改registrationId,您必须在服务器端更新它。

当您发送消息时,结果可能包含将用于设备的新registrationId,因此您必须将旧registrationId 更新为新的。

看这个link at Interpreting a success response section

如果设置了 message_id,请检查 registration_id:如果设置了 registration_id,则将原始 ID 替换为服务器数据库中的新值(规范 ID)。注意原始ID不是结果的一部分,所以需要从请求中传入的registration_ids列表中获取(使用相同的索引)。

【讨论】:

谢谢大家,但主要问题不会更新。在场景中,用户可能有很多安卓设备。因为这 3 个设备可能有 7 个registrationId。我想发送 3 条推送消息,但 GCM 将发送 7 条。 通过 GCM 服务,一个 registrationId 标识一个设备/应用程序,因此如果用户有 3 个设备使用 GCM 连接到您的服务器,您应该有 3 个 regId。如果您的 regId 仍存储在 GCMRegistrar.getRegistrationId 上,请在 GCMRegistrar.register 之前检查,您正在生成超出需要的内容。 坏主意,因为:(terminal1) gcm (aaa) login user1 and user2, if you change (terminal1) gcm (bbb) if you send a GCM check terminal (bbb) and user2 or user1 not收到此通知。误解!【参考方案2】:

有时,Google 会更改注册 ID,您会关联多个 ID。发送通知的服务器(您的服务器)必须使用新 ID 更新数据库。

欲了解更多信息,请查看此文档:

http://developer.android.com/google/gcm/adv.html

他们说:

这是一个规范的 ID

在服务器端,只要应用程序运行良好,一切都应该正常工作。但是,如果应用程序中的错误触发同一设备的多个注册,则可能很难协调状态,并且您最终可能会收到重复的消息。

GCM 提供了一种称为“规范注册 ID”的工具,可以轻松地从这些情况中恢复。规范注册 ID 定义为您的应用程序请求的最后一次注册的 ID。这是服务器在向设备发送消息时应使用的 ID。

如果稍后您尝试使用不同的注册 ID 发送消息,GCM 将照常处理请求,但它会在响应的 registration_id 字段中包含规范注册 ID。确保用此规范 ID 替换存储在您服务器中的注册 ID,因为最终您使用的 ID 将停止工作。

【讨论】:

我在同一设备上面临 2 个不同的注册 ID 的问题,但返回 0 个 canonical_ids..【参考方案3】:

您只需在数据库中为每个设备存储一个唯一 ID。然后,您可以为每台设备发送一个推送通知。

我从未见过同一设备有多个注册 ID。奇怪的事情。

您是否正确管理了注册 ID 更新?

【讨论】:

谢谢大家,但主要问题不会更新。在场景中,用户可能有很多安卓设备。因为这 3 个设备可能有 7 个registrationId。我想发送 3 条推送消息,但 GCM 将发送 7 条。【参考方案4】:

这是我对这个问题的解决方案。 当您发送 gcm 通知时,您会收到这样的响应

Array
    (
        [multicast_id] => 12345678910
        [success] => 8
        [failure] => 3
        [canonical_ids] => 4
        [results] => Array
            (
                [0] => Array
                    (
                        [error] => NotRegistered
                    )

                [1] => Array
                    (
                        [message_id] => 0:1412242641156904%3dc89e2df9fd7ecd
                    )

                [2] => Array
                    (
                        [registration_id] => APA91bH3WdWwqFCVKbvZXsf-gj28iWU5oYbSCRZYFp987CHasxwT_HOiE7dp8212XID0FMGVG2n4NLohFsEGYJ-LEA07xsgsKfT00xModQcx5QgTBmJtxlWgeaWFpz29z-iCPYbvOHEhqfwHmN-HIps7DiWntcs-Qg
                        [message_id] => 0:1412242641155639%3dc89e2df9fd7ecd
                    )

                [3] => Array
                    (
                        [registration_id] => APA91bH3WdWwqFCVKbvZXsf-gj28iWU5oYbSCRZYFp987CHasxwT_HOiE7dp8212XID0FMGVG2n4NLohFsEGYJ-LEA07xsgsKfT00xModQcx5QgTBmJtxlWgeaWFpz29z-iCPYbvOHEhqfwHmN-HIps7DiWntcs-Qg
                        [message_id] => 0:1412242641155809%3dc89e2df9fd7ecd
                    )


                [4] => Array
                    (
                        [message_id] => 0:1412242641157971%3dc89e2df9fd7ecd
                    )


                [5] => Array
                    (
                        [registration_id] => APA91bGXo_gnfBZsvPoqJTYy1BWz0FQIkwlD1EmBtcxgWWfceYvd0ehWqVCtfd8n56VGYrvXCS2v48kTiA69BD7Sci0BA9a9bKTIg_MUEnDd79ssCK-miPG88DDGL4oKtB14cPbh-_xbgVRZllMOzwNZf_w5uJGR8g
                        [message_id] => 0:1412242641157969%3dc89e2df9fd7ecd
                    )


                [6] => Array
                    (
                        [registration_id] => APA91bGXo_gnfBZsvPoqJTYy1BWz0FQIkwlD1EmBtcxgWWfceYvd0ehWqVCtfd8n56VGYrvXCS2v48kTiA69BD7Sci0BA9a9bKTIg_MUEnDd79ssCK-miPG88DDGL4oKtB14cPbh-_xbgVRZllMOzwNZf_w5uJGR8g
                        [message_id] => 0:1412242641157967%3dc89e2df9fd7ecd
                    )

            )

    )

在数组中有一个索引canonical_ids 是 4,这意味着你有 4 个用户已经生成了新的 gcm id,现在你需要做的就是用你在上面的数组中收到的新的替换他们的 gcmid,接下来您发送 gcm 通知的时间。

我的 php 应用程序是基于 codeigniter 框架构建的,所以这就是我所做的。 我有一个名为 login 的模型,在那里我有一个函数从数据库返回 gcmids 以及用户的用户 ID 作为数组的索引

function getAllRegIds()
    $query = "SELECT gcmid,id FROM users_app WHERE gcmid IS NOT NULL AND gcmid != '' GROUP BY gcmid"; //group by incase a user loggedin with multiple userids in your a
    $query = $this->db->query($query);

    if($query->num_rows() > 0)
        $result = $query->result_array();
        $regids = array();

        foreach($result as $row)
            $regids[$row['id']] = $row['gcmid'];
        
        return $regids; 
    else
        return false;
    

数组看起来像这样

Array(
  [29] => APA91bEYda3DVb...
  [1] => APA91bF0yfdZjX4...
  [12] => APA91bG-9fsBGT-...
  [11] => APA91bGNRh_VWF...
  [3] => APA91bGXo_gnfBZ...
  [2] => APA91bH3WdWwqFC...
  [26] => APA91bHn8Ufwe4...
)

index 是值为 gcmid 的用户 ID

这个函数在控制器函数内部调用,这里是实现

public function sendtoall()
    $this->load->model('app/login');

    $result = $this->login->getAllRegIds(); //here i got all gcmids with userids as index

    $message = $this->input->post('message');
    $chunks = array_chunk($result,1000); //broken the array into chunks of 1000 ids because gcm can only send message to 1000 ids at a time

    $status = $this->sendNotification($chunks,$message);


    if(!empty($status) && is_array($status))
        foreach($status as $key=>$row)

            if(array_key_exists('canonical_ids',$row) && $row['canonical_ids'] > 0)
                $canonical_ids = $row['canonical_ids'];

                if(array_key_exists('results',$row) && !empty($row['results']) && is_array($row['results']))
                    foreach($row['results'] as $k=>$v)
                        if(array_key_exists('registration_id',$v))

                            $userid = array_search($chunks[$key][$k], $result);
                            $newgcmid  = $v['registration_id'];

                            $this->login->updateGCMId($userid,$newgcmid);
                        
                    
                
            
        
    

    echo json_encode($status);

正如您在 cmets 中一样,我已将数组分成 1000 个 id 的块,因为 gcm 可以一次向 1000 个 id 发送消息,并调用函数 sendNotification 返回 gcm 收到的响应,这是我粘贴的数组上面有所有规范的 id。

然后我检查状态是否不为空并且是一个数组,foreach 状态因为它可能有多个响应数组,因为我以 1000 个 id 的块发送通知,请参阅下面的 sendNotification 实现

然后我检查它是否有索引canonical_ids 并且大于0,这意味着有需要替换的id,然后我检查它是否有索引result,不为空并且是一个数组。

foreach result 并获取其中包含registration_id 索引的那些索引的键和值,这意味着需要在我们的数据库中替换这些id

通过搜索result数组,获取需要替换gcmid的用户的userid,用用户的oldgcm id($chunks[$key][$k],第一个参数是有的chunk的key你需要的gcmid,第二个是indexgcmid)

results 数组中获取新的gcmid

调用模型函数updateGCMId,传递useridnewgcmid

这里是sendNotificationupdateGCMId函数的实现。

private function sendNotification($regids,$message)
    $status = array();
    $result = $regids;
    if($result)
        foreach($result as $thousandids)
            $registatoin_ids=$thousandids;

            $msg=array("message"=>$message);



            $url='https://android.googleapis.com/gcm/send';
            $fields=array
             (
              'registration_ids'=>$registatoin_ids,
              'data'=>$msg
             );
            $headers=array
             (
              'Authorization: key=AIza..............',
              'Content-Type: application/json'
             );
            $ch=curl_init();
            curl_setopt($ch,CURLOPT_URL,$url);
            curl_setopt($ch,CURLOPT_POST,true);
            curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);
            curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
            curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
            curl_setopt($ch,CURLOPT_POSTFIELDS,json_encode($fields));
            $result=curl_exec($ch);
            curl_close($ch);


            $result = json_decode($result,1);

            $status[] = $result;

        
    

    return $status;

update函数:

public function updateGCMId($userid,$gcmid)
      $query = 'UPDATE users_app SET 
                  gcmid = "'.$gcmid.'" 
                WHERE id = "'.$userid.'"';
      $this->db->query($query);

【讨论】:

【参考方案5】:

我认为您的问题是如何从设备获取注册 ID。 您必须确保它会发送一次 regID,并且当 GCM 服务器请求刷新时,您必须将 regID 重新发送到您的网络服务器并存储它,覆盖旧的 redID。 请参阅this 部分 android 参考。

【讨论】:

谢谢大家,但主要问题不会更新。在场景中,用户可能有很多安卓设备。因为这 3 个设备可能有 7 个registrationId。我想发送 3 条推送消息,但 GCM 将发送 7 条。 你不明白我的回答...对于每个设备,Google GCM 服务器都希望定期刷新 ID...检查 android api 文档...错误就在开头:每台设备一次只能有一个 regID。【参考方案6】:

您将在推送响应中获得规范的 ID。您需要使用新的规范 ID 更新您的旧 ID。

还有另一种解决方案。更像是一个补丁。您可以在 GCM 请求 JSON 中传递参数“dry_run”。当我们将其设置为 true 时,它​​会向设备 ID 发送虚假消息并生成响应。

设备不会收到消息,但您会收到响应,因此您可以检查哪些设备 ID 在其结果中具有registration_ids,并将其从您的数据库中删除。

        $fields = array(
                    'registration_ids'  => $deviceId,
                    'data' => array( "message" =>'fake_message'),
                    'dry_run'=>true
                    );

希望对你有帮助。

【讨论】:

【参考方案7】:

终于得到了Duplicate Registration Id 的有效解决方案。全部都是关于使用规范 id 更新现有注册 id。

【讨论】:

【参考方案8】:

从 Android Studio 中的模板生成的后端附带的 Google 示例代码会为您完成这项工作。查看 MessageEndpoint 类,您会注意到他们正在检查规范 id,如果存在,则假设 regid 已更改,因此理想情况下需要针对该特定设备进行更新。

public void sendMessage(@Named("message") String message) throws IOException 
    if (message == null || message.trim().length() == 0) 
        log.warning("Not sending message because it is empty");
        return;
    
    // crop longer messages
    if (message.length() > 1000) 
        message = message.substring(0, 1000) + "[...]";
    
    Sender sender = new Sender(API_KEY);
    Message msg = new Message.Builder().addData("message", message).build();
    List<RegistrationRecord> records = ofy().load().type(RegistrationRecord.class).limit(10).list();
    for (RegistrationRecord record : records) 
        Result result = sender.send(msg, record.getRegId(), 5);
        if (result.getMessageId() != null) 
            log.info("Message sent to " + record.getRegId());
            String canonicalRegId = result.getCanonicalRegistrationId();
            if (canonicalRegId != null) 
                // if the regId changed, we have to update the datastore
                log.info("Registration Id changed for " + record.getRegId() + " updating to " + canonicalRegId);
                record.setRegId(canonicalRegId);
                ofy().save().entity(record).now();
            
         else 
            String error = result.getErrorCodeName();
            if (error.equals(Constants.ERROR_NOT_REGISTERED)) 
                log.warning("Registration Id " + record.getRegId() + " no longer registered with GCM, removing from datastore");
                // if the device is no longer registered with Gcm, remove it from the datastore
                ofy().delete().entity(record).now();
             else 
                log.warning("Error when sending message : " + error);
            
        
    

【讨论】:

以上是关于Android GCM 一台设备有多个registrationid的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 GCM 和 java 向多个设备发送推送通知

某些设备在 android 中未收到推送通知

Android GCM:.register 函数需要服务器吗?

Google Cloud Message (GCM) sent android is Error inValid Register

Android 2.2 上的 GCM SERVICE_NOT_AVAILABLE

如何使用 GCM 在 android 设备上分别显示每个推送通知