捋一捋關於APP 常用促活的一個手段:push。先來看一張圖,撇開平臺相關,基本流程大體和下圖一致:
- 手機是如何顯示“Push Notification”的?
- 服務器怎麼下發“Push Notification”到用戶手機上?
之前我也對這倆問題蠻好奇的,後來對接了一個相關的需求,就摸索了下相關知識,現整理如下。
手機 APP(這裏指商用APP,單機版 APP 不作考慮) 一般來說,都會添加一些手機服務商的 SDK(尤其是安卓手機,OV 華米,小米推送等,iOS 自己有另外的 APNS 服務),其目的就是讓手機和服務商做成**“長鏈接”**,方便一些業務場景的實現。
下面一起看下 APP 如何與業務服務器配合,實現用戶push notification 的。
- APP 向操作系統註冊 push 通知
- 操作系統向服務商申請 device token
- APP 將拿到的 device token 與用戶相關信息(如 userid)發送給業務服務器,由業務服務器進行記錄
- 業務服務器將 userid 對應的 device token 拿到,配合業務文案,發給服務商
- 服務商通過長鏈接,將信息推給 APP,來實現“push notification”
整個流程還算比較清晰,但是在一個公司內部,如何把這套系統做的更簡潔高效,還是需要花點時間的。
- 第三步中,用戶將device token 以及自己的信息傳給業務服務器,服務器需要進行記錄,這個 device tokenn 通常來說每個 APP 獨一份。業務服務器對每次 push 業務,都需要拿到每個人的 device token,然後加上業務文案,告訴服務商:“把這個文案推給device token”是 xxx 的設備上。
- 第四步中業務服務器向服務商請求的過程一般來說都不會很快,所以 qps 肯定高不到哪裏去。這就導致運營的某次 push 通知需求可能持續很久很久。所以一般會藉助消息隊列來達到“異步”、“削峯”處理。
- 和公司雙端的客戶端確認了下,這個 device token 上報的時機是不一樣的,安卓是會記錄上報狀態,遍歷所有推送渠道如 OV 華米,小米等渠道是否上報過或者本地賬號切換都會觸發重新上傳;iOS 是每次冷啓動,用戶登錄登出會進行解綁,然後重新上報。
- 發送量(業務服務器管理)、到達率(對接服務商)、點擊率(用戶上報到業務服務器)等十個很冗雜的活兒,一般來說 push 本身是一個較強的促活手段,對很多 APP 來說是標配組件,還是值得花時間來完整做一套出來的。
業務服務器向服務商請求 push, 以OPPO 的 push 爲例:
public function sendToOne($title, $desc, $data, $userToken, $expireTime=3600, $retry=true) {
$ret = 1;
if($this->accessToken == "" || $this->expire < time()) {
$this->getAccessToken(true);
}
if($this->isenabled == 1) {
$messages = array(
'target_type' => 2,
'registration_id' => $userToken,
'target_value' => $userToken,
'notification' => array(
//'app_message_id' => intval($data['id']),// 這個id不能重複,不然就不發了
'title' => $title,
'subtitle' => '',
'content' => $desc,
'click_action_type' => 1,
'click_action_activity' => 'com.xiaochang.easylive.OppoPushMessage',
'action_parameters' => array(
'uri' => json_encode(array(
'id' => intval($data['id']),
'title' => strval($title),
'url' => strval($data['url']),
'iscb' => strval($data['iscb']),)),
'source' => 'oppo',
),
//'call_back_url' => '',
//'call_back_parameter' => strval($data['id']),
)
);
if(!empty($expireTime)) {
$message['off_line_ttl'] = $expireTime;
}
$postData = array('auth_token' => $this->accessToken, 'message' => json_encode($messages),);
$pushUrl = self::$pushUrl.'/server/v1/message/notification/unicast';
$curl['url'] = $pushUrl;
$curl['post'] = http_build_query($postData);
list($code, $response) = $this->curlContent($curl);
LogError("response code for {$userToken} : $code, response ".json_encode($response), "/tmp/oppopush.log");
if($code == 0) {
$response = json_decode($response, true);
if($response["code"] == 29) {
$this->getAccessToken(true);
if($retry) {//取token然後再嘗試發一次。
$ret = $this->sendToOne($title, $desc, $data, $userToken, $expireTime, false);
}
} else if($response["code"] == 0) {
$ret = 0;
} else if($response["code"] == 33) {//超限了,今天不走oppo推送了
global $user_redis;
$num = isset($response["data"]["permits"]) ? intval($response["data"]["permits"]) : 1;
$user_redis->init()->hset(self::IS_OPPO_OUT_OF_LIMIT, date("YmdH"), $num);
$this->isenabled = 0;
}
}
}
return $ret;
}