接入友盟推送实战
标签(空格分隔): Android
请注意:###
- 消息推送SDK 不支持在QEMU模拟器上调试,调试时请尽量使用真机。
- 友盟推送为什么对“广播”的发送次数有3次限制?博客
- 友盟消息推送API调用有什么频率或者次数的限制 博客
如果分别下发了两条消息,却只收到了一条,先不要着急。因为同一台设备在1分钟内收到同一个应用的多条通知时,不会重复提醒,同时在通知栏里新的通知会替换掉旧的通知。
1、首先是自己申请一个APPKey,不能偷懒,妈的!我就是想偷一下懒,直接用主管的demo里的APPKey,结果搞了半天没出,因为可能他在友盟上已经注销了这个AppKey对应的应用或者关闭了对这个应用的推送功能,所以肯定是不能收到推送的。
2、然后是最坑的一个Bug,可能是中国人的软肋吧,就是依赖中文,虽然自己是个程序员,但是很多时候还是用能用中文就尽量用中文吧。所以导致今天在友盟的官网上出现了用中文起名的悲剧,图中的用圆圈画住的部分,千万不要用中文,不然会出现意想不到的Bug。总的开说,在代码的世界里,最好一切都用英文!!!
3、第三个就是自己真的太粗心了,竟然认为所以然就没有加上这一步,真是该打!
原文:
在所有的Activity 的onCreate方法或在应用的BaseActivity的onCreate方法中添加:
PushAgent.getInstance(context).onAppStart();
**注意**: 此方法与统计分析sdk中统计日活的方法无关!请务必调用此方法!
如果不调用此方法,不仅会导致按照"几天不活跃"条件来推送失效,还将导致广播发送不成功以及设备描述红色等问题发生。可以只在应用的主Activity中调用此方法,但是由于SDK的日志发送策略,有可能由于主activity的日志没有发送成功,而导致未统计到日活数据。
我竟然还没看完就认为这行代码只是用来统计App启动的次数的,真是日了狗!殊不知没有这行代码的话,是无法接收到推送的,这行代码跟统计App的启动次数是两码事!
4、为什么一旦把应用关了就收不到推送?
除了跟着官方文档的说明步骤第四大步骤:轻松集成外,还要记得在自的项目中添加权限和在application标签中添加相应的组件,这样才能使应用不再运行在前台时,还可以开启一个在后台服务接受推送,才不会只能在应用开启时才能接到推送!
权限如下:
//下面列出的是必选的权限,还有可选的权限没有列出,例如前台是否可以显示通知这个可选权限
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
添加相应的友盟组件如下:
//注意一共有三处地方要将友盟得包名改为自己的包名
<receiver
android:name="com.umeng.message.NotificationProxyBroadcastReceiver"
android:exported="false" >
</receiver>
<receiver
android:name="com.umeng.message.SystemReceiver"
android:process=":push" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<receiver
android:name="com.umeng.message.MessageReceiver"
android:exported="false"
android:process=":push" >
<intent-filter>
<action android:name="org.agoo.android.intent.action.RECEIVE" />
</intent-filter>
</receiver>
<receiver
android:name="com.umeng.message.ElectionReceiver"
android:process=":push" >
<intent-filter>
<action android:name="org.agoo.android.intent.action.ELECTION_RESULT_V4" />
<category android:name="umeng" />
</intent-filter>
</receiver>
<receiver
android:name="com.umeng.message.RegistrationReceiver"
android:exported="false" >
<intent-filter>
<action android:name="com.zun1.whenask.intent.action.COMMAND" />//这是第一处地方要将友盟得包名改为自己的包名
</intent-filter>
</receiver>
<receiver android:name="com.umeng.message.UmengMessageBootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service
android:name="com.umeng.message.UmengService"
android:label="PushService"
android:exported="true"
android:process=":push" >
<intent-filter>
<action android:name="com.zun1.whenask.intent.action.START" />//这是第二处地方要将友盟得包名改为自己的包名
</intent-filter>
<intent-filter>
<action android:name="com.zun1.whenask.intent.action.COCKROACH" />//这是第三处地方要将友盟得包名改为自己的包名
</intent-filter>
<intent-filter>
<action android:name="org.agoo.android.intent.action.PING_V4" />
<category android:name="umeng" />
</intent-filter>
</service>
<service android:name="com.umeng.message.UmengIntentService"
android:process=":push" />
<service
android:name="com.umeng.message.UmengMessageIntentReceiverService"
android:process=":push"
android:exported="true" >
<intent-filter>
<action android:name="org.android.agoo.client.MessageReceiverService" />
</intent-filter>
<intent-filter>
<action android:name="org.android.agoo.client.ElectionReceiverService" />
</intent-filter>
</service>
<service android:name="com.umeng.message.UmengMessageCallbackHandlerService"
android:exported="false">
<intent-filter>
<action android:name="com.umeng.messge.registercallback.action" />
</intent-filter>
<intent-filter>
<action android:name="com.umeng.message.unregistercallback.action"/>
</intent-filter>
<intent-filter>
<action android:name="com.umeng.message.message.handler.action"/>
</intent-filter>
<intent-filter>
<action android:name="com.umeng.message.autoupdate.handler.action"/>
</intent-filter>
</service>
<service android:name="com.umeng.message.UmengDownloadResourceService" />
5、关于获取不到device_token的问题——参考友盟官方论坛
本人证实可行的方法:
【原理:因为首次获取device_token时用到的是 mPushAgent.enable(new IUmengRegisterCallback(),这种异步回调来获取device_token。注意点有两个:
第一:正是因为是异步回调的获取,所以不能保证在用到device_token的时候就已经完成了异步回调获取,所以会造成device_token还没获取到就已经执行了调用device_token的代码,造成device_token为空的原因,所以解决方法是一定要把那些用到device_token的全部代码放在获取到device_token的代码的后面,如第39-39行
第二:注意到第一点没有?首次获取device_token时用到的是 mPushAgent.enable(new IUmengRegisterCallback(),因为是首次启动应用及安装后运行的这种情况才会执行到它,如果退出应用后再次打开时就不走这个方法了,所以要做好是否第一次启动应用的判断,如第10-11行、36-37行、40-41行都是对是否第一次启动应用的判断以及数值记录】
private PushAgent mPushAgent;
private String device_token;
private static final int REQUEST_CODE=200;
private SharedPreferences sp;
private SharedPreferences.Editor editor;
mPushAgent = PushAgent.getInstance(this);
mPushAgent.onAppStart();
//在这里判断是否是第一次启动应用
Boolean isFirstOpen=sp.getBoolean("isFirstOpen",true);//默认是true,第一次启动!
if(isFirstOpen){//如果是第一次启动
Log.i("Lee","第一次启动应用");
mPushAgent.enable(new IUmengRegisterCallback() {//用这种回调方法的话,只有在首次安装时才会执行!用于初次获取测试设备的Device Token。
@Override
public void onRegistered(final String s) {
Log.i("Lee","go into onRegistered()");
new Handler().post(new Runnable() {
@Override
public void run() {
Log.i("Lee","go into run()");
int count=0;
do {//关键是在这里一直循环获取device_token,这里是在主线程不断循环,为什么不会造成ANR?难道是异步回调的原因吗?
count++;
device_token = UmengRegistrar.getRegistrationId(SetLanguage.this);
// device_token = mPushAgent.getRegistrationId();
try {
Thread.sleep(1000);
} catch (InterruptedException e)
{
Log.i("LeeInterruptedException",e.getMessage());
}
} while (TextUtils.isEmpty(device_token));//当device_token为null或""时,即还没获取到device_token,若是就继续循环
Log.i("Lee count",""+count);
Log.i("Lee ——onRegistered", "device_token=" + device_token);
editor.putBoolean("isFirstOpen",false);//已运行过应用一次,
editor.commit();
//以下是添加拿到device_token后的操作
AAAAAA..................
}else {
Log.i("Lee","不是第一次启动应用");
//mPushAgent.enable();//因为上面已经开启了mPushAgent.enable(new IUmengRegisterCallback(),所以不用再次开启mPushAgent.enable()
//device_token = UmengRegistrar.getRegistrationId(this);//使用这一句跟下面这句是一样的
device_token = mPushAgent.getRegistrationId();
Log.i("Lee ——Not onRegistered", "device_token=" + device_token);
//以下是添加拿到device_token后的操作
AAAAAA..................
}
下面这个是对上面代码的一些优化,因为经过试验,就算是写了循环去获取device_token ,但是结果显示循环只执行了一次,所以说用不着循环(如果你过大胆的话,不过建议还是要用循环比较安全保守)
PushAgent mPushAgent = PushAgent.getInstance(this);
mPushAgent.onAppStart();
mPushAgent.enable(new IUmengRegisterCallback() {
@Override
public void onRegistered(final String registrationId) {
//onRegistered方法的参数registrationId即是device_token
//new Handler().post(new Runnable() {
// @Override
// public void run() {
// Log.i("xiyuan", "device_token=" + registrationId);
// }
// });
//其实可以直接不写一个handler来执行一个runnable,然后再Run方法里打log,可以直接在onRegistered里直接打一个Log(不用写handler,如下) Log.i("xiyuan", "device_token=" + s);
}
});
//mPushAgent.enable();//因为上面已经开启了mPushAgent.enable(new IUmengRegisterCallback(),所以不用再次开启mPushAgent.enable()
//String device_token = UmengRegistrar.getRegistrationId(this);//使用这一句跟下面这句是一样的
//关键点是这里,不要在onRegistered方法里获取device_token,虽然onRegistered方法的参数registrationId就是device_token,但是经过本人实践,是获取不到的!所以才会在enable后加上下面这句。
String device_token = mPushAgent.getRegistrationId();
Log.i("xiyuani 2", "device_token=" + device_token);
下面是自己另外做的调试代码,错误例子:
//不使用异步的回调去一次获取device_token,第一次安装时获取不到,但那时第二次启动时可以获取到
// mPushAgent.enable();
// device_token = mPushAgent.getRegistrationId();
// //device_token = UmengRegistrar.getRegistrationId(SetLanguage.this);
// Log.i("Lee —NoCallBack", "device_token=" + device_token);
//不使用异步的回调去循环获取device_token,这样的不断循环会出现ANR!
// mPushAgent.enable();
// int count=0;
// do {//关键是在这里一直循环获取device_token
// count++;
// device_token = mPushAgent.getRegistrationId();
// //device_token = UmengRegistrar.getRegistrationId(SetLanguage.this);
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e)
// {
// Log.i("LeeInterruptedException",e.getMessage());
// }
// } while (TextUtils.isEmpty(device_token));//判断device_token是否为空,若是就继续循环
// Log.i("Lee count",""+count);
// Log.i("Lee —NoCallBack", "device_token=" + device_token);
下面这个是用计时器去不断获取device_token ,但那时还没验证
if (device_token.isEmpty()) {
timer.schedule(new TimerTask() {
@Override
public void run() {
if (device_token.isEmpty() && count < 10) {
String temp = UmengRegistrar.getRegistrationId(SetLanguage.this);
if (temp == null || temp.isEmpty()) {
count++;
} else {
device_token = temp;
count = 0;
}
} else {
count = 0;
cancel();
Log.d("device_token1", "task has cancle and deviceToken=" + device_token);
}
}
}, 0, 800);
}
6、关于API23 6.0的系统集成友盟时,权限android.permission.WRITE_SETTINGS"——系统设置权限的授权的解决方案
android.permission.WRITE_SETTINGS不能自动授权,也不能运行时请求授权,咋整啊?通过打开Intent来让用户设置。貌似SETTINGS的权限只能这么处理,from CommonsWare research Android 6.0 changes
@TargetApi(Build.VERSION_CODES.M)//当现在运行程序的系统是API23时才会执行这个方法
public void permissionToWRITE_SETTINGS(){
if(Build.VERSION.SDK_INT >= 23){
if(!Settings.System.canWrite(this)){
//打开设置界面让用户设置是否授权系统设置权限
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE);
}
}
}
@TargetApi(Build.VERSION_CODES.M)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE) {
if (Settings.System.canWrite(this)) {
//检查返回结果
Toast.makeText(SetLanguage.this, "WRITE_SETTINGS permission granted", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(SetLanguage.this, "WRITE_SETTINGS permission not granted", Toast.LENGTH_SHORT).show();
}
}
}
7、在友盟注册时忽略的一个小问题导致接收不到推送
官方文档说明如下:
app_master_secret要填写在安卓端的androidmanifest中还是只是填写在服务器的配置中?还是两个都要填写?不管怎样,两个端都写上就最安全!
使用情况:在有自己的服务器转接友盟的推送时,记得加上自己的服务器IP地址