精华内容
下载资源
问答
  • APP有推送功能时,需要判断当前app在手机中是否开启了允许消息推送,否则即使添加了推送代码仍然收不到通知,所以需要要么跳转至设置界面设置,要么自定义消息通知。 效果图 方法一:跳转到应用程序设置界面...

    前言

    当APP有推送功能时,需要判断当前app在手机中是否开启了允许消息推送,否则即使添加了推送代码仍然收不到通知,所以需要要么跳转至设置界面设置,要么自定义消息通知。

    效果图

     

    方法一:跳转到应用程序设置界面

    1、将NotificationSetUtil.java类复制到项目中

    package com.php.project.notificationsetutildemo.utils;
    
    import android.app.AppOpsManager;
    import android.content.Context;
    import android.content.Intent;
    import android.content.pm.ApplicationInfo;
    import android.net.Uri;
    import android.os.Build;
    import android.support.annotation.RequiresApi;
    import android.support.v4.app.NotificationManagerCompat;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    /**
     * Created by peihp
     * Used 判断是否开启消息通知,没有开启的话跳转到手机系统设置界面
     */
    public class NotificationSetUtil {
    
        //判断是否需要打开设置界面
        @RequiresApi(api = Build.VERSION_CODES.KITKAT)
        public static void OpenNotificationSetting(Context context, OnNextLitener mOnNextLitener) {
            if (!isNotificationEnabled(context)) {
                gotoSet(context);
            } else {
                if (mOnNextLitener != null) {
                    mOnNextLitener.onNext();
                }
            }
        }
    
        //判断该app是否打开了通知
        /**
         * 可以通过NotificationManagerCompat 中的 areNotificationsEnabled()来判断是否开启通知权限。NotificationManagerCompat 在 android.support.v4.app包中,是API 22.1.0 中加入的。而 areNotificationsEnabled()则是在 API 24.1.0之后加入的。
         * areNotificationsEnabled 只对 API 19 及以上版本有效,低于API 19 会一直返回true
         * */
        @RequiresApi(api = Build.VERSION_CODES.KITKAT)
        public static boolean isNotificationEnabled(Context context) {
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
                NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
                boolean areNotificationsEnabled = notificationManagerCompat.areNotificationsEnabled();
                return areNotificationsEnabled;
            }
    
            String CHECK_OP_NO_THROW = "checkOpNoThrow";
            String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";
    
            AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            ApplicationInfo appInfo = context.getApplicationInfo();
            String pkg = context.getApplicationContext().getPackageName();
            int uid = appInfo.uid;
    
            Class appOpsClass = null;
            /* Context.APP_OPS_MANAGER */
            try {
                appOpsClass = Class.forName(AppOpsManager.class.getName());
                Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
                        String.class);
                Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
    
                int value = (Integer) opPostNotificationValue.get(Integer.class);
                return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
    
        }
    
        //打开手机设置页面
        /**
         * 假设没有开启通知权限,点击之后就需要跳转到 APP的通知设置界面,对应的Action是:Settings.ACTION_APP_NOTIFICATION_SETTINGS, 这个Action是 API 26 后增加的
         * 如果在部分手机中无法精确的跳转到 APP对应的通知设置界面,那么我们就考虑直接跳转到 APP信息界面,对应的Action是:Settings.ACTION_APPLICATION_DETAILS_SETTINGS*/
        private static void gotoSet(Context context) {
    
            Intent intent = new Intent();
            if (Build.VERSION.SDK_INT >= 26) {
                // android 8.0引导
                intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
                intent.putExtra("android.provider.extra.APP_PACKAGE", context.getPackageName());
            } else if (Build.VERSION.SDK_INT >= 21) {
                // android 5.0-7.0
                intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
                intent.putExtra("app_package", context.getPackageName());
                intent.putExtra("app_uid", context.getApplicationInfo().uid);
            } else {
                // 其他
                intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
                intent.setData(Uri.fromParts("package", context.getPackageName(), null));
            }
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent);
        }
    
        /*=====================添加Listener回调================================*/
        public interface OnNextLitener {
            /**
             * 不需要设置通知的下一步
             */
            void onNext();
        }
    
        private OnNextLitener mOnNextLitener;
    
        public void setOnNextLitener(OnNextLitener mOnNextLitener) {
            this.mOnNextLitener = mOnNextLitener;
        }
    }
    

    使用方法:

    package com.php.project.notificationsetutildemo;
    
    import android.content.Context;
    import android.os.Build;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.widget.Toast;
    
    import com.php.project.notificationsetutildemo.utils.NotificationSetUtil;
    
    public class MainActivity extends AppCompatActivity {
    
        private Context mContext;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mContext = this;
    
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                //判断是否需要开启通知栏功能
                NotificationSetUtil.OpenNotificationSetting(mContext, new NotificationSetUtil.OnNextLitener() {
                    @Override
                    public void onNext() {
                        Toast.makeText(mContext,"已开启通知权限",Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }
    }

     

    方法二:自定义消息通知

    新建Toast.java

    package com.php.utils.ui.toast;
    
    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.app.AppOpsManager;
    import android.app.Application;
    import android.content.Context;
    import android.content.pm.ApplicationInfo;
    import android.os.Build;
    import android.support.annotation.RequiresApi;
    import android.support.v4.app.NotificationManagerCompat;
    import android.view.Gravity;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.widget.TextView;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    import com.php.notification.R;
    
    
    
    public class Toast {
        private static final String CHECK_OP_NO_THROW = "checkOpNoThrow";
        private static final String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";
        private static int checkNotification = 0;
        private static Object mToast;
        private static boolean flag = true;
        private Toast(Context context, String message, int duration) {
            if(context instanceof Application)
                checkNotification = 0;
            else
                checkNotification = isNotificationEnabled(context) ? 0 : 1;
            if (checkNotification == 1) {
                try {
                    mToast = EToast2.makeText(context, message, duration);
                } catch (Exception e) {
                    e.printStackTrace();
                    synchronized (CHECK_OP_NO_THROW) {
                        if(flag){
                            flag = false;
                            View toastRoot = LayoutInflater.from(context).inflate(R.layout.toast, null);
                            TextView tv = (TextView) toastRoot.findViewById(R.id.toast_notice);
                            tv.setText(message);
                            mToast = android.widget.Toast.makeText(context,"",duration);
                            ((android.widget.Toast) mToast).setView(toastRoot);
                            ((android.widget.Toast) mToast).setGravity(Gravity.BOTTOM, 0, 200);
                        }
                    }
                    ((TextView)((android.widget.Toast) mToast).getView().findViewById(R.id.toast_notice)).setText(message);
                }
            } else {
    //            mToast = android.widget.Toast.makeText(context, message, duration);
                synchronized (CHECK_OP_NO_THROW) {
                    if(flag){
                        flag = false;
                        View toastRoot = LayoutInflater.from(context).inflate(R.layout.toast, null);
                        TextView tv = (TextView) toastRoot.findViewById(R.id.toast_notice);
                        tv.setText(message);
                        mToast = android.widget.Toast.makeText(context,"",duration);
                        ((android.widget.Toast) mToast).setView(toastRoot);
                        ((android.widget.Toast) mToast).setGravity(Gravity.BOTTOM, 0, 200);
                    }
                }
                ((TextView)((android.widget.Toast) mToast).getView().findViewById(R.id.toast_notice)).setText(message);
            }
        }
        private Toast(Context context, int resId, int duration) {
            if(context instanceof Application)
                checkNotification = 0;
            else
                checkNotification = isNotificationEnabled(context) ? 0 : 1;
            if (checkNotification == 1 && context instanceof Activity) {
                mToast = EToast2.makeText(context, resId, duration);
            } else {
                mToast = android.widget.Toast.makeText(context, resId, duration);
            }
        }
    
        public static Toast makeText(Context context, String message, int duration) {
            return new Toast(context,message,duration);
        }
        public static Toast makeText(Context context, int resId, int duration) {
            return new Toast(context,resId,duration);
        }
    
        public void show() {
            if(mToast instanceof EToast2){
                ((EToast2) mToast).show();
            }else if(mToast instanceof android.widget.Toast){
                ((android.widget.Toast) mToast).show();
            }
        }
        public void cancel(){
            if(mToast instanceof EToast2){
                ((EToast2) mToast).cancel();
            }else if(mToast instanceof android.widget.Toast){
                ((android.widget.Toast) mToast).cancel();
            }
        }
        //判断该app是否打开了通知
        /**
         * 可以通过NotificationManagerCompat 中的 areNotificationsEnabled()来判断是否开启通知权限。NotificationManagerCompat 在 android.support.v4.app包中,是API 22.1.0 中加入的。而 areNotificationsEnabled()则是在 API 24.1.0之后加入的。
         * areNotificationsEnabled 只对 API 19 及以上版本有效,低于API 19 会一直返回true
         * */
        @RequiresApi(api = Build.VERSION_CODES.KITKAT)
        @SuppressLint("NewApi")
        private static boolean isNotificationEnabled(Context context){
            if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT){
                NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
                boolean areNotificationsEnabled = notificationManagerCompat.areNotificationsEnabled();
                return areNotificationsEnabled;
            }
            AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            ApplicationInfo appInfo = context.getApplicationInfo();
    
            String pkg = context.getApplicationContext().getPackageName();
    
            int uid = appInfo.uid;
    
            Class appOpsClass = null; //* Context.APP_OPS_MANAGER *//*
    
            try {
                appOpsClass = Class.forName(AppOpsManager.class.getName());
                Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE, String.class);
                Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
                int value = (int)opPostNotificationValue.get(Integer.class);
                return ((int)checkOpNoThrowMethod.invoke(mAppOps,value, uid, pkg) == AppOpsManager.MODE_ALLOWED);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    }

    新建EToast2.java文件:

    package com.php.utils.ui.toast;
    
    import android.content.Context;
    import android.graphics.PixelFormat;
    import android.os.Handler;
    import android.os.Message;
    import android.view.Gravity;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.WindowManager;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import java.util.Timer;
    import java.util.TimerTask;
    
    import com.php.notification.R;
    
    
    
    public class EToast2 {
    
        private WindowManager manger;
        private Long time = 2000L;
        private View contentView;
        private WindowManager.LayoutParams params;
        private static Timer timer;
        private Toast toast;
        private static Toast oldToast;
        private static Context context;
        public static final int LENGTH_SHORT = 0;
        public static final int LENGTH_LONG = 1;
        private static Handler handler;
        private CharSequence text;
    
        private View toastRoot;
    
        private EToast2(Context context, CharSequence text, int HIDE_DELAY){
            this.text = text;
    
            if(HIDE_DELAY == EToast2.LENGTH_SHORT)
                this.time = 2000L;
            else if(HIDE_DELAY == EToast2.LENGTH_LONG)
                this.time = 3500L;
    
            if(oldToast != null && EToast2.context != null && EToast2.context != context){
                EToast2.context = context;
                oldToast.cancel();
                oldToast = null;
            }
            if(oldToast == null){
                LayoutInflater inflate = (LayoutInflater)
                        context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                toastRoot = inflate.inflate(R.layout.toast, null);
                TextView tv = (TextView) toastRoot.findViewById(R.id.toast_notice);
                tv.setText(text);
                toast = Toast.makeText(context,"",HIDE_DELAY);
                toast.setView(toastRoot);
                toast.setGravity(Gravity.BOTTOM, 0, 200);
                contentView = toastRoot;
    
                params = new WindowManager.LayoutParams();
                params.height = WindowManager.LayoutParams.WRAP_CONTENT;
                params.width = WindowManager.LayoutParams.WRAP_CONTENT;
                params.format = PixelFormat.TRANSLUCENT;
                params.windowAnimations = context.getResources().getIdentifier("android:style/Animation.Toast", null, null);
                params.type = WindowManager.LayoutParams.TYPE_TOAST;
                params.setTitle("EToast2");
                params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
                params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
                params.y = 200;
            }
            if(handler == null){
                handler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        EToast2.this.cancel();
                    }
                };
            }
        }
    
        public static EToast2 makeText(Context context, String text, int HIDE_DELAY){
            EToast2 toast = new EToast2(context, text, HIDE_DELAY);
            return toast;
        }
    
        public static EToast2 makeText(Context context, int resId, int HIDE_DELAY) {
            return makeText(context,context.getText(resId).toString(),HIDE_DELAY);
        }
    
        public void show(){
            if(oldToast == null){
                oldToast = toast;
                Context context = contentView.getContext().getApplicationContext();
                if (context == null) {
                    context = contentView.getContext();
                }
                manger = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                manger.addView(contentView, params);
            }else{
                if(timer != null){
                    timer.cancel();
                }
                ((TextView)(oldToast).getView().findViewById(R.id.toast_notice)).setText(text);
            }
            timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    handler.sendEmptyMessage(1);
                }
            }, time);
        }
    
    
        public void cancel(){
            try {
                manger.removeView(contentView);
            } catch (IllegalArgumentException e) {
                //这边由于上下文被销毁后removeView可能会抛出IllegalArgumentException
                //暂时这么处理,因为EToast2是轻量级的,不想和Context上下文的生命周期绑定在一块儿
                //其实如果真的想这么做,可以参考博文2的第一种实现方式,添加一个空的fragment来做生命周期绑定
            }
            timer.cancel();
            oldToast.cancel();
            timer = null;
            toast = null;
            oldToast = null;
            contentView = null;
            handler = null;
        }
    }

    toast.xml文件:

    <?xml version="1.0" encoding="utf-8"?>  
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"  
        android:background="@drawable/toast_bg">  
        
        <TextView android:id="@+id/toast_notice"  
              android:layout_width="wrap_content"  
              android:layout_height="wrap_content"  
              android:layout_gravity="center_vertical"  
              android:gravity="center_vertical"  
              android:background="@color/transparent"
              android:textSize="18sp"
              android:padding="7dp"
              android:textColor="@color/white">  
        </TextView>  
    </LinearLayout>

    使用方法:

    /**
    	 * 提示窗
    	 *
    	 * @param context
    	 * @param mes
    	 */
    	public static void promptMes(Context context, String mes) {
    		com.php.utils.ui.toast.Toast.makeText(context, mes,
    				Toast.LENGTH_SHORT).show();
    	}

    欢迎大家关注本人公众号,一起学习进步,谢谢!

    公众号

    展开全文
  • 只是一般用户不会怎么注意,开发人员也不会很在意APP通知开关,因为GOOGLE还没有在通知上大做文章,进入到APP信息中,通知的开关也不是很起眼。但是8.0上针对通知部分(主要针对下拉通知)做了较大修改,其中牵连...

    Android在8.0中针对通知一块在功能上做了较大的改动。
    Toast被纳入到了通知管理,其实这在Android 4.4(API 19)以上就已经有特别的处理了。只是一般用户不会怎么注意,开发人员也不会很在意APP的通知开关,因为GOOGLE还没有在通知上大做文章,进入到APP信息中,通知的开关也不是很起眼。但是8.0上针对通知部分(主要针对下拉通知)做了较大修改,其中牵连到Toast,且APP信息也种通知开发内选项也丰富了。

    因此在APP设置中关闭通知开关后,Toast也会有不显示的情况——这个主要是针对国内不同的厂商定制,表现各有不同。

    标准API中可以通过

    android.support.v4.app.NotificationManagerCompat
    

    的方法

    /**
     * Returns whether notifications from the calling package are not blocked.
     */
    public boolean areNotificationsEnabled ()
    

    判断通知是否阻止。

    这个方法里的实现分成了3段,Android 4.4之前方法永远是true,在Android 4.4 ~ Android 7通过AppOpsManager来判断,在Android 7以上最终调用的android.app.NotificationManager进行的判断。

    至于为什么关闭通知开关后不显示Toast,需要跟踪到NotificationManagerService的源码查看enqueueToast方法,这里不具体说,主要说说如何解决。


    先看看各机型测试的情况

    机型 Android版本 areNotificationsEnabled SDK_INT Toast类型 是否显示
    OPPO R15 8.1.0 false 27 Customized Toast
    MI NOTE LTE 6.0.1 false 23 Android OS Toast
    NUBIA Z11 mini 5.1.1 false 22 Android OS Toast
    HW MATE 7 6.0 false 23 Android OS Toast

    小米显示TYPE_TOAST机型

    机型 Android版本 MIUI版本 areNotificationEnabled SDK_INT Toast显示 是否显示 备注
    MI MOTE LTE 6.0 MIUI 9.2稳定版 false 23 Custimized Toast 正常显示
    Redmi 3s 6.0 MIUI 7.3稳定版 false 23 Customized Toast 正常显示
    MI 4c 5.1.1 MIUI 8.2稳定版 false 22 Customized Toast 虽然显示,但是不正常,点击N次只显示一两次
    MI 5 8.0.0 MIUI 10 8.11.22开发版 false 26 Customized Toast 正常显示
    MI 8 9 MIUI 10.1稳定版 false 28 Customized Toast 正常显示
    Redmi Note4 6.0 MIUI 10.2稳定版 false 23 Android OS Toast 正常显示

    结果: MIUI 8以下Toast不显示,在MIUI论坛上可以看到这是因为MIUI不支持TYPE_TOAST,因此无法显示。

    ####小米,华为等关闭通知,系统Toast显示情况。

    机型 Android版本 MIUI版本 areNotificationEnabled SDK_INT Toast显示 是否显示 备注
    MI MOTE LTE 6.0 MIUI 9.2稳定版 false 23 Android OS Toast 正常显示
    MI 3 4.4.4 MIUI 9.2稳定版 false 19 Android OS Toast 正常显示
    MI 4C 5.1.1 MIUI 8.2稳定版 false 22 Android OS Toast -
    MI 5 8.0.0 MIUI 10 8.00.22开发版 false 26 Android OS Toast -
    MI 8 9 MIUI 10.1稳定版 false 28 Android OS Toast -
    Redmi 3s 6.0.1 MIUI 7.3稳定版 false 23 Android OS Toast -
    Redmi Note4 6.0 MIUI 10.2稳定版 false 23 Android OS Toast -
    华为荣耀6 4.4.2 - false 19 Android OS Toast -
    华为荣耀7 5.0.2 - false 21 Android OS Toast -
    华为Mate 10 9 - false 28 Android OS Toast -
    魅族 MX4 PRO 5.1.1 - false 22 Android OS Toast -
    魅族 MX6 7.1.1 - false 25 Android OS Toast -
    OPPO R15 8.1.0 - false 27 Android OS Toast -
    OPPO R11 PLUS 8.1.0 - false 27 Android OS Toast -
    OPPO R7 4.4.4 - false 19 Android OS Toast -
    OPPO R9 PLUS 5.1.1 - false 22 Android OS Toast -
    努比亚Z11 mini 5.1.1 - false 22 Android OS Toast -

    MIUI显示与其他品牌手机Toast显示有区别。
    OPPO R15也显示了Toast。

    看看自定义Toast

    根据系统Toast进行修改,由于系统Toast中调用了NotificationManagerService的接口,因此将涉及的部分先进行删除。第二,Toast在NotificationManagerService中是被进行排队处理,因此删除了NotificationManagerService的调用部分后,需要自己定义队列管理。

    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.content.res.Configuration;
    import android.content.res.Resources;
    import android.graphics.PixelFormat;
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    import android.support.annotation.NonNull;
    import android.support.annotation.Nullable;
    import android.support.annotation.StringRes;
    import android.util.Log;
    import android.view.Gravity;
    import android.view.View;
    import android.view.WindowManager;
    import android.widget.TextView;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.reflect.Field;
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.atomic.AtomicInteger;
    
    import static android.os.Build.VERSION.SDK_INT;
    
    /**
     * A toast is a view containing a quick little message for the user.  The toast class
     * helps you create and show those.
     *
     * <p>
     * When the view is shown to the user, appears as a floating view over the
     * application.  It will never receive focus.  The user will probably be in the
     * middle of typing something else.  The idea is to be as unobtrusive as
     * possible, while still showing the user the information you want them to see.
     * Two examples are the volume control, and the brief message saying that your
     * settings have been saved.
     * <p>
     * The easiest way to use this class is to call one of the static methods that constructs
     * everything you need and returns a new Toast object.
     *
     * <div class="special reference">
     * <h3>Developer Guides</h3>
     * <p>For information about creating Toast notifications, read the
     * <a href="{@docRoot}guide/topics/ui/notifiers/toasts.html">Toast Notifications</a> developer
     * guide.</p>
     * </div>
     */
    class Toast {
        private static final String TAG = "Toast";
        private static final boolean localLOGV = false;
    
    
        /**
         * 维护toast的队列
         */
        private static BlockingQueue<TN> mQueue = new LinkedBlockingQueue<TN>();
    
        /**
         * 原子操作:判断当前是否在读取{**@linkplain **#mQueue 队列}来显示toast
         */
        private static AtomicInteger mAtomicInteger = new AtomicInteger(0);
    
        @Retention(RetentionPolicy.SOURCE)
        @interface Duration {
        }
    
        /**
         * Show the view or text notification for a short period of time.  This time
         * could be user-definable.  This is the default.
         *
         * @see #setDuration
         */
        public static final int LENGTH_SHORT = 3000;
    
        /**
         * Show the view or text notification for a long period of time.  This time
         * could be user-definable.
         *
         * @see #setDuration
         */
        public static final int LENGTH_LONG = 5000;
    
        private final Context mContext;
        private final TN mTN;
        private long mDuration;
        private View mNextView;
    
        private final static Handler mHandler = new Handler();
    
        private static Runnable mActive = new Runnable() {
            @Override
            public void run() {
                activeQueue();
            }
        };
    
        private static void activeQueue() {
            final TN tn = mQueue.peek();
            if (tn == null) {
                mAtomicInteger.decrementAndGet();
                return;
            }
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    tn.show();
                }
            });
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (tn.mNextView != null && tn.mNextView.getParent() != null) {
                        mQueue.poll();
                    }
                    tn.hide();
                }
            }, tn.mDuration);
            mHandler.postDelayed(mActive, tn.mDuration);
        }
    
        /**
         * Construct an empty Toast object.  You must call {@link #setView} before you
         * can call {@link #show}.
         *
         * @param context The context to use.  Usually your {@link android.app.Application}
         *                or {@link android.app.Activity} object.
         */
        public Toast(Context context) {
            this(context, null);
        }
    
        /**
         * Constructs an empty Toast object.  If looper is null, Looper.myLooper() is used.
         */
        public Toast(@NonNull Context context, @Nullable Looper looper) {
            mContext = context;
            mTN = new TN(context.getPackageName(), looper);
            mTN.mY = dip2px(context, 26);
            mTN.mGravity = 0x00000051;
        }
    
    	public int dip2px(Context context,float dpValue) {
    	    final float scale = context.getResources().getDisplayMetrics().density;
            return (int) (dpValue * scale +0.5f);
        }
    
        /**
         * Show the view for the specified duration.
         */
        public void show() {
            if (mNextView == null) {
                throw new RuntimeException("setView must have been called");
            }
    
            TN tn = mTN;
            tn.mNextView = mNextView;
    
            mQueue.offer(tn);
            if (mAtomicInteger.get() == 0) {
                mAtomicInteger.incrementAndGet();
                mHandler.post(mActive);
            }
        }
    
        /**
         * Close the view if it's showing, or don't show it if it isn't showing yet.
         * You do not normally have to call this.  Normally view will disappear on its own
         * after the appropriate duration.
         */
        public void cancel() {
            if (0 == mAtomicInteger.get() && mQueue.isEmpty()) {
                return;
            }
    
            final TN tn = mQueue.peek();
            if (mTN == tn) {
                mHandler.removeCallbacks(mActive);
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (tn != null) {
                            tn.cancel();
                        }
                    }
                });
                mHandler.post(mActive);
            }
        }
    
        /**
         * Set the view to show.
         *
         * @see #getView
         */
        public void setView(View view) {
            mNextView = view;
        }
    
        /**
         * Return the view.
         *
         * @see #setView
         */
        public View getView() {
            return mNextView;
        }
    
        /**
         * Set how long to show the view for.
         *
         * @see #LENGTH_SHORT
         * @see #LENGTH_LONG
         */
        public void setDuration(@Duration int duration) {
            mDuration = duration;
            mTN.mDuration = duration;
        }
    
        /**
         * Return the duration.
         *
         * @see #setDuration
         */
        @Duration
        public long getDuration() {
            return mDuration;
        }
    
        /**
         * Set the margins of the view.
         *
         * @param horizontalMargin The horizontal margin, in percentage of the
         *                         container width, between the container's edges and the
         *                         notification
         * @param verticalMargin   The vertical margin, in percentage of the
         *                         container height, between the container's edges and the
         *                         notification
         */
        public void setMargin(float horizontalMargin, float verticalMargin) {
            mTN.mHorizontalMargin = horizontalMargin;
            mTN.mVerticalMargin = verticalMargin;
        }
    
        /**
         * Return the horizontal margin.
         */
        public float getHorizontalMargin() {
            return mTN.mHorizontalMargin;
        }
    
        /**
         * Return the vertical margin.
         */
        public float getVerticalMargin() {
            return mTN.mVerticalMargin;
        }
    
        /**
         * Set the location at which the notification should appear on the screen.
         *
         * @see android.view.Gravity
         * @see #getGravity
         */
        public void setGravity(int gravity, int xOffset, int yOffset) {
            mTN.mGravity = gravity;
            mTN.mX = xOffset;
            mTN.mY = yOffset;
        }
    
        /**
         * Get the location at which the notification should appear on the screen.
         *
         * @see android.view.Gravity
         * @see #getGravity
         */
        public int getGravity() {
            return mTN.mGravity;
        }
    
        /**
         * Return the X offset in pixels to apply to the gravity's location.
         */
        public int getXOffset() {
            return mTN.mX;
        }
    
        /**
         * Return the Y offset in pixels to apply to the gravity's location.
         */
        public int getYOffset() {
            return mTN.mY;
        }
    
        /**
         * Gets the LayoutParams for the Toast window.
         *
         * @hide
         */
        public WindowManager.LayoutParams getWindowParams() {
            return mTN.mParams;
        }
    
        /**
         * Make a standard toast that just contains a text view.
         *
         * @param context  The context to use.  Usually your {@link android.app.Application}
         *                 or {@link android.app.Activity} object.
         * @param text     The text to show.  Can be formatted text.
         * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
         *                 {@link #LENGTH_LONG}
         */
        public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
            return makeText(context, null, text, duration);
        }
    
        /**
         * Make a standard toast to display using the specified looper.
         * If looper is null, Looper.myLooper() is used.
         *
         * @hide
         */
        @SuppressLint("ShowToast")
        public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
                                     @NonNull CharSequence text, @Duration int duration) {
            Toast result = new Toast(context, looper);
    
            View v = android.widget.Toast.makeText(context, text, duration).getView();
            TextView tv = v.findViewById(android.R.id.message);
            tv.setText(text);
    
            result.mNextView = v;
            result.mDuration = duration;
    
            return result;
        }
    
        /**
         * Make a standard toast that just contains a text view with the text from a resource.
         *
         * @param context  The context to use.  Usually your {@link android.app.Application}
         *                 or {@link android.app.Activity} object.
         * @param resId    The resource id of the string resource to use.  Can be formatted text.
         * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
         *                 {@link #LENGTH_LONG}
         * @throws Resources.NotFoundException if the resource can't be found.
         */
        public static Toast makeText(Context context, @StringRes int resId, @Duration int duration)
                throws Resources.NotFoundException {
            return makeText(context, context.getResources().getText(resId), duration);
        }
    
        /**
         * Update the text in a Toast that was previously created using one of the makeText() methods.
         *
         * @param resId The new text for the Toast.
         */
        public void setText(@StringRes int resId) {
            setText(mContext.getText(resId));
        }
    
        /**
         * Update the text in a Toast that was previously created using one of the makeText() methods.
         *
         * @param s The new text for the Toast.
         */
        public void setText(CharSequence s) {
            if (mNextView == null) {
                throw new RuntimeException("This Toast was not created with Toast.makeText()");
            }
            TextView tv = mNextView.findViewById(android.R.id.message);
            if (tv == null) {
                throw new RuntimeException("This Toast was not created with Toast.makeText()");
            }
            tv.setText(s);
        }
    
    
        private static class TN {
            private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
    
            private static final int SHOW = 0;
            private static final int HIDE = 1;
            private static final int CANCEL = 2;
            final Handler mHandler;
    
            int mGravity;
            int mX;
            int mY;
            float mHorizontalMargin;
            float mVerticalMargin;
    
            View mView;
            View mNextView;
            long mDuration = SHORT_DURATION_TIMEOUT;
    
            WindowManager mWM;
    
            String mPackageName;
    
            static final long SHORT_DURATION_TIMEOUT = 2000;
            static final long LONG_DURATION_TIMEOUT = 5000;
    
            TN(String packageName, @Nullable Looper looper) {
                // XXX This should be changed to use a Dialog, with a Theme.Toast
                // defined that sets up the layout params appropriately.
                final WindowManager.LayoutParams params = mParams;
                params.height = WindowManager.LayoutParams.WRAP_CONTENT;
                params.width = WindowManager.LayoutParams.WRAP_CONTENT;
                params.format = PixelFormat.TRANSLUCENT;
                params.windowAnimations = android.R.style.Animation_Toast;
                params.type = WindowManager.LayoutParams.TYPE_TOAST;
                params.setTitle("Toast");
                params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
    
                mPackageName = packageName;
    
                if (looper == null) {
                    // Use Looper.myLooper() if looper is not specified.
                    looper = Looper.myLooper();
                    if (looper == null) {
                        throw new RuntimeException(
                                "Can't toast on a thread that has not called Looper.prepare()");
                    }
                }
                mHandler = new Handler(looper, null) {
                    @Override
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case SHOW: {
                                handleShow();
                                break;
                            }
                            case HIDE: {
                                handleHide();
                                // Don't do this in handleHide() because it is also invoked by
                                // handleShow()
                                mNextView = null;
                                break;
                            }
                            case CANCEL: {
                                handleHide();
                                // Don't do this in handleHide() because it is also invoked by
                                // handleShow()
                                mNextView = null;
                                break;
                            }
                        }
                    }
                };
            }
    
            /**
             * schedule handleShow into the right thread
             */
            public void show() {
                if (localLOGV) Log.v(TAG, "SHOW: " + this);
                mHandler.obtainMessage(SHOW).sendToTarget();
            }
    
            /**
             * schedule handleHide into the right thread
             */
            public void hide() {
                if (localLOGV) Log.v(TAG, "HIDE: " + this);
                mHandler.obtainMessage(HIDE).sendToTarget();
            }
    
            public void cancel() {
                if (localLOGV) Log.v(TAG, "CANCEL: " + this);
                mHandler.obtainMessage(CANCEL).sendToTarget();
            }
    
            public void handleShow() {
                if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                        + " mNextView=" + mNextView);
                // If a cancel/hide is pending - no need to show - at this point
                // the window token is already invalid and no need to do any work.
                if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
                    return;
                }
                if (mView != mNextView) {
                    // remove the old view if necessary
                    handleHide();
                    mView = mNextView;
                    Context context = mView.getContext().getApplicationContext();
                    String packageName = mPackageName;
                    if (context == null) {
                        context = mView.getContext();
                    }
                    mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
                    // We can resolve the Gravity here by using the Locale for getting
                    // the layout direction
                    final Configuration config = mView.getContext().getResources().getConfiguration();
                    int gravity = mGravity;
                    if (SDK_INT > 16) {
                        gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
                    }
    
                    mParams.gravity = gravity;
                    if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                        mParams.horizontalWeight = 1.0f;
                    }
                    if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                        mParams.verticalWeight = 1.0f;
                    }
                    mParams.x = mX;
                    mParams.y = mY;
                    mParams.verticalMargin = mVerticalMargin;
                    mParams.horizontalMargin = mHorizontalMargin;
                    mParams.packageName = packageName;
    
                    if (SDK_INT > 23) {
                        try {
                            Class clazz = Class.forName("android.view.WindowManager");
                            Field field = clazz.getDeclaredField("hideTimeoutMilliseconds");
                            field.setAccessible(true);
                            field.setLong(mParams, mDuration ==
                                    Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT);
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        } catch (NoSuchFieldException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
    
                    if (mView.getParent() != null) {
                        if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                        mWM.removeView(mView);
                    }
                    if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                    // Since the notification manager service cancels the token right
                    // after it notifies us to cancel the toast there is an inherent
                    // race and we may attempt to add a window after the token has been
                    // invalidated. Let us hedge against that.
                    try {
                        mWM.addView(mView, mParams);
                    } catch (WindowManager.BadTokenException e) {
                        /* ignore */
                    }
                }
            }
    
            public void handleHide() {
                if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
                if (mView != null) {
                    // note: checking parent() just to make sure the view has
                    // been added...  i have seen cases where we get here when
                    // the view isn't yet added, so let's try not to crash.
                    if (mView.getParent() != null) {
                        if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                        mWM.removeViewImmediate(mView);
                    }
    
                    mView = null;
                }
            }
        }
    }
    

    这个类完全可用,需要使用可以拷贝直接使用

    针对MIUI 8需要在WindowManager.addView前后添加标记设置。

                    //...
                    try {
                        final int miuiVersion = MobileManufacturer.miuiVersion();
                        if (miuiVersion <= 8) {
                            MobileManufacturer.setMIUIInternational(true);
                        }
                        mWM.addView(mView, mParams);
                        if (miuiVersion <= 8) {
                            MobileManufacturer.setMIUIInternational(false);
                        }
                    } catch (WindowManager.BadTokenException e) {
                        /* ignore */
                    }
                   // ...
    

    针对不同厂商需要可能需要不同适配,因此重新定义一个判断厂商工具类。

    // MobileManufacturer.java
    import android.annotation.SuppressLint;
    import android.os.Build;
    import android.text.TextUtils;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    final class MobileManufacturer {
        static boolean isMIUI() {
            return TextUtils.equals(Build.MANUFACTURER, "Xiaomi");
        }
    
        static int miuiVersion() {
            return getMIUIVersion();
        }
    
        static void setMIUIInternational(boolean flag) {
            try {
                final Class BuildForMi = Class.forName("miui.os.Build");
                final Field isInternational = BuildForMi.getDeclaredField("IS_INTERNATIONAL_BUILD");
                isInternational.setAccessible(true);
                isInternational.setBoolean(null, flag);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        private static int getMIUIVersion() {
            try {
                @SuppressLint("PrivateApi")
                Class<?> sysClass = Class.forName("android.os.SystemProperties");
                Method getStringMethod = sysClass.getDeclaredMethod("get", String.class);
                final String version = (String) getStringMethod.invoke(sysClass, "ro.miui.ui.version.name");
                if (!TextUtils.isEmpty(version)) {
                    return Integer.valueOf(version.substring(1));
    
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 6;
        }
    }
    

    这里主要采用了MIUI做实验对比,若不同厂商定制的手机不同而导致不显示Toast,均可采用以上自定义的Toast进行显示。

    查看厂商ROM

    展开全文
  • 最近接了激光推送,完成后测试这边提出了这么一个问题:app退出后,通知还在通知栏,这个时候点击通知后,手机会从桌面跳转到对应的订单页面,接着app直接闪退.原因分析:因为app中跳转掉订单页面,获取订单信息需要用户信息...

    最近接了激光推送,完成后测试这边提出了这么一个问题:

    app退出后,通知还在通知栏,这个时候点击通知后,手机会从桌面跳转到对应的订单页面,接着app直接闪退.

    原因分析:

    因为app中跳转掉订单页面,获取订单信息需要用户信息,但是这个时候App是直接进入订单页面的,没有经过欢迎页面的初始化,因此是拿不到用户信息的,自然会闪退掉!

    解决方案:

    1. 在app退出的时候制造一个假象,只是跳转到桌面,并没有真正的关闭.

    解释下:很多app做的都是双击两次返回键退出,但是我们可以做一个假象,就是直接跳转到桌面,像京东.淘宝就是这样做的.你会发现你进入桌面后,再次进入淘宝或者京东的时候,并没有进入启动页,而是直接进入了主页
    这里写图片描述

     @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                if (event.getAction() == KeyEvent.ACTION_DOWN) {
                    long secondTime = System.currentTimeMillis();
                    if (secondTime - firstTime > 800) {// 如果两次按键时间间隔大于800毫秒,则不退出
                        CustomToast.createToast().showToast(MainActivity.this,
                                "再按一次  退出程序");
                        firstTime = secondTime;// 更新firstTime
                        return true;
                    } else {
    //                    BaseApplication.getInstance().exit();//真正的退出
                        moveTaskToBack(false);
                    }
                }
            }
            return super.dispatchKeyEvent(event);
        }

    说明:这种方法有治标不治本,如果从任务栏直接把app退出或者红米等一些低配的机器虽然你自己没有直接关闭,但是稍微打开几个app,我的app就可能被回收了,这个时候我们再点击通知进入app,仍然会因为缺少用户信息闪退的.

    2.设置两个广播接受者,一个用来接收推送和展示推送,另一个用来处理通知点击后的操作

    a.极光推送和个推都是通过BroadcastReceiver来接收推送的,那么我们在这个类中设置显示通知,点击通知后的延时意图设置为发一个广播而不是一个开启一个页面.在另外一个BroadcastReceiver中,接收到广播后进行一些判断,打开页面.
    b.判断:首先我们判断这个app整个进程是不是还存活着,
    如果存活,我们就直接打开我们要打开的订单页面,并且通过Intent将相关的数据传递过去
    如果不存活,那就先打开启动页(WelcomeActivity),并将相关的数据通过Intent传递过去,进入启动页再将主界面(MainActivity)后,再进入订单页面

    下边贴出代码(我们发一条广播代替推送):

    1.NotificationShowReceiver

    /**
     * 接收和展示推送
     * Created by Haipeng on 2017/5/11.
     */
    public class NotificationShowReceiver extends BroadcastReceiver {
        private static final String TAG = "NotShowReceiver";
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "ShowNotificationReceiver onReceive");
    
            String message = intent.getStringExtra("message");
            Log.e(TAG, "[JPushReceiver] onReceive - " + intent.getAction() + ", extras: " + message);
    
            showNotification(context, message);
        }
        private String id;
        private Notification myNotify;
        private NotificationManager manager;
    
    
        //    send msg to MainActivity
        private void showNotification(Context context, String message) {
    
    
    //         message = "{\"type\":\"Test\",\"title\":\"测试\",\"content\":\"您有新的订单,点击查看\",\"id\":\"888888\",\"url\":\"\"}";
    
            //设置点击通知栏的动作为启动另外一个广播
            Intent broadcastIntent = new Intent(context, NotificationClickReceiver.class);
            broadcastIntent.putExtra("message",message);
            broadcastIntent.setAction("com.jike.testpushnotification.push.notificationclickreceiver");
            PendingIntent pendingIntent = PendingIntent.
                    getBroadcast(context, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
            if (!TextUtils.isEmpty(message)) {
                Log.e(TAG, message);
                PushData pushData = new Gson().fromJson(message, PushData.class);
                id = pushData.getId();
                manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
                myNotify = new Notification();
                myNotify.icon = R.mipmap.ic_launcher;
                myNotify.tickerText = pushData.getContent();
                myNotify.when = System.currentTimeMillis();
                myNotify.defaults = Notification.DEFAULT_SOUND;
                myNotify.defaults |= Notification.DEFAULT_VIBRATE;
                //myNotify.flags = Notification.FLAG_NO_CLEAR;// 不能够自动清除
                myNotify.flags = Notification.FLAG_AUTO_CANCEL;
                RemoteViews rv = new RemoteViews(context.getPackageName(),
                        R.layout.my_notification);
                rv.setTextViewText(R.id.text_content, pushData.getContent());
                rv.setTextViewText(R.id.text_content1, pushData.getTitle());
                myNotify.contentView = rv;
                myNotify.contentIntent = pendingIntent;
                manager.notify(10123, myNotify);
            }}
    }

    2.NotificationClickReceiver

    /**
     * 接收点击通知的事件和设置点击后做什么事情
     * Created by Haipeng on 2017/5/11.
     */
    public class NotificationClickReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            String message = intent.getStringExtra("message");
    
            //判断app进程是否存活
            if (SystemUtil.isAppAlive(context, "com.jike.testpushnotification")) {
                //如果存活的话,就直接启动DetailActivity,但要考虑一种情况,就是app的进程虽然仍然在
                //但Task栈已经空了,比如用户点击Back键退出应用,但进程还没有被系统回收,如果直接启动
                //DetailActivity,再按Back键就不会返回MainActivity了。所以在启动
                //DetailActivity前,要先启动MainActivity。
                Log.i("NotClickReceiver", "the app process is alive");
    
                Intent desIntnet = new Intent(context, OrderActivity.class);
    
                if (!TextUtils.isEmpty(message)) {
                    desIntnet.putExtra("message", message);
                }
                desIntnet.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                if (SystemUtil.isActivityRunning(context, MainActivity.class)) {
                    context.startActivity(desIntnet);
                } else {
                    Intent mainIntent = new Intent(context, MainActivity.class);
                    //将MainAtivity的launchMode设置成SingleTask, 或者在下面flag中加上Intent.FLAG_CLEAR_TOP,
                    //如果Task栈中有MainActivity的实例,就会把它移到栈顶,把在它之上的Activity都清理出栈,
                    //如果Task栈不存在MainActivity实例,则在栈顶创建
                    mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    Intent[] intents = {mainIntent, desIntnet};
    
                    context.startActivities(intents);
                }
    
            } else {
                //如果app进程已经被杀死,先重新启动app,将DetailActivity的启动参数传入Intent中,参数经过
                //SplashActivity传入MainActivity,此时app的初始化已经完成,在MainActivity中就可以根据传入             //参数跳转到DetailActivity中去了
                Log.i("NotClickReceiver", "the app process is dead");
                Intent launchIntent = context.getPackageManager().
                        getLaunchIntentForPackage("com.jike.testpushnotification");
                launchIntent.setFlags(
                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
                launchIntent.putExtra("message", message);
    
    //            NotificationClickReceiver.
    //            launchIntent.putExtra(Constants.EXTRA_BUNDLE, args);
                context.startActivity(launchIntent);
            }
        }
    }
    

    3.WelcomeActivity

    /**
     * desc:启动页
     * Edited by Haipeng.
     */
    public class WelcomeActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_welcome);
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SystemClock.sleep(2000);
                    goMainPage();
                }
            }).start();
        }
    
        private void goMainPage() {
            BaseApplication.isLogin=true;
            String message = getIntent().getStringExtra("message");
            Intent intent = new Intent(this, MainActivity.class);
            if (!TextUtils.isEmpty(message)) {//将相关数据传过去
                intent.putExtra("message", message);
            }
            startActivity(intent);
            finish();
        }
    }

    2.MainActivity

    /**
     * desc: 主界面  我们发一条广播代替推送
     * Edited by Haipeng.
     */ 
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            init();
        }
    
        private void init() {
            String message = getIntent().getStringExtra("message");
            if (!TextUtils.isEmpty(message)) {
                Intent intent = new Intent(this, OrderActivity.class);
                intent.putExtra("message", message);
                startActivity(intent);
            }
            findViewById(R.id.bt_push).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent=new Intent();
                    intent.setAction("com.jike.testpushnotification.push.notificationshowreceiver");
                    String message = "{\"type\":\"Test\",\"title\":\"测试\",\"content\":\"您有新的订单,点击查看\",\"id\":\"888888\",\"url\":\"\"}";
                    intent.putExtra("message",message);
                    sendBroadcast(intent);
                }
            });
        }
    }

    3.OrderActivity

    /**
     * 测试订单页面   如果没有登录进入这个页面就会抛出一个异常
     */
    public class OrderActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_order);
              if (!BaseApplication.isLogin){
                       throw new IllegalStateException("未登录");
                }
            TextView tv_push_data= (TextView) findViewById(R.id.tv_push_data);
            String message = getIntent().getStringExtra("message");
            if (!TextUtils.isEmpty(message)) {
                tv_push_data.setText(message);
            }
        }
    }

    看下效果图:
    首先是app没有退出的时候:
    这里写图片描述
    接着是app退出后,点击通知:
    这里写图片描述

    点击这里下载源码

    展开全文
  • 获取App通知权限是否开启的方法

    万次阅读 2017-05-13 23:34:23
    我们有时候需要获知用户是否允许了App通知栏显示通知,设置入口一般见于AppInfo即应用详情页面。

    我们有时候需要获知用户是否允许了App在通知栏显示通知,设置入口一般见于AppInfo即应用详情页面。
    方法来自于官方的support包,必须先更新build.gradle中的support包版本到24以上:

    compile 'com.android.support:appcompat-v7:24.0.0'
    compile 'com.android.support:support-annotations:24.0.0'
    compile 'com.android.support:support-v4:24.0.0'
    

    鄙人的项目里只用到了这3个support包,如果大家有其余的,都要改成24以上。
    然后再在代码中通过NotificationManagerCompat包获取是否打开了通知显示权限:

    NotificationManagerCompat manager = NotificationManagerCompat.from(App.getInstance().getContext());
    boolean isOpened = manager.areNotificationsEnabled();
    

    要注意的是,areNotificationsEnabled方法的有效性官方只最低支持到API 19,低于19的仍可调用此方法不过只会返回true,即默认为用户已经开启了通知。
    查了各种资料,目前暂时没有办法获取19以下的系统是否开启了某个App的通知显示权限。

    最后,附上打开应用详情页面的代码:

    // 根据isOpened结果,判断是否需要提醒用户跳转AppInfo页面,去打开App通知权限
    Intent intent = new Intent();
    intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    Uri uri = Uri.fromParts("package", App.getInstance().getApplication().getPackageName(), null);
    intent.setData(uri);
    startActivity(intent);
    
    展开全文
  • NSWindow 通知/关闭/退出app

    千次阅读 2017-11-27 11:01:16
    窗口通知 NSWindowDidBecomeKeyNotification: 窗口成为keyWindow NSWindowDidBecomeMainNotification:窗口成为mainWindow NSWindowDidMoveNotification: 窗口移动 NSWindowDidResignKeyNotification: 窗口不再是...
  • 移动app通知和消息展示测试项详解

    千次阅读 2017-05-10 18:12:45
    使用App过程中,最烦的莫过于接收到各种已安装的App发送的各类通知,而这些通知的行为和触发的方式又各有不同,让用户感到无所适从。   测试App安装时是否明确申明在用户使用App时需要用到的权限,否则App在提交...
  • 点击通知栏里的通知启动App

    千次阅读 2016-12-14 10:26:09
    点击通知栏里的通知启动App(在App被杀死的情况下) - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    self.window = [[UIWindow ...
  • 1、全新、升级安装后判断系统是否屏蔽App通知。 2、如果通知被屏蔽,弹出提醒开启通知的弹窗。 3、点击弹窗中“立即开启”按钮,跳转到系统对应的此App通知设置界面。 解决方法: 1、判断通知App通知功能...
  • 一般来说APP都会有更新功能,这里提供一种简单的实现方式(在通知栏后台更新)。
  • ios APP前台时显示消息通知

    千次阅读 2018-06-05 17:00:07
    最近整理了项目使用的推送资料,包括...在网上查了很多资料,都说只能app在后台时才能调用系统的通知弹框,所以以前用的是自定义的弹框,最近发现在app活跃时,可以调用本地通知达到app前台活跃时显示系统的弹框。...
  • 用手机UC浏览器的时候,用settimeout方法。如果有该app就用app打开如果没有就提示下载。 可是听说UC浏览器不支持window.onblur方法,现在是有app也提示,没有app也提示。 请问有什么办法解决?
  • app通知栏下载更新进度条卡顿处理

    千次阅读 2016-01-08 14:22:55
    问题:发现更新app时候通知栏的处理下拉上滑过程会越来越卡,最终直接导致手机卡死。 解决: 第一种: 更新的时候不能把确实的数值传递给进度条更新,要先转为100份,然后目前占了几份再去更新,不然频繁调用通知...
  • iOS swift当app从后台切换到前台,或者锁屏后开启,app收到通知。根据单一职责原则,所以在需要该方法的ViewController中加入监听就好。 代码实现 for swift 5 class ViewController: UIViewController { override...
  • Android中 RemoveView的使用和举例 —— 最全面的APP通知栏适配 [下载完整演示代码,下载完整演示代码,下载完整演示代码](https://github.com/benchegnzhou/AndroidDevelomentArtDemo) 目录 1. ...
  • 问题描述: 在Android 7.0以上的版本, 同一个App的消息通知超过3条时会合并到一起, 经过我对部分机型的测试, 目前只发现小米手机在点击合并到一起的消息时会将消息列表展开, 其他品牌都是启动App, 不能跳转到我们需要...
  • Java支付宝APP支付-验证异步通知消息

    千次阅读 2018-09-04 10:41:40
    这里先讲一下啥叫支付宝异步通知:对于App支付产生的交易,支付宝会根据原始支付API中传入的异步通知地址notify_url,通过POST请求的形式将支付结果作为参数通知到商户系统。 通知参数详细见官方API:...
  • Android 实现通知通知APP版本更新

    千次阅读 2017-05-13 21:35:44
    需要一进去APP就去请求网络进行APP版本信息比配     /** * 在程序进入的时候我们就去读取网络上APK的信息 * Created by WL-鬼 on 2017/5/13. */ public class MyApplication extends Application {...
  • APP版本更新通知流程图

    千次阅读 2018-01-30 17:58:00
    一、APP版本更新通知流程图如下: 二、测试注意点:  1、Android更新直接下载APK,IOS引导至APP Store更新页面;  强制更新------只有“立即更新”  1、一般“强制更新”的机制不常用,除非涉及到APP的紧急...
  • 自己做的app 当收到一条通知后,将app至于后台然后杀掉app的进程,发现通知跟着一起消失了,app 被杀,通知也跟着消失,有神码方法跟微信一样通知不消失
  • 这个链接讲的挺详细的,可以参考:http://blog.csdn.net/zhonggaorong/article/details/51374275
  • 最近好多app都已经满足了沉浸式通知栏,所谓沉浸式通知栏:就是把用来导航的各种界面操作空间隐藏在以程序内容为主的情景中,通过相对“隐形”的界面来达到把用户可视范围最大化地用到内容本身上。 而最新安卓4.4...
  • 1.最近在做app内部的推送开关功能。 这样顾客可以自己定义推送给他的内容,屏蔽不想要的一些烦人推送。  在开发过程中, 如果顾客打开推送开关的时候,... 很简单的一个问题,原来是顾客没有在系统通知处打开app的通

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 241,482
精华内容 96,592
关键字:

如何关闭app的通知