第一步 申请极光账号 --> 创建应用-->填写应用名称和图标-->保存.
打开应用信息,可以看到一会所配置的AppKey
点击推送设置 配置android 和ios 项目对应的包名等信息;
如果你没有配置包名信息 , 极光推送提示:包名和AppKey不匹配:
第二步:
android 集成sdk 因为我是用androidStudio开发,就以androidStudio开发环境为例:
jcenter自动集成,不需要在项目中添加jar和so
在Project 根目录的主 gradle 中配置了jcenter支持
在module 的 gradle 中ndk和对应的appkey 和依赖。
添加依赖
compile 'cn.jiguang.sdk:jpush:3.1.1' compile 'cn.jiguang.sdk:jcore:1.1.9'
配置清单文件
添加权限:
<uses-permission android:name="你的app包名.permission.JPUSH_MESSAGE" />
<uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- Optional for location -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <!-- 用于开启 debug 版本的应用在6.0 系统上 层叠窗口权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_TASKS" />
配置service
<!--极光推送--> <service android:name="cn.jpush.android.service.PushService" android:process=":multiprocess" tools:node="replace"></service> <!--用户自定义的广播接收器--> <receiver android:name=".MyReceiver"<!--你自定义的receiver--> android:enabled="true" android:exported="false"> <intent-filter> <action android:name="cn.jpush.android.intent.REGISTRATION" /> <!--Required 用户注册SDK的intent--> <action android:name="cn.jpush.android.intent.MESSAGE_RECEIVED" /> <!--Required 用户接收SDK消息的intent--> <action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED" /> <!--Required 用户接收SDK通知栏信息的intent--> <action android:name="cn.jpush.android.intent.NOTIFICATION_OPENED" /> <!--Required 用户打开自定义通知栏的intent--> <action android:name="cn.jpush.android.intent.CONNECTION" /><!-- 接收网络变化 连接/断开 since 1.6.3 --> <category android:name="你的包名" /> </intent-filter> </receiver>
自定义MyReceiver:
package com.jstyle.demo; import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.text.TextUtils; import org.json.JSONException; import org.json.JSONObject; import cn.jpush.android.api.JPushInterface; /** * 自定义接收器 * 如果不定义这个 Receiver,则: 1) 默认用户会打开主界面 2) 接收不到自定义消息 */ public class MyReceiver extends BroadcastReceiver { private NotificationManager nm; @Override public void onReceive(Context context, Intent intent) { if (null == nm) { nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } Bundle bundle = intent.getExtras(); if (JPushInterface.ACTION_REGISTRATION_ID.equals(intent.getAction())) { //JPush用户注册成功 } else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) { //接收到推送下来的自定义消息 processCustomMessage(context, bundle); } else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) { //接收到推送下来的通知 } else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) { //用户点击打开了通知 String type = ""; String url = ""; String extras = bundle.getString(JPushInterface.EXTRA_EXTRA); //JPushInterface.EXTRA_EXTRA里的值是自定义的 在这儿我们自定义了两个值 type url if (!TextUtils.isEmpty(extras)) { try { JSONObject extraJson = new JSONObject(extras); if (null != extraJson && extraJson.length() > 0) { type = extraJson.getString("type"); url = extraJson.getString("url"); } } catch (JSONException e) { } if (TextUtils.isEmpty(type)) { //打开自定义的Activity Intent i = new Intent(context, SplashActivity.class); i.putExtras(bundle); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); context.startActivity(i); } else if (type.contains("1")) { //根据type值跳转指定页面 } } } else if (JPushInterface.ACTION_RICHPUSH_CALLBACK.equals(intent.getAction())) { //用户收到到RICH PUSH CALLBACK } else if (JPushInterface.ACTION_CONNECTION_CHANGE.equals(intent.getAction())) { } else { } } private void processCustomMessage(Context context, Bundle bundle) { if (SplashActivity.isForeground) { String message = bundle.getString(JPushInterface.EXTRA_MESSAGE); String extras = bundle.getString(JPushInterface.EXTRA_EXTRA); Intent msgIntent = new Intent(SplashActivity.MESSAGE_RECEIVED_ACTION); msgIntent.putExtra(SplashActivity.KEY_MESSAGE, message); if (!JpushUtil.isEmpty(extras)) { try { JSONObject extraJson = new JSONObject(extras); if (null != extraJson && extraJson.length() > 0) { msgIntent.putExtra(SplashActivity.KEY_EXTRAS, extras); } } catch (JSONException e) { } } context.sendBroadcast(msgIntent); } } }
JpushUtil文件
public class JpushUtil { public static final String PREFS_NAME = "JPUSH_EXAMPLE"; public static final String PREFS_DAYS = "JPUSH_EXAMPLE_DAYS"; public static final String PREFS_START_TIME = "PREFS_START_TIME"; public static final String PREFS_END_TIME = "PREFS_END_TIME"; public static final String KEY_APP_KEY = "JPUSH_APPKEY"; public static boolean isEmpty(String s) { if (null == s) return true; if (s.length() == 0) return true; if (s.trim().length() == 0) return true; return false; } // 校验Tag Alias 只能是数字,英文字母和中文 public static boolean isValidTagAndAlias(String s) { Pattern p = Pattern.compile("^[\u4E00-\u9FA50-9a-zA-Z_!@#$&*+=.|]+$"); Matcher m = p.matcher(s); return m.matches(); } // 取得AppKey public static String getAppKey(Context context) { Bundle metaData = null; String appKey = null; try { ApplicationInfo ai = context.getPackageManager().getApplicationInfo( context.getPackageName(), PackageManager.GET_META_DATA); if (null != ai) metaData = ai.metaData; if (null != metaData) { appKey = metaData.getString(KEY_APP_KEY); if ((null == appKey) || appKey.length() != 24) { appKey = null; } } } catch (NameNotFoundException e) { } return appKey; } // 取得版本号 public static String GetVersion(Context context) { try { PackageInfo manager = context.getPackageManager().getPackageInfo( context.getPackageName(), 0); return manager.versionName; } catch (NameNotFoundException e) { return "Unknown"; } } public static void showToast(final String toast, final Context context) { new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Toast.makeText(context, toast, Toast.LENGTH_SHORT).show(); Looper.loop(); } }).start(); } public static boolean isConnected(Context context) { ConnectivityManager conn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo info = conn.getActiveNetworkInfo(); return (info != null && info.isConnected()); } public static String getImei(Context context, String imei) { String ret = null; try { TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); ret = telephonyManager.getDeviceId(); } catch (Exception e) { Log.e(JpushUtil.class.getSimpleName(), e.getMessage()); } if (isReadableASCII(ret)){ return ret; } else { return imei; } } private static boolean isReadableASCII(CharSequence string){ if (TextUtils.isEmpty(string)) return false; try { Pattern p = Pattern.compile("[\\x20-\\x7E]+"); return p.matcher(string).matches(); } catch (Throwable e){ return true; } } public static String getDeviceId(Context context) { return JPushInterface.getUdid(context); } }
在 Application里初始化JPush
public class AppApplication extends Application { @Override public void onCreate() { super.onCreate(); JPushInterface.setDebugMode(false); // 设置开启日志,发布时请关闭日志 JPushInterface.init(this); // 初始化 JPush } }
在欢迎界面配置;
public class SplashActivity extends AppCompatActivity { public static boolean isForeground = false; private static final int MSG_SET_ALIAS = 1001; private final Handler mHandler = new Handler() { @Override public void handleMessage(android.os.Message msg) { super.handleMessage(msg); switch (msg.what) { case MSG_SET_ALIAS: JPushInterface.setAliasAndTags(getApplicationContext(), (String) msg.obj, null, mAliasCallback); break; default: } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //推送 JPushInterface.init(getApplicationContext()); registerMessageReceiver(); setAlias(); } @Override public void onResume() { super.onResume(); isForeground = true; JPushInterface.onResume(this); } @Override protected void onPause() { super.onPause(); isForeground = false; JPushInterface.onPause(this); } @Override protected void onDestroy() { unregisterReceiver(mMessageReceiver); super.onDestroy(); } private MessageReceiver mMessageReceiver; public static final String MESSAGE_RECEIVED_ACTION = "cn.edu.sjzc.student.broadcasterceiver.MESSAGE_RECEIVED_ACTION"; public static final String KEY_TITLE = "title"; public static final String KEY_MESSAGE = "message"; public static final String KEY_EXTRAS = "extras"; public void registerMessageReceiver() { mMessageReceiver = new MessageReceiver(); IntentFilter filter = new IntentFilter(); filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); filter.addAction(MESSAGE_RECEIVED_ACTION); registerReceiver(mMessageReceiver, filter); } public class MessageReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (MESSAGE_RECEIVED_ACTION.equals(intent.getAction())) { String messge = intent.getStringExtra(KEY_MESSAGE); String extras = intent.getStringExtra(KEY_EXTRAS); StringBuilder showMsg = new StringBuilder(); showMsg.append(KEY_MESSAGE + " : " + messge + "\n"); if (!JpushUtil.isEmpty(extras)) { showMsg.append(KEY_EXTRAS + " : " + extras + "\n"); } } } } private void setAlias() { String alias = getSharedPreferences("jstyleNews", MODE_PRIVATE).getString("uid", ""); if (JpushUtil.isEmpty(alias)) { return; } if (!JpushUtil.isValidTagAndAlias(alias)) { return; } //调用JPush API设置Alias mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_ALIAS, alias)); } private final TagAliasCallback mAliasCallback = new TagAliasCallback() { @Override public void gotResult(int code, String alias, Set<String> tags) { String logs; switch (code) { case 0: logs = "Set tag and alias success"; break; case 6002: logs = "Failed to set alias and tags due to timeout. Try again after 60s."; if (JpushUtil.isConnected(getApplicationContext())) { mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_ALIAS, alias), 1000 * 60); } else { } break; default: logs = "Failed with errorCode = " + code; } } }; }
设置关闭开启推送:
//重新开启消息推送 JPushInterface.resumePush(getApplicationContext()); //关闭消息推送 JPushInterface.stopPush(getApplicationContext());
OK,android 集成算完成了;
自定义消息推送PHP后台代码:
类文件Jpush:
<?php namespace Home\Common; class Jpush{ private $app_key = '你应用的appkey'; //待发送的应用程序(appKey),只能填一个。 private $master_secret = '你应用的Master Secret'; //主密码 private $url = "https://api.jpush.cn/v3/push"; //推送的地址 //若实例化的时候传入相应的值则按新的相应值进行 public function __construct($app_key=null, $master_secret=null,$url=null) { if ($app_key) $this->app_key = $app_key; if ($master_secret) $this->master_secret = $master_secret; if ($url) $this->url = $url; } /* $receiver 接收者的信息 all 字符串 该产品下面的所有用户. 对app_key下的所有用户推送消息 tag(20个)Array标签组(并集): tag=>array('昆明','北京','曲靖','上海'); tag_and(20个)Array标签组(交集): tag_and=>array('广州','女'); alias(1000)Array别名(并集): alias=>array('93d78b73611d886a74*****88497f501','606d05090896228f66ae10d1*****310'); registration_id(1000)注册ID设备标识(并集): registration_id=>array('20effc071de0b45c1a**********2824746e1ff2001bd80308a467d800bed39e'); $content 推送的内容。 $extras 附加字段 array类型 $m_time 保存离线时间的秒数默认为一天(可不传)单位为秒 */ public function push($receiver='all', $title='', $content='', $extras, $m_time='86400'){ $base64=base64_encode("$this->app_key:$this->master_secret"); $header=array("Authorization:Basic $base64","Content-Type:application/json"); $data = array(); $data['platform'] = 'android'; //目标用户终端手机的平台类型android,ios,winphone $data['audience'] = $receiver; //目标用户 //发送通知 $data['notification'] = array( //统一的模式--标准模式 //"alert"=>$content, //安卓自定义 "android"=>array( "alert"=>$content, "title"=>$title, "builder_id"=>1, "extras"=>$extras ), //ios的自定义 /*"ios"=>array( // "alert"=>$content, "badge"=>"1", "sound"=>"default", // "extras"=>array("type"=>$m_type, "txt"=>$m_txt) ),*/ ); //自定义信息 $data['message'] = array( "msg_content"=>$content, "extras"=>$extras ); //echo $m_time;die; //附加选项 $data['options'] = array( "sendno"=>time(), "time_to_live"=>$m_time, //保存离线时间的秒数默认为一天 "apns_production"=>0, //指定 APNS 通知发送环境:0开发环境,1生产环境。 ); $param = json_encode($data); $res = $this->push_curl($param,$header); //var_dump($res);die; if($res){ //得到返回值--成功已否后面判断 return $res; }else{ //未得到返回值--返回失败 return false; } } //推送的Curl方法 public function push_curl($param,$header) { if (empty($param)) { return false; } $postUrl = $this->url; $curlPost = $param; $ch = curl_init(); //初始化curl curl_setopt($ch, CURLOPT_URL,$postUrl); //抓取指定网页 curl_setopt($ch, CURLOPT_HEADER, 0); //设置header curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //要求结果为字符串且输出到屏幕上 curl_setopt($ch, CURLOPT_POST, 1); //post提交方式 curl_setopt($ch, CURLOPT_POSTFIELDS, $curlPost); curl_setopt($ch, CURLOPT_HTTPHEADER,$header); // 增加 HTTP Header(头)里的字段 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); // 终止从服务端进行验证 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); $data = curl_exec($ch); //运行curl curl_close($ch); return $data; } } ?>
控制器:
<?php namespace Home\Controller; use Think\Controller; use Home\Common\JPush; class JPushController extends Controller { public function pushMess(){ //vendor('Jpush/Jpush'); $pushObj = new Jpush(); //组装需要的参数 $receive = 'all'; //全部 //$receive = array('tag'=>array('1','2','3')); //标签 //$receive = array('alias'=>array('111')); //别名 $title = "闲聊话题"; $content = "啥时候下班啊"; $m_time = '86400'; //离线保留时间 $extras = array("url"=>"http://app.jstyle.cn/newwap/index.php/home/AppIndex/index","type"=>"1"); //自定义数组 //调用推送,并处理 $result = $pushObj->push($receive,$title,$content,$extras,$m_time); // var_dump($result);die; if($result){ $res_arr = json_decode($result, true); if(isset($res_arr['error'])){ //如果返回了error则证明失败 //错误信息 错误码 echo $res_arr['error']['message'];die; $this->error($res_arr['error']['message'].':'.$res_arr['error']['code'],U('Jpush/index')); }else{ //处理成功的推送...... //可执行一系列对应操作~ echo "推送成功";die; $this->success('推送成功~'); } }else{ //接口调用失败或无响应 $this->error('接口调用失败或无响应~'); } } }
下面附上Java后台集成demo代码:
控制器:
public class SendMessageController { @RequestMapping(value = RequestConstants.SENDMSG, method = RequestMethod.GET) @ResponseBody public String sendHeadMessage(HttpServletRequest request,String sendtime) { String appKey1="你应用的appkey"; String masterSecret1="你应用的Master Secret"; /*if(jpushway==2){//定时推送 SendScheduleMessage.sendSchedulePush(appKey, masterSecret, msg.getMsg(), msg.getEqutype(), msg.getTargetpsn(),ali,msgurl,msg.getMsgtype(), sendtime,isimage); }else if(jpushway==1){//及时推送 SendMessage.testSendPush(appKey,masterSecret,msg.getMsg(),msg.getEqutype(),msg.getTargetpsn(),ali,msgurl,msg.getMsgtype(),isimage); }*/ //SendMessage.testSendPush(appKey,masterSecret,msg.getMsg(),msg.getEqutype(),msg.getTargetpsn(),ali,msgurl,msg.getMsgtype()); Map<String,Object> map=new HashMap<>(); /*map.put("ctime", new Date()); if(sendtime!=null && sendtime!="" && sendtime.length()>0){ msg.setSendtime(sendtime); }else{ msg.setSendtime(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date())); }*/ SendMessage.testSendPush(appKey1,masterSecret1,"大家好啊"); return "1"; } }
发送消息类:
public class SendMessage{ protected static final Logger LOG = LoggerFactory.getLogger(SendMessage.class); public static JPushClient jpushClient=null; @SuppressWarnings("deprecation") public static void testSendPush(String appKey ,String masterSecret,String msg ) { jpushClient = new JPushClient(masterSecret, appKey); PushPayload payload=null; Audience ad=Audience.all(); Platform plat=Platform.android(); payload=buildPushObject_android_tag_alertWithTitle(plat,ad,msg); try { System.out.println(payload.toString()); PushResult result = jpushClient.sendPush(payload); System.out.println(result+"................................"); int num=result.sendno; long msg_id=result.msg_id; // jpushClient.g ReceivedsResult results = jpushClient.getReportReceiveds(msg_id+""); LOG.info("Got result - " + result); } catch (APIConnectionException e) { LOG.error("Connection error. Should retry later. ", e); } catch (APIRequestException e) { LOG.error("Error response from JPush server. Should review and fix it. ", e); LOG.info("HTTP Status: " + e.getStatus()); LOG.info("Error Code: " + e.getErrorCode()); LOG.info("Error Message: " + e.getErrorMessage()); LOG.info("Msg ID: " + e.getMsgId()); } } /** * ios有url广播所有人 */ public static PushPayload pushData(Platform plat,Audience ad,String msg) { PushPayload payload = PushPayload.newBuilder() .setPlatform(plat) .setAudience(ad) //Audience.alias("12") .setNotification(Notification.newBuilder() .addPlatformNotification(IosNotification.newBuilder() .setAlert(msg) .setBadge(1) .setSound("happy") .addExtra("from", "JPush") .build()) .build()) .setMessage(Message.content(msg)) .setOptions(Options.newBuilder() .build()) .build(); return payload; } public static PushPayload buildPushObject_all_alias_alert(String msg) { return PushPayload.newBuilder() .setPlatform(Platform.all())//设置接受的平台 .setAudience(Audience.all())//Audience设置为all,说明采用广播方式推送,所有用户都可以接收到 .setNotification(Notification.alert(msg)) .build(); } public static PushPayload buildPushObject_android_tag_alertWithTitle(Platform plat,Audience ad,String msg) { return PushPayload.newBuilder() .setPlatform(Platform.android()) .setAudience(ad) //Audience.alias("12") .setNotification(Notification.newBuilder() .addPlatformNotification(AndroidNotification.newBuilder() .setAlert(msg) .addExtra("from", "JPush") .addExtra("url","http://www.baidu.com") .build()) .build()) .setMessage(Message.content(msg)) /* .setOptions(Options.newBuilder() .setApnsProduction(isDev) .build()) */ .build(); /*PushPayload.newBuilder() .setPlatform(Platform.android()) .setAudience(ad) .setNotification(Notification.android(title, content, null)) .build(); */ } public static PushPayload buildPushObject_android_and_ios() { return PushPayload.newBuilder() .setPlatform(Platform.android_ios()) .setAudience(Audience.tag("tag1")) .setNotification(Notification.newBuilder() .setAlert("alert content") .addPlatformNotification(AndroidNotification.newBuilder() .setTitle("Android Title").build()) .addPlatformNotification(IosNotification.newBuilder() .incrBadge(1) .addExtra("extra_key", "extra_value").build()) .build()) .build(); } /** * ios有url广播所有人 */ public static PushPayload buildPushObject_ios_tagAnd_alertWithExtrasAndMessage(String msg,String url,Integer msgtype ,String isimage) { return PushPayload.newBuilder() .setPlatform(Platform.all()) .setAudience(Audience.alias("12")) .setNotification(Notification.newBuilder() .addPlatformNotification(IosNotification.newBuilder() .setAlert(msg) .setBadge(1) .setSound("happy") .addExtra("from", "JPush") .addExtra("url",url) // .addExtra("gid", "2aa5db8481647000358218e35c35894b") .addExtra("type", msgtype) //1 article 2 spu //0 我的信息界面 .addExtra("isimage", isimage) .build()) .build()) .setMessage(Message.content(msg)) .setOptions(Options.newBuilder() .setApnsProduction(false) .build()) .build(); } /** * 无别名ios * @return */ public static PushPayload buildPushObject_ios_nonetagtWithExtrasAndMessage(String msg,String goodsorarticle,Integer msgtype) { return PushPayload.newBuilder() .setPlatform(Platform.all()) // .setAudience(Audience.alias("12")) .setNotification(Notification.newBuilder() .addPlatformNotification(IosNotification.newBuilder() .setAlert(msg) .setBadge(1) .setSound("happy") .addExtra("from", "JPush") .addExtra("url",goodsorarticle) // .addExtra("gid", "2aa5db8481647000358218e35c35894b") .addExtra("type", "2") //1 article 2 spu //0 我的信息界面 .build()) .build()) .setMessage(Message.content(msg)) .setOptions(Options.newBuilder() .setApnsProduction(false) .build()) .build(); } public static PushPayload buildPushObject_ios_audienceMore_messageWithExtras() { return PushPayload.newBuilder() .setPlatform(Platform.android_ios()) .setAudience(Audience.newBuilder() .addAudienceTarget(AudienceTarget.tag("tag1", "tag2")) .addAudienceTarget(AudienceTarget.alias("alias1", "alias2")) .build()) .setMessage(Message.newBuilder() .addExtra("from", "JPush") .build()) .build(); } public static PushPayload buildPushObject_ios_audienceMore_messageWithExtras1(Integer [] arr) { return PushPayload.newBuilder() .setPlatform(Platform.android_ios()) .setAudience(Audience.newBuilder() .addAudienceTarget(AudienceTarget.tag("tag",arr.toString())) //.addAudienceTarget(AudienceTarget.alias("alias1", "alias2")) .build()) .setMessage(Message.newBuilder() .setMsgContent("视频即将开始,请您马上观看") .addExtra("from", "JPush") .build()) .build(); } }
记得依赖 jpush-client-3.2.9.jar
OK,想在整体一套极光推送完成了;
下面附上所有demo下载地址:
https://download.csdn.net/download/shanshan_1117/10344139