Android使用GCM实现前后台推送的功能

原创
2018/03/12 01:33
阅读数 1.4K

 前言:

GCM即Google Cloud Messaging,可以让开发者在客户端和服务器之间传递消息。GCM需要google service支持,国内采用比较多的是极光、友盟、信鸽等第三方推送。 由于项目要求在国外上线,所以本人使用Google自带的GCM实现推送 在推送的时候记得先连接VPN 。(2018年写的博客,Google的Firebase SDK不断升级,2020年我重新整理了一下内容,支持Android最新版本。) 下面简单分享在项目中如何实现GCM前后台推送。

用到的知识点:

  1. Retrofit实现网络请求
  2. GCM+NotificationCompat/NotificationChannels实现后台推送
  3. EventBus+FragmentTabHost实现前台(底部导航)推送

实现的代码:

1.Google官网注册应用

   首先去网址:https://console.firebase.google.com/ 去注册自己的应用,并下载google-services.json的文件,把它放到自己项目的app/目录。

2.添加依赖

  2.1 Project的build.gradle

buildscript {
  repositories {  
    google()  //如果没有的话,就添加
  }

  dependencies {
    //添加
    classpath 'com.google.gms:google-services:4.3.4'
  }
}

allprojects {
  repositories {   
    google()  //如果没有的话,就添加
  }
}

  2.2  Module的build.gradle

apply plugin: 'com.android.application'
//添加
apply plugin: 'com.google.gms.google-services'  

dependencies {
  //添加
  implementation 'com.google.firebase:firebase-messaging:20.3.0'
  implementation 'com.google.firebase:firebase-analytics:17.6.0'
}

3.配置AndroidMenifest.xml文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="项目包名">

    <!--连接网络权限-->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!--保证消息到达的时候,可以得到及时处理-->
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <!--声音震动的权限-->
    <uses-permission android:name="android.permission.VIBRATE"/>

    <application
        android:name="uk.co.common.base.MyApp"
        android:allowBackup="true"
        android:icon="@drawable/app_icon"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <!-- FCM和Analytics停用自动初始化 -->
        <meta-data
            android:name="firebase_messaging_auto_init_enabled"
            android:value="false" />
        <meta-data
            android:name="firebase_analytics_collection_enabled"
            android:value="false" />
       
        <service android:name=".ui.service.MyFirebaseMessagingService">
            <!--这里的resource的ic_notification要跟接口一致 -->
            <meta-data
                android:name="com.google.firebase.messaging.default_notification_icon"
                android:resource="@drawable/ic_notification" />
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
    </application>

</manifest>

4.MyFirebaseMessagingService获取token的接口

public class MyFirebaseMessagingService extends FirebaseMessagingService {
   /**
   * 每当生成新token时,都会触发onNewToken回调函数
   */
   @Override
    public void onNewToken(@NonNull String token) {
        //保存token
        SharedPreUtil.saveString(this, Const.fcm_token, token);
    }

}

 5.MyFirebaseMessagingService接收推送

public class MyFirebaseMessagingService extends FirebaseMessagingService {
    public void onMessageReceived(RemoteMessage remoteMessage) {
        //APP在前台运行的话就把消息推送底部导航,显示未读信息      
        if (remoteMessage.getData().size() > 0) {
            int datatype = Integer.valueOf(remoteMessage.getData().get("type"));
            int count = Integer.valueOf(remoteMessage.getData().get("count"));
            if (datatype == 1 || datatype == 2) {   // 1和2都表示不同的类型                      
                EventBus.getDefault().post(new FcmMessageEvent(type, count));//EventBus发送消息
            }
        }

        //APP在后台运行(包括锁屏状态)就显示Notification通知
        if (remoteMessage.getNotification() != null) {          
            Intent intent = new Intent(this, MainActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,PendingIntent.FLAG_ONE_SHOT);

            String channelId = getString(R.string.default_notification_channel_id);
            Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
            NotificationCompat.Builder notificationBuilder =
                    new NotificationCompat.Builder(this, channelId)
                            .setSmallIcon(R.mipmap.ic_notification)
                            .setContentTitle(remoteMessage.getNotification().getTitle())
                            .setContentText(remoteMessage.getNotification().getBody())
                            .setAutoCancel(true)
                            .setSound(defaultSoundUri)
                            .setContentIntent(pendingIntent);

            NotificationManager notificationManager =
                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

            // Since android Oreo notification channel is needed.
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                NotificationChannel channel = new NotificationChannel(channelId,
                        remoteMessage.getNotification().getTitle(),
                        NotificationManager.IMPORTANCE_DEFAULT);
                notificationManager.createNotificationChannel(channel);
            }
            notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
        }

    }
}

6.MainActivity接收EventBus消息显示底部导航的未读消息

public class MainActivity extends BaseActivity{
    ...

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EventBus.getDefault().register(this);//注册
        runtimeEnableAutoInit();//重新启用FCM
    }

    /*
     *重新启用FCM,会调用此方法
     */
    public void runtimeEnableAutoInit() {
        FirebaseMessaging.getInstance().setAutoInitEnabled(true);
    }

    /*
     *当有消息推送并且在前台运行的时候,调用此方法
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(FcmMessageEvent event) {
        type = event.getDatatype();
        count = event.getCount();
        int curtab = tabHost.getCurrentTab();//获取当前的tab
        tabHost.clearAllTabs();//清空之前的tab,因为已经初始化过一次了
        //update indicator
        for (int i = 0; i < texts.length; i++) {
            TabHost.TabSpec tabSpec = tabHost.newTabSpec(texts[i]);
            if (i == 1) { //Creidit Report
                if (count > 0) { //count有数据的话,显示未读条数
                    View v = getIndicatorView(texts[1], count, imageButton[1]);
                    tabSpec.setIndicator(v);
                    tabHost.addTab(tabSpec, fragments[i], null);
                } else { //count为0,隐藏未读条数
                    View v = getIndicatorView(texts[i], 0, imageButton[i]);
                    tabSpec.setIndicator(v);
                    tabHost.addTab(tabSpec, fragments[i], null);
                }

            } else {
                View v = getIndicatorView(texts[i], 0, imageButton[i]);
                tabSpec.setIndicator(v);
                tabHost.addTab(tabSpec, fragments[i], null);
            }
        }
        tabHost.setCurrentTab(2);//这句代码要加上,否则第一个tab会出现白屏
        tabHost.setCurrentTab(curtab);//设置当前的tab
    }

       @Override
    protected void onResume() {
        //APP在后台运行,点击Notification进入的页面
        int status = SharedPreUtil.getInt(Global.mContext, "status", 0);
        int type2 = SharedPreUtil.getInt(Global.mContext, "type", 0);
        if (status == 1 || status == 2) {  //status为1是登录进来的状态,status为2是注册进来的状态
            if (type2 == 1) { //当type=1进去页面              
                tabHost.setCurrentTab(1);
                CreditReportFragment.position = 2;
                tabHost.getTabWidget().getChildAt(1).setBackgroundResource(R.color.colorAccent2);
                SharedPreUtil.saveInt(Global.mContext,"type",0);
            } else if (type2 == 2) {//当type2=2进去另一个页面
                tabHost.setCurrentTab(1);
                CreditReportFragment.position = 0;
                tabHost.getTabWidget().getChildAt(1).setBackgroundResource(R.color.colorAccent2);
                SharedPreUtil.saveInt(Global.mContext,"type",0);
            }else {
                int currentTab = tabHost.getCurrentTab();
                tabHost.setCurrentTab(currentTab);
            }
        } else {//如果status既不是1,也不是2,要求用户直接登录
            startActivity(new Intent(MainActivity.this, LoginActivity.class));
            finish();
        }

        super.onResume();
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);//反注册
    }
    
    @Override
    public void initView() {
        tabHost = (FragmentTabHost) findViewById(R.id.tab_host);
        tabcontent = (FrameLayout) findViewById(R.id.tabcontent);
        tabHost.setup(this, getSupportFragmentManager(), R.id.tabcontent);
        //Add indicator
        for (int i = 0; i < texts.length; i++) {
            TabHost.TabSpec tabSpec = tabHost.newTabSpec(texts[i]);
            View v = getIndicatorView(texts[i], 0, imageButton[i]);
            tabSpec.setIndicator(v);
            tabHost.addTab(tabSpec, fragments[i], null);
        }
        ...
    }

     @Override
    public void initData() {
          //请求网络,将token的值发送给服务器,服务器收到后会推送消息
    }
    
     /*
      *获取底部导航icon和text,显示与隐藏未读条数的方法
      */
     private View getIndicatorView(String name, int num, int imgId) {
        //R.layout.guide_indicator_item:底部导航的icon和text布局
        View indicatorView = View.inflate(this, R.layout.guide_indicator_item, null);
        ImageView img = (ImageView) indicatorView.findViewById(R.id.img_indicator);
        tv_report_num = (TextView) indicatorView.findViewById(R.id.tv_report_num);//默认GONE
        TextView tv = (TextView) indicatorView.findViewById(R.id.tv_indicator);

    
        if (num == 0) {
            tv_report_num.setVisibility(View.GONE);

        } else {
            tv_report_num.setVisibility(View.VISIBLE);
            if (num > 99) {//自定义
                tv_report_num.setText(String.valueOf(num) + "+");
            } else {
                tv_report_num.setText(String.valueOf(num));
            }

        }
        tv.setText(name);
        img.setBackgroundResource(imgId);
        return indicatorView;
    }

}

7.FcmMessageEvent的类

public class FcmMessageEvent {
    private int datatype;
    private int count;

    public FcmMessageEvent(int datatype, int count) {
        this.datatype = datatype;
        this.count = count;
    }

    public int getDatatype() {
        return datatype;
    }

    public void setDatatype(int datatype) {
        this.datatype = datatype;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }


}

8.SettingNotificationsFragment推送的开关,用户可以决定要不要推送

public class SettingNotificationsFragment extends BaseFragment {
    ...

    @Override
    public int getLayoutRes() {
        return R.layout.setting_notifications;
    }

    @Override
    public void initView() {
        tvNotifications = (TextView) findView(R.id.tv_notifications);
        switchPush = (Switch) findView(R.id.switch_push);
        switchApp = (Switch) findView(R.id.switch_app);
        switchPush.setChecked(true);//默认选中
        switchApp.setChecked(true);
    }

    @Override
    public void initData() {
        //Retrofit请求网络
        commonPresenter = new CommonPresenter(this);
    }

    @Override
    public void onClick(View v, int id) {

        switchPush.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    if (!TextUtils.isEmpty(uuid)) {
                        String token = SharedPreUtil.getString(Global.mContext, "fcm_token", "");//取token
                        //请求推送的网络请求
                    }

                } else {
                    if (!TextUtils.isEmpty(uuid)) {
                        // 取消推送的网络请求
                    }
                }
            }
        });
        ....
       }
      ....

}

9.总结:

GCM前后台的推送功能已经实现啦,欢迎大家围观如果有什么疑问的话,可以留言联系我哦!

展开阅读全文
打赏
2
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
2
分享
返回顶部
顶部