美文网首页Android开发经验谈Android开发Android知识
极光推送(二)——推送的使用

极光推送(二)——推送的使用

作者: 奔跑的佩恩 | 来源:发表于2017-12-01 16:33 被阅读830次

前言

极光推送(一)——配置中讲过了极光推送的配置,这节讲讲极光推送的使用
参考文档
极光官网

下面以我写的demo为例进行讲解.

一.先展示我jpush文件结构
1.png

里面有6个类,它们的解释如下:

JpushConfig --------- 极光推送方法类,外部调用所有有关极光推送的方法,都通过此类
JpushHelper --------- 极光推送中tag,alias的设置,删除等具体逻辑均在此类中进行
TagAliasBean -------- 设置极光推送的tag,alias需要的model类
JpushReceiver ------- 定义极光推送的广播类,用于接收极光推送的消息
JpushContract -------- 处理极光推送消息的接口
JpushPresenter ------ 极光推送消息处理的具体类,该类实现JpushContract接口,
                      用户需要处理的极光消息均在此类中做具体处理

ok,下面来具体讲讲每个类。

二.TagAliasBean设置极光的model类

极光推送的设置无非涉及到一个tag,一个alias的操作,这里将要设置的tag和alias统一封装到TagAliasBean,便于后续处理,以下为TagAliasBean代码:

package com.jpushdemo.jpush;

import java.util.Set;

/**
 * Title:
 * Description:
 * <p>
 * Created by pei
 * Date: 2017/11/30
 */
public class TagAliasBean{

    private int action;
    private Set<String> tags;
    private String alias;
    private boolean isAliasAction;

    public int getAction() {
        return action;
    }

    public void setAction(int action) {
        this.action = action;
    }

    public Set<String> getTags() {
        return tags;
    }

    public void setTags(Set<String> tags) {
        this.tags = tags;
    }

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public boolean isAliasAction() {
        return isAliasAction;
    }

    public void setAliasAction(boolean aliasAction) {
        isAliasAction = aliasAction;
    }
}

其中 action为增删改查的类型,tags为要设置的tag集合,alias为要设置的别名,isAliasAction则用来表示当前操作的data是否为alias。

三.JpushHelper类

此类实现tag和alias相关操作(添加,删除,设置等)的具体实现细节

package com.jpushdemo.jpush;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.SparseArray;

import com.jpushdemo.app.AppContext;
import com.jpushdemo.util.LogUtil;
import com.jpushdemo.util.NetUtil;

import java.lang.ref.WeakReference;

import cn.jpush.android.api.JPushInterface;
import cn.jpush.android.api.JPushMessage;

/**
 * Title:Tag,Alias逻辑处理相关帮助类
 * Description:
 * <p>
 * Created by pei
 * Date: 2017/11/30
 */
public class JpushHelper {

    /**增加*/
    public static final int ACTION_ADD = 1;
    /**覆盖*/
    public static final int ACTION_SET = 2;
    /**删除部分*/
    public static final int ACTION_DELETE = 3;
    /**删除所有*/
    public static final int ACTION_CLEAN = 4;
    /**查询*/
    public static final int ACTION_GET = 5;
    /**检测**/
    public static final int ACTION_CHECK = 6;
    /**重新设置messageCode**/
    private static final int DELAY_SEND_ACTION=7;
    /**重设时长间隔**/
    private static final long DELAY_TIME=1000*60;//一分钟

    public static int mSequence=1;
    private static JpushHelper mInstance;
    private SparseArray<TagAliasBean> mTagAliasActionCache = new SparseArray<TagAliasBean>();

    private Handler delaySendHandler = new JpushHandler(JpushHelper.this);

    private JpushHelper(){}

    public static JpushHelper getInstance(){
        if(mInstance == null){
            synchronized (JpushHelper.class){
                if(mInstance == null){
                    mInstance = new JpushHelper();
                }
            }
        }
        return mInstance;
    }

    public void put(int sequence,TagAliasBean tagAliasBean){
        mTagAliasActionCache.put(sequence,tagAliasBean);
    }

    public TagAliasBean get(int sequence){
        return mTagAliasActionCache.get(sequence);
    }
    public TagAliasBean remove(int sequence){
        return mTagAliasActionCache.get(sequence);
    }



    /**处理tag和alias的操作**/
    public void handleAction(int sequence, TagAliasBean tagAliasBean){
        Context context= AppContext.getInstance();
        if(tagAliasBean==null){
            LogUtil.e(JpushHelper.class,"=====tagAliasBean was null=====");
            return;
        }
        put(sequence,tagAliasBean);
        if(tagAliasBean.isAliasAction()){
            //别名的处理(Alias)
            handleAlias(context,sequence,tagAliasBean);
        }else{
            //标签的处理(Tag)
            handleTags(context,sequence,tagAliasBean);
        }
    }

    /**别名的处理(Alias)**/
    private void handleAlias(Context context,int sequence, TagAliasBean tagAliasBean){
        switch (tagAliasBean.getAction()){
            case ACTION_GET://获取
                JPushInterface.getAlias(context,sequence);
                break;
            case ACTION_DELETE://删除
                JPushInterface.deleteAlias(context,sequence);
                break;
            case ACTION_SET://设置
                JPushInterface.setAlias(context,sequence,tagAliasBean.getAlias());
                break;
            default:
                LogUtil.e(JpushHelper.class,"====unsupport alias action type===");
                break;
        }
    }

    /**标签的处理(Tag)**/
    private void handleTags(Context context,int sequence, TagAliasBean tagAliasBean){
        switch (tagAliasBean.getAction()) {
            case ACTION_ADD://添加
                JPushInterface.addTags(context, sequence, tagAliasBean.getTags());
                break;
            case ACTION_SET://设置
                JPushInterface.setTags(context, sequence, tagAliasBean.getTags());
                break;
            case ACTION_DELETE://删除
                JPushInterface.deleteTags(context, sequence, tagAliasBean.getTags());
                break;
            case ACTION_CHECK://检查
                //一次只能check一个tag
                String tag = (String)tagAliasBean.getTags().toArray()[0];
                JPushInterface.checkTagBindState(context,sequence,tag);
                break;
            case ACTION_GET://获取
                JPushInterface.getAllTags(context, sequence);
                break;
            case ACTION_CLEAN://清除
                JPushInterface.cleanTags(context, sequence);
                break;
            default:
                LogUtil.e(JpushHelper.class,"====unsupport tag action type===");
                break;
        }
    }

    /**是否需要重新设置**/
    private boolean retryActionIfNeeded(int errorCode,TagAliasBean tagAliasBean){
        if(!NetUtil.isNetworkConnected()){
            //检测网络链接
            LogUtil.e(JpushHelper.class,"=======no network=======");
            return false;
        }
        //返回的错误码为6002 超时,6014 服务器繁忙,都建议延迟重试
        if(errorCode == 6002 || errorCode == 6014){
            LogUtil.e(JpushHelper.class,"=======need retry=======");
            if(tagAliasBean!=null){
                Message message = new Message();
                message.what = DELAY_SEND_ACTION;
                message.obj = tagAliasBean;
                delaySendHandler.sendMessageDelayed(message,DELAY_TIME);
                return true;
            }
        }
        return false;
    }

    static class JpushHandler extends Handler{
        //弱引用(引用外部类)
        WeakReference<JpushHelper>mCls;

        JpushHandler(JpushHelper cls){
           //构造弱引用
            mCls=new WeakReference<JpushHelper>(cls);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //通过弱引用获取外部类.
            JpushHelper cls = mCls.get();
            //进行非空再操作
            if (cls != null) {
                switch (msg.what) {
                    case DELAY_SEND_ACTION:
                        if (msg.obj != null && msg.obj instanceof TagAliasBean) {
                            LogUtil.e(JpushHelper.class, "===on delay time===");
                            JpushHelper.mSequence++;
                            TagAliasBean tagAliasBean = (TagAliasBean) msg.obj;
                            cls.put(JpushHelper.mSequence, tagAliasBean);
                            cls.handleAction(JpushHelper.mSequence, tagAliasBean);
                        } else {
                            LogUtil.e(JpushHelper.class, "====unexcepted - msg obj was incorrect===");
                        }
                        break;
                    default:
                        break;
                }
            }
        }
    }

    /**tag增删查改的操作会在此方法中回调结果**/
    public void onTagOperatorResult(Context context, JPushMessage jPushMessage){
        int sequence = jPushMessage.getSequence();
        LogUtil.e(JpushHelper.class,"action - onTagOperatorResult, sequence:"+sequence+",tags:"+jPushMessage.getTags());
        LogUtil.e(JpushHelper.class,"tags size:"+jPushMessage.getTags().size());
        //根据sequence从之前操作缓存中获取缓存记录
        TagAliasBean tagAliasBean =get(sequence);
        if(tagAliasBean == null){
            LogUtil.e(JpushHelper.class,"获取缓存记录失败");
            return;
        }
        if(jPushMessage.getErrorCode() == 0){
            LogUtil.e(JpushHelper.class,"action - modify tag Success,sequence:"+sequence);
            remove(sequence);
        }else{
            String logs=null;
            if(jPushMessage.getErrorCode() == 6018){
                //tag数量超过限制,需要先清除一部分再add
                logs = "tag数量超过限制,需要先清除一部分再add";
            }
            logs += ", errorCode:" + jPushMessage.getErrorCode();
            if(!retryActionIfNeeded(jPushMessage.getErrorCode(),tagAliasBean)) {
                LogUtil.e(JpushHelper.class,logs);
            }
        }
    };

    /**查询某个tag与当前用户的绑定状态的操作会在此方法中回调结果**/
    public void onCheckTagOperatorResult(Context context, JPushMessage jPushMessage){
        int sequence = jPushMessage.getSequence();
        LogUtil.e(JpushHelper.class,"action - onCheckTagOperatorResult, sequence:"+sequence+",checktag:"+jPushMessage.getCheckTag());
        //根据sequence从之前操作缓存中获取缓存记录
        TagAliasBean tagAliasBean =get(sequence);
        if(tagAliasBean == null){
            LogUtil.e(JpushHelper.class,"===onCheckTagOperatorResult==获取缓存记录失败==");
            return;
        }
        if(jPushMessage.getErrorCode() == 0){
            LogUtil.e(JpushHelper.class,"tagBean:"+tagAliasBean);
            remove(sequence);
        }else{
            if(!retryActionIfNeeded(jPushMessage.getErrorCode(),tagAliasBean)) {
                LogUtil.e(JpushHelper.class,"====="+jPushMessage.getErrorCode()+"====");
            }
        }
    }

    /**alias相关的操作会在此方法中回调结果**/
    public void onAliasOperatorResult(Context context, JPushMessage jPushMessage) {
        int sequence = jPushMessage.getSequence();
        LogUtil.e(JpushHelper.class,"action - onAliasOperatorResult, sequence:"+sequence+",alias:"+jPushMessage.getAlias());
        //根据sequence从之前操作缓存中获取缓存记录
        TagAliasBean tagAliasBean =get(sequence);
        if(tagAliasBean == null){
            LogUtil.e(JpushHelper.class,"===onAliasOperatorResult获取缓存记录失败===");
            return;
        }
        if(jPushMessage.getErrorCode() == 0){
            LogUtil.e(JpushHelper.class,"action - modify alias Success,sequence:"+sequence);
            remove(sequence);
        }else{
            if(!retryActionIfNeeded(jPushMessage.getErrorCode(),tagAliasBean)) {
                LogUtil.e(JpushHelper.class,"=====alias, errorCode:"+jPushMessage.getErrorCode()+"====");
            }
        }
    }
}
四.JpushConfig类

此类其实是对JpushHelper类中方法的一个提炼,目的是方便用户在activity中调用极光tag和alias设置的方法,用户只需要用JpushConfig去调用极光相关方法就好,无需关注方法的实现细节

package com.jpushdemo.jpush;

import android.content.Context;

import com.jpushdemo.app.AppContext;
import com.jpushdemo.util.StringUtil;

import java.util.LinkedHashSet;
import java.util.Set;

import cn.jpush.android.api.JPushInterface;

/**
 * Title:
 * Description:
 * <p>
 * Created by pei
 * Date: 2017/11/30
 */
public class JpushConfig {

    private Context mContext;

    private JpushConfig() {
        mContext= AppContext.getInstance();
    }

    private static class Holder {
        private static JpushConfig instance = new JpushConfig();
    }

    public static JpushConfig getInstance() {
        return Holder.instance;
    }

    /**初始化极光,在Application的oncreate()方法中调用**/
    public void initJpush(){
        //极光推送
        JPushInterface.setDebugMode(true);
        JPushInterface.init(mContext);
    }

    /**添加tag**/
    public void addTag(String tag){
        handleTag(tag,JpushHelper.ACTION_ADD);
    }

    /**设置tag**/
    public void setTag(String tag){
        handleTag(tag,JpushHelper.ACTION_SET);
    }

    /**删除tag**/
    public void deleteTag(String tag){
        handleTag(tag,JpushHelper.ACTION_DELETE);
    }

    /**获取所有tag**/
    public void getAllTags(){
        handleTag(null,JpushHelper.ACTION_GET);
    }

    /**清除所有tag**/
    public void cleanAllTags(){
        handleTag(null,JpushHelper.ACTION_CLEAN);
    }

    /**检测tag**/
    public void checkTag(Set<String> tags){
       if(tags==null){
           return;
       }
        TagAliasBean tagAliasBean = new TagAliasBean();
        tagAliasBean.setAction(JpushHelper.ACTION_CHECK);
        tagAliasBean.setAlias(null);
        tagAliasBean.setTags(tags);
        tagAliasBean.setAliasAction(false);
        JpushHelper.mSequence++;
        JpushHelper.getInstance().handleAction(JpushHelper.mSequence,tagAliasBean);
    }

    /**设置Alias**/
    public void setAlias(String alias){
        if(StringUtil.isNotEmpty(alias)){
            handleAlias(alias,JpushHelper.ACTION_SET);
        }
    }

    /**获取alias**/
    public void getAlias(){
        handleAlias(null,JpushHelper.ACTION_GET);
    }

    /**删除alias**/
    public void deleteAlias(){
        handleAlias(null,JpushHelper.ACTION_DELETE);
    }


    private void handleTag(String tag,int action){
        Set<String> tags = new LinkedHashSet<>();
        if(StringUtil.isNotEmpty(tag)){
            tags.add(tag);
        }
        TagAliasBean tagAliasBean = new TagAliasBean();
        tagAliasBean.setAction(action);
        tagAliasBean.setAlias(null);
        tagAliasBean.setTags(tags);
        tagAliasBean.setAliasAction(false);
        JpushHelper.mSequence++;
        JpushHelper.getInstance().handleAction(JpushHelper.mSequence,tagAliasBean);
    }

    private void handleAlias(String alias,int action){
        TagAliasBean tagAliasBean = new TagAliasBean();
        tagAliasBean.setAction(action);
        tagAliasBean.setAlias(alias);
        tagAliasBean.setTags(null);
        tagAliasBean.setAliasAction(true);
        JpushHelper.mSequence++;
        JpushHelper.getInstance().handleAction(JpushHelper.mSequence,tagAliasBean);
    }

    /**停止极光服务**/
    public void stopJpush(){
        if(!JPushInterface.isPushStopped(mContext)){
            JPushInterface.stopPush(mContext);
        }
    }

    /**恢复极光推送**/
    public void resumeJPush(){
        JPushInterface.resumePush(mContext);
    }

}

JpushConfig类中方法虽多,但我们用到的就那么几个,首先是在项目中自己的application类中初始化极光,需要调用到initJpush()方法,然后是我们在需要设置极光标签(tag)和别名(alias)的地方会用到setTag(String tag)和setAlias(String alias)方法,最后需要注意的是极光服务停止和恢复,即stopJpush()和resumeJPush()方法可能会用到。

然后就是极光消息的获取了,极光消息的获取,我们是通过定义一个广播接收,下面看看广播接收的代码

五.JpushReceiver接收极光消息的广播类
package com.jpushdemo.jpush;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import com.jpushdemo.util.LogUtil;

import cn.jpush.android.api.JPushInterface;

/**
 * 自定义接收器
 * <p>
 * 如果不定义这个 Receiver,则:
 * 1) 默认用户会打开主界面
 * 2) 接收不到自定义消息
 */
public class JpushReceiver extends BroadcastReceiver {

    private JpushPresenter jpushPresenter = null;
    private Context mContext;

    @Override
    public void onReceive(Context context, Intent intent) {
        this.mContext=context;
        Bundle bundle = intent.getExtras();
        if (bundle == null) {
            return;
        }
        if (jpushPresenter == null) {
            jpushPresenter = new JpushPresenter();
        }
        jpushPresenter.setContext(context);
        if (JPushInterface.ACTION_REGISTRATION_ID.equals(intent.getAction())) {
            // SDK 向 JPush Server 注册所得到的注册 全局唯一的 ID
            String regId = bundle.getString(JPushInterface.EXTRA_REGISTRATION_ID);
            LogUtil.e(JpushReceiver.class, "-Registration Id : " + regId);
            //send the Registration Id to your server...
        } else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) {
            LogUtil.e(JpushReceiver.class, "-推送消息: " + bundle.getString(JPushInterface.EXTRA_MESSAGE));
            jpushPresenter.doProcessPushMessage(bundle);

        } else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) {
            LogUtil.e(JpushReceiver.class, "-推送通知: " + bundle.getString(JPushInterface.EXTRA_ALERT));
            jpushPresenter.doProcessPusNotify(bundle);

        } else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) {
            LogUtil.e(JpushReceiver.class, "-点击推送的通知: " + bundle.getString(JPushInterface.EXTRA_ALERT));
            jpushPresenter.doOpenPusNotify(bundle);
        } else if (JPushInterface.ACTION_RICHPUSH_CALLBACK.equals(intent.getAction())) {
            LogUtil.e(JpushReceiver.class,"-用户收到到RICH PUSH CALLBACK: " + bundle.getString(JPushInterface.EXTRA_EXTRA));
            //在这里根据 JPushInterface.EXTRA_EXTRA 的内容处理代码,比如打开新的Activity, 打开一个网页等..

        } else if (JPushInterface.ACTION_CONNECTION_CHANGE.equals(intent.getAction())) {
            boolean connected = intent.getBooleanExtra(JPushInterface.EXTRA_CONNECTION_CHANGE, false);
            LogUtil.e(JpushReceiver.class, "-" + intent.getAction() + " connected state change to " + connected);
        } else {
            LogUtil.e(JpushReceiver.class, "-Unhandled intent - " + intent.getAction());
        }
    }

}

这是一个静态广播,需要在mainfast中注册,注册代码如下:

 <application
        android:name=".app.AppContext"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.NoActionBar">
        <activity android:name=".ui.MainActivity">
          ....
          .....
         </activity>



        <!-- Jpush for receive  用户自定义的广播接收器 -->
        <receiver
            android:name=".jpush.JpushReceiver"
            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="com.jpushdemo" />
            </intent-filter>
        </receiver>
    </application>

receiver标签中包含的 <category android:name="com.jpushdemo" />中name需要填当前项目的包名

仔细看JpushReceiver中有一个JpushPresenter对象,虽然JpushReceiver负责接收极光消息,但具体的处理却交给了JpushPresenter类,而JpushPresenter类是实现JpushContract接口的。

六.JpushContract接口类
package com.jpushdemo.jpush;

import android.os.Bundle;

/**
 * @author pei
 * @date 2016/12/24.19:47
 */

public interface JpushContract {
    void doProcessPushMessage(Bundle bundle);

    void doProcessPusNotify(Bundle bundle);

    void doOpenPusNotify(Bundle bundle);
}

然后是处理消息类JpushPresenter类的代码

七.JpushPresenter具体处理消息类
package com.jpushdemo.jpush;

import android.content.Context;
import android.os.Bundle;

import com.jpushdemo.util.LogUtil;

import cn.jpush.android.api.JPushInterface;

/**
 * Title:极光推送具体处理类
 * Description:
 * <p>
 * Created by pei
 * Date: 2017/11/30
 */
public class JpushPresenter implements JpushContract {

    private Context mContext;

    public JpushPresenter() {
    }

    public void setContext(Context context) {
        this.mContext = context;
    }

    @Override
    public void doProcessPushMessage(Bundle bundle) {
        String message = bundle.getString(JPushInterface.EXTRA_MESSAGE);
        String extras = bundle.getString(JPushInterface.EXTRA_EXTRA);

        LogUtil.e(JpushPresenter.class,"=====doProcessPushMessage=======");

    }

    @Override
    public void doProcessPusNotify(Bundle bundle) {
        LogUtil.e(JpushPresenter.class,"=====doProcessPusNotify=======");
    }

    @Override
    public void doOpenPusNotify(Bundle bundle) {
        LogUtil.e(JpushPresenter.class,"=====doOpenPusNotify=======");
    }
}

八.Jpush在项目中具体使用

在自己项目的application中初始化Jpush

public class AppContext extends Application{

    private static AppContext INSTANCE;

    public static synchronized AppContext getInstance() {
        return INSTANCE;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        INSTANCE = this;

        //初始化极光推送
        JpushConfig.getInstance().initJpush();
    }
}

在要设置极光tag和alias的地方进行设置

                //设置标签
                JpushConfig.getInstance().setTag("1.0.1");
                //设置别名
                JpushConfig.getInstance().setAlias("android");

记得要将上面的''1.0.1"和"android"换成自己的tag和alias。

ok,关于Jpush推送的使用今天就讲到这里了,谢谢哎。

相关文章

  • 极光推送(二)——推送的使用

    前言 在极光推送(一)——配置中讲过了极光推送的配置,这节讲讲极光推送的使用参考文档极光官网 下面以我写的demo...

  • 【知识总结】(2)远程推送

    推送SDK:极光推送 后台点击推送: iOS 10 以下收到推送点击触发 iOS 10 以上触发: 极光推送中使用...

  • 极光推送进行远程推送

    借阅:极光推送进行远程推送 怎么使用极光推送进行远程推送 在极光官网注册极光推送创建一个应用在应用配置中导入两个证...

  • ios极光推送

    第一次使用极光推送,在这里把极光推送的步骤说一下,省的以后再次用到极光推送的时候,给忘了,其实,极光推送不难...

  • iOS推送通知(极光推送)

    写在前面 要实现推送功能先要有苹果的推送证书:如何创建苹果推送证书要使用极光推送的功能,需要先集成极光推送SDK ...

  • 极光推送

    极光推送视频地址,非常详细的极光推送视频 极光推送

  • 在 android Notification使用PendingI

    项目的推送是采用极光推送,使用的是极光推送自定义消息,自己弹出通知栏,当有多天消息推送的时候PendingI...

  • 推送-JPush(极光推送)的使用

    前言 推送服务可以说是所有 App 的标配,不论是哪种类型的 App,推送都从很大程度上决定了 App 的 打开率...

  • React-Native 消息推送

    在这里我们可以选择大厂的推送,优先使用极光推送,下一篇将介绍如何使用阿里推送。 使用说明 PS: 真没想到极光大厂...

  • iOS 极光推送

    一、理解 二、集成、使用 ios 极光推送的集成及注意事项

网友评论

    本文标题:极光推送(二)——推送的使用

    本文链接:https://www.haomeiwen.com/subject/ubdwbxtx.html