精华内容
下载资源
问答
  • 拿了个安卓手机,设置-通知-关闭允许使用通知圆点,微信右上角不再有小圆点了,但是电话短信仍然有,这是为什么,是要app内部实现什么权限吗
  • Selenium关闭chrome通知弹窗

    千次阅读 2021-02-20 16:22:50
    当用selenium中的get(url)方法打开微博,Chrome弹出通知窗口,导致Selenium语句失效 此篇记录一下学到的解决方法 options = webdriver.ChromeOptions() prefs = { 'profile.default_content_setting_values': { ...

    当用selenium中的get(url)方法打开微博,Chrome弹出通知窗口,导致Selenium语句失效

    在这里插入图片描述
    此篇记录一下学到的解决方法

    options = webdriver.ChromeOptions()
    prefs = {
        'profile.default_content_setting_values':
            {
                'notifications': 2
            }
    }
    options.add_experimental_option('prefs', prefs)
    wb= webdriver.Chrome(chrome_options=options)
    wb.get(url)
    wb.implicitly_wait(5)
    

    亲测有效!

    展开全文
  • 前言既然能看到这篇博文,就说明你一定看过这个【Android】当关闭通知消息权限后无法显示系统Toast的解决方案 。然后,我很开心的告诉你,兄弟,可能你们的心病这次就解决了~当然,没看过上一篇博文的还是建议看下,...

    ##前言
    既然能看到这篇博文,就说明你一定看过这个【Android】当关闭通知消息权限后无法显示系统Toast的解决方案 。然后,我很开心的告诉你,兄弟,可能你们的心病这次就解决了~当然,没看过上一篇博文的还是建议看下,看看以前的解决思路,看看之前的实现方式和一些遇到的问题,说不定对你也很有收获呢。
    ##怎么使用
    github地址:https://github.com/Blincheng/EToast2
    如果大家有任何什么问题,欢迎大家加入EToast交流群和我一起讨论和改进。
    QQ群:EToast交流群
    群号码:547279762
    这里写图片描述
    更新日志:(具体请移步到 github查看)

    v2.2.1(2019年5月28日10:24:41)

    在2.2.0的基础上优化初始化方式,无需手动初始化,在集成后直接使用EToastUtils.show(text)即可使用。
    技术说明: 主要实现方式参考了Lifecycle组件的初始化的方式,通过自定义ContentProvider去初始化一些库或者其他的内容。


    V2.2.0(2018年11月15日15:25:20)来了,米娜桑,我还没有忘记大家~

    希望本次修改,可以做到适配全部机型和系统(吧?),发现任何问题可以在Issues中留言或者直接进群联系群主。

    新增EToastUtils,需要在Application中注册EToastUtils,调用方法为EToastUtils.show(text);
    适配到Android 9,对于Android M以下的机型直接绕过悬浮窗权限弹出全局Toast
    优化对context使用Application的支持,前提是需要提前在Application中注册EToastUtils


    Step 1. Add the JitPack repository to your build file

    allprojects {
    	repositories {
    		...
    		maven { url 'https://jitpack.io' }
    	}
    }
    

    Step 2. Add the dependency

    dependencies {
    	compile 'com.github.Blincheng:EToast2:v2.1.0'
    }
    

    然后,就大大咧咧的用吧

    EToast 一个关闭系统消息通知后依然可以显示的Toast,此版本完全是独立于v1.x.x的版本,实现方式上也是完全的不同,尽量的参考系统Toast的源码去实现。
    和上代EToast相比,有以下的改动:

    1. Context不再依赖于Activity显示。
    2. 显示动画完全跟随着系统走,也就是说和系统的Toast动画效果完全一致
    3. 多条显示规则还是保留了V1.x的版本的规则,永远只显示一个Toast。
    4. 由于实现原理的更改,EToast不再会被Dialog、PopupWindow等弹窗布局覆盖

    由于在Android5.0以下无法获取是否打开系统通知权限,所以为了防止用户看不到Toast,最终的逻辑优化了一下:

    1. 当用户是5.0以下的机器时,永远只显示EToast
    2. 当用户是5.0以上的机器是,如果打开了通知权限,则显示系统Toast;反之则显示Etoast
    Toast.makeText(mActivity, text, EToast2.LENGTH_SHORT).show();
    

    需要注意的是,此Toast非彼Toast,应该使用“import com.mic.etoast2.Toast”,建议在BaseActivity中如下使用:

    public void showShortText(String text) {
    	Toast.makeText(mActivity, text, Toast.LENGTH_SHORT).show();
    }
    

    好了,没有兴趣想看看怎么实现的,只是来拿货的,你就可以走了/(ㄒoㄒ)/~~

    然后本文会有点长~请耐心往下看,哈哈哈。
    ##实现思路
    ###首先思考下这个版本要做什么事

    1. 首先,Context的使用限制,之前只能是Activity,这个有点忧伤,相当于直接依赖Activity,使用上的确有点不踏实哈~
    2. 然后,不能在顶层显示,会被键盘、dialog等遮挡,如果有这种场景的话,比较尴尬。
    3. 内存泄漏,由于引用了当前Activity的上下文
    4. 显示动画是不是应该跟着系统走,好让Toast和其他App一致。(反正目的就是说不管怎么样,一定让Toast显示呗)
    5. 貌似管理机制不是很好,能不能和Activity或者Fragment的生命周期保持一致呢?

    ###那么,怎么解决
    ####刚开始我也脑子中思考了许久,想想有什么好的办法可以解决以上问题呢?当然,结果肯定是有的。不过貌似也遇到了一些坎坷。刚好呢,我之前抽空看了一下Glide的源码,因为类似的图片框架加载就和Activity、Fragment等的声明周期密切相关~具体Glide的源码我就不展开了,简单说下:

    Glide.with(this).load(url).into(imageView);
    

    上面就是Glide的最简单用法,whit(this)在一开始就把当前环境丢进去了哈然后那么久开始仿照直接写呗。(忘记说了哈由于之前好像说过以后的博文直接用Kotlin开发,不知道大家能不能习惯哈~)

    companion object{
            var style = android.R.style.Theme_Light_NoTitleBar
            val LENGTH_SHORT = 0
            val LENGTH_LONG = 1
            var toast:EToast2 ?= null
            fun makeText(context: Context?, message: CharSequence, HIDE_DELAY: Int): EToast2?{
                if(toast == null)
                    toast = EToast2()
                var etoast: EToast2 = toast!!
                if(context == null)
                    throw IllegalArgumentException("You cannot show EToast2 on a null Context")
                else if(Utils.isOnMainThread()&&context !is Application){
                    if(context is FragmentActivity){
                        etoast.initDialog(etoast.get(context),HIDE_DELAY,message)
                    }else if(context is Activity){
                        etoast.initDialog(etoast.get(context),HIDE_DELAY,message)
                    }
                }else{
                    etoast.get(context)
                }
                return etoast
            }
            fun makeText(activity: Activity?,message: CharSequence,HIDE_DELAY: Int): EToast2?{
                if(toast == null)
                    toast = EToast2()
                var etoast: EToast2 = toast!!
                etoast.initDialog(etoast.get(activity),HIDE_DELAY,message)
                return etoast
            }
            fun makeText(activity: FragmentActivity?, message: CharSequence, HIDE_DELAY: Int): EToast2?{
                if(toast == null)
                    toast = EToast2()
                var etoast: EToast2 = toast!!
                etoast.initDialog(etoast.get(activity),HIDE_DELAY,message)
                return etoast
            }
            @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
            fun makeText(fragment: android.app.Fragment?, message: CharSequence, HIDE_DELAY: Int): EToast2?{
                if(toast == null)
                    toast = EToast2()
                var etoast: EToast2 = toast!!
                etoast.initDialog(etoast.get(fragment),HIDE_DELAY,message)
                return etoast
            }
            fun makeText(fragment: Fragment?, message: CharSequence, HIDE_DELAY: Int): EToast2?{
                if(toast == null)
                    toast = EToast2()
                var etoast: EToast2 = toast!!
                etoast.initDialog(etoast.get(fragment),HIDE_DELAY,message)
                return etoast
            }
        }
    

    我们先来看下构造函数,还是通过makeText(…)只是说,这边重载了很多个不同参数的函数,也就是说,Context不限制喽~比如是Activity的话,EToast的生命周期就跟着Activity走,如果是ApplicationContext的话,那就跟着整个App生命周期走,Fragment也一样。
    主要看看参数为Cotext的时候:

    fun makeText(context: Context?, message: CharSequence, HIDE_DELAY: Int): EToast2?{
                if(toast == null)
                    toast = EToast2()
                var etoast: EToast2 = toast!!
                if(context == null)
                    throw IllegalArgumentException("You cannot show EToast2 on a null Context")
                else if(Utils.isOnMainThread()&&context !is Application){
                    if(context is FragmentActivity){
                        etoast.initDialog(etoast.get(context),HIDE_DELAY,message)
                    }else if(context is Activity){
                        etoast.initDialog(etoast.get(context),HIDE_DELAY,message)
                    }
                }else{
                    etoast.get(context)
                }
                return etoast
            }
    

    其实就是和Glide学的,这边对context进行了分类处理喽当然先看看是不是在主线程喽不然的话也没意义是吧。然后说说重点,为什么Glide的所有图片都可以那么灵活聪明呢?为什么都可以跟着生命周期一起走呢?其实,当你简单看一下源码就知道了(以下是Glide的源码):

    public RequestManager get(FragmentActivity activity) {
            if (Util.isOnBackgroundThread()) {
                return get(activity.getApplicationContext());
            } else {
                assertNotDestroyed(activity);
                FragmentManager fm = activity.getSupportFragmentManager();
                return supportFragmentGet(activity, fm);
            }
        }
    
        public RequestManager get(Fragment fragment) {
            if (fragment.getActivity() == null) {
                throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
            }
            if (Util.isOnBackgroundThread()) {
                return get(fragment.getActivity().getApplicationContext());
            } else {
                FragmentManager fm = fragment.getChildFragmentManager();
                return supportFragmentGet(fragment.getActivity(), fm);
            }
        }
    
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        public RequestManager get(Activity activity) {
            if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
                return get(activity.getApplicationContext());
            } else {
                assertNotDestroyed(activity);
                android.app.FragmentManager fm = activity.getFragmentManager();
                return fragmentGet(activity, fm);
            }
        }
    
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
        private static void assertNotDestroyed(Activity activity) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) {
                throw new IllegalArgumentException("You cannot start a load for a destroyed activity");
            }
        }
    
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
        public RequestManager get(android.app.Fragment fragment) {
            if (fragment.getActivity() == null) {
                throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
            }
            if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
                return get(fragment.getActivity().getApplicationContext());
            } else {
                android.app.FragmentManager fm = fragment.getChildFragmentManager();
                return fragmentGet(fragment.getActivity(), fm);
            }
        }
    

    其实不难发现,不管你在Glide.with()方法中传入的是Activity、FragmentActivity、v4包下的Fragment、还是app包下的Fragment,最终的流程都是一样的,那就是会向当前的Activity当中添加一个隐藏的Fragment,然后你就领悟了吧~这个时候其实就有生命周期了。然后这样的话,就简单了,你只需要在Fragment中的每个生命周期回调中调用你自己定义的接口做对应的事情就OK喽 ~这样别人用起来是不是也相当方便,当生命被销毁的时候,自然就释放你的Toast的资源。其实这种方式对于你来说,以后如果想要封装一个控件,然后呢需要绑定生命周期来做一些事情或者搞事情,这种方法就很棒哈。

    那么布局怎么来显示呢?EToast在之前是通过Activity最外层布局来加载需要显示的Toast的,当时呢,我觉得这样的方式挺新颖的,所以就那么做了~那么,我们这次如果不是Activity,那怎么办呢?然后我想既然要用到Fragment,那么我们能不能用DialogFragment呢?这样是不是一举两得,生命周期也有了,布局显示也有了?

    所以我做了,看代码

    class EToast2Fragment: DialogFragment(){
        var HIDE_DELAY = 2000L
        var mTextView:TextView ?= null
        var message: CharSequence ?= ""
        val TAG = "EToast2"
        var callbask: CallBack?= null
        var isShow:Boolean = false
        var outAnimation: Animation ?= null
        companion object{
            val ANIMATION_DURATION = 500L
            fun NewInstance(callback: CallBack): EToast2Fragment {
                var fg = EToast2Fragment()
                var bundle: Bundle = Bundle()
                bundle.putSerializable(TAG,callback)
                fg.arguments = bundle
                return fg
            }
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            initAnimation()
            callbask = arguments.getSerializable(TAG) as CallBack?
            if(callbask?.getStyle() != null)
                setStyle(android.app.DialogFragment.STYLE_NORMAL, callbask?.getStyle()!!)
            else
                setStyle(android.app.DialogFragment.STYLE_NORMAL, android.R.style.Theme_Light_NoTitleBar)
        }
    
        fun initAnimation(){
            outAnimation = AlphaAnimation(1.0f, 0.0f)
            outAnimation?.duration = ANIMATION_DURATION
            outAnimation?.setAnimationListener(object : Animation.AnimationListener{
                override fun onAnimationRepeat(animation: Animation?) {
                }
    
                override fun onAnimationEnd(animation: Animation?) {
                    dismiss()
                }
    
                override fun onAnimationStart(animation: Animation?) {
                }
    
            })
        }
        override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View {
            dialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
            var view = inflater?.inflate(R.layout.etoast,null)
            mTextView = view?.findViewById(R.id.mbMessage) as TextView?
            mTextView?.text = message
            return view!!
        }
    
        override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
            view?.setOnTouchListener{ _, event ->
                activity.dispatchTouchEvent(event)
            }
            isShow = true
        }
        fun setText(message: CharSequence){
            this.message = message
            mTextView?.text = message
        }
        fun delayTime(HIDE_DELAY: Int){
            if (HIDE_DELAY == EToast2.LENGTH_LONG) {
                this.HIDE_DELAY = 2500
            } else if(HIDE_DELAY == EToast2.LENGTH_SHORT){
                this.HIDE_DELAY = 1500
            }
        }
    
        override fun show(manager: FragmentManager?, tag: String?) {
            if(isShow){
                mTextView?.removeCallbacks(mHideRunnable)
            }else{
                super.show(manager, tag)
            }
            mTextView?.postDelayed(mHideRunnable,HIDE_DELAY)
        }
    
        private val mHideRunnable = Runnable {
            mTextView?.startAnimation(outAnimation)
        }
        override fun onDestroy() {
            super.onDestroy()
            callbask?.onDestroy()
        }
    
        override fun onPause() {
            super.onPause()
            callbask?.onPause()
        }
    
        override fun onResume() {
            super.onResume()
            callbask?.onResume()
        }
    
        override fun onStart() {
            super.onStart()
            callbask?.onStart()
        }
    
        override fun onDismiss(dialog: DialogInterface?) {
            super.onDismiss(dialog)
            callbask?.onDismiss()
            isShow = false
        }
    
        override fun onSaveInstanceState(outState: Bundle?) {
            super.onSaveInstanceState(outState)
        }
    }
    

    这就是常规的显示一个DialogFragment,当然还有些其他的细节,比如:

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
            view?.setOnTouchListener{ _, event ->
                activity.dispatchTouchEvent(event)
            }
            isShow = true
        }
    

    这边的就是为了使我们的点击穿透~当你点击DilaogFragment 的时候,点击事件还可以往下传递。然后呢,把所有的生命周期对应的方法绑定起来,也就完事儿了~当然,由于情况很多种,其实还有一个

    EToast2SupportFragment
    

    只不过是继承的包不一样而已了,其余代码是一致的。但是呢,我发现当我对Dialog设置不同显示效果的时候,的确有些不一样的事情,如果我隐藏标题,这个时候点击事件向下传递,其实透过Dialog以后,点击的位置回向上偏移50个点左右,当然,为什么引起的,大家肯定也能想到。就是标题隐藏了,导致实际位置往上走了,但是下面的Activity或Fragment的点击位置也就被偏移了。那么不隐藏标题呢,可能大家的需求不一致~又有说不清的情况。其实东西最终是写完了,目录结构如下:
    这里写图片描述

    但是!但是!我们再仔细想想上面我们要解决的问题哈好像也是都解决了吧但是最终还是被我抛弃了我觉得是不是太臃肿了,就一个Toast以至于这么大费周折吗?主要还有点击偏移的问题,这个就不好解决了。还有dialog的style问题,那么又产生新的问题喽好吧好吧,就当自己练练手了/(ㄒoㄒ)/~~(如果有感兴趣的,可以私聊我要源码哈~)

    所以 最终,我决定从Toast源码入手,看看系统是怎么做的!我们就算不改系统的,那我们仿照系统的来做一套Toast为什么不可以呢?哈哈哈,那就开始分析源码吧。

    ###Toast源码分析
    切记看源码不要一定盯着某一行代码去拼命理解,然后卡在那里…

    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
            Toast result = new Toast(context);
    
            LayoutInflater inflate = (LayoutInflater)
                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
            TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
            tv.setText(text);
            
            result.mNextView = v;
            result.mDuration = duration;
    
            return result;
        }
    

    嗯,就是这个方法了哈~这边能看出2个信息

    1. 源码真的很简单
    2. Toast的布局其实就是R.layout.transient_notification

    然后看show()方法

    public void show() {
            if (mNextView == null) {
                throw new RuntimeException("setView must have been called");
            }
    
            INotificationManager service = getService();
            String pkg = mContext.getOpPackageName();
            TN tn = mTN;
            tn.mNextView = mNextView;
    
            try {
                service.enqueueToast(pkg, tn, mDuration);
            } catch (RemoteException e) {
                // Empty
            }
        }
    

    然后,这边来看先要拿到一个INotificationManager ,然后service.enqueueToast(pkg, tn, mDuration); 这个很关键啊,是队列!有木有,其实这样就能理解了,为什么系统的Toast是那种很讨厌的,一个接着一个慢慢的出来的,有时候点个10下,你App都退了,却还能看到一只在弹Toast。其实这东西,我们并不关注,我们主要是看看系统的Toast的是怎么显示在屏幕上的,是吧。那么继续跟踪源码,我们看看TN又是什么鬼呢?

    private static class TN extends ITransientNotification.Stub {
            final Runnable mHide = new Runnable() {
                @Override
                public void run() {
                    handleHide();
                    // Don't do this in handleHide() because it is also invoked by handleShow()
                    mNextView = null;
                }
            };
    
            private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
            final Handler mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    IBinder token = (IBinder) msg.obj;
                    handleShow(token);
                }
            };
    
            int mGravity;
            int mX, mY;
            float mHorizontalMargin;
            float mVerticalMargin;
    
    
            View mView;
            View mNextView;
            int mDuration;
    
            WindowManager mWM;
    
            static final long SHORT_DURATION_TIMEOUT = 5000;
            static final long LONG_DURATION_TIMEOUT = 1000;
    
            TN() {
                // 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 = com.android.internal.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;
            }
    
            /**
             * schedule handleShow into the right thread
             */
            @Override
            public void show(IBinder windowToken) {
                if (localLOGV) Log.v(TAG, "SHOW: " + this);
                mHandler.obtainMessage(0, windowToken).sendToTarget();
            }
    
            /**
             * schedule handleHide into the right thread
             */
            @Override
            public void hide() {
                if (localLOGV) Log.v(TAG, "HIDE: " + this);
                mHandler.post(mHide);
            }
    
            public void handleShow(IBinder windowToken) {
                if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                        + " mNextView=" + mNextView);
                if (mView != mNextView) {
                    // remove the old view if necessary
                    handleHide();
                    mView = mNextView;
                    Context context = mView.getContext().getApplicationContext();
                    String packageName = mView.getContext().getOpPackageName();
                    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();
                    final int 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;
                    mParams.hideTimeoutMilliseconds = mDuration ==
                        Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
                    mParams.token = windowToken;
                    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);
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                }
            }
    
            private void trySendAccessibilityEvent() {
                AccessibilityManager accessibilityManager =
                        AccessibilityManager.getInstance(mView.getContext());
                if (!accessibilityManager.isEnabled()) {
                    return;
                }
                // treat toasts as notifications since they are used to
                // announce a transient piece of information to the user
                AccessibilityEvent event = AccessibilityEvent.obtain(
                        AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
                event.setClassName(getClass().getName());
                event.setPackageName(mView.getContext().getPackageName());
                mView.dispatchPopulateAccessibilityEvent(event);
                accessibilityManager.sendAccessibilityEvent(event);
            }        
    
            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;
                }
            }
        }
    

    哈哈哈~这个时候是不是真相大白了,看handleShow这边才是我们的关键,原来啊,Toast的视图是通过WindowManager的addView来加载的。那么Toast的原来总结一下就是这样:
    先通过makeText()实例化出一个Toast,然后调用toast.show()方法,这时并不会马上显示Toast,而是会实例化一个TN变量,然后通过service.enqueueToast()将其加到服务队列里面去等待显示。在TN中进行调控Toast的显示格式以及里面的hide()、show()方法来控制Toast的显示和消失。然后最可悲的是这个队列是系统维护的,我们并不能干涉,所以才会出现我们屏蔽系统通知的时候,连Toast都没有了哈~

    ###真正实现EToast2
    那么既然上面做了那么多事,我们也看了Toast的源码,那么就直接仿照Google的也来写一套呗,这样不是所的问题都解决了么,不受系统控制,然后还可以把那个队列去掉~优化一下显示规则什么的,是吧。

    /**
     * Author: Blincheng.
     * Date: 2017/6/30.
     * Description:EToast2.0
     */
    
    public class EToast2 {
    
        private WindowManager manger;
        private Long time = 2000L;
        private static View contentView;
        private WindowManager.LayoutParams params;
        private static Timer timer;
        private Toast toast;
        private static Toast oldToast;
        public static final int LENGTH_SHORT = 0;
        public static final int LENGTH_LONG = 1;
        private static Handler handler;
        private CharSequence text;
    
        private EToast2(Context context, CharSequence text, int HIDE_DELAY){
            manger = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    
            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){
                toast = Toast.makeText(context, text, Toast.LENGTH_SHORT);
                contentView = toast.getView();
    
                params = new WindowManager.LayoutParams();
                params.height = WindowManager.LayoutParams.WRAP_CONTENT;
                params.width = WindowManager.LayoutParams.WRAP_CONTENT;
                params.format = PixelFormat.TRANSLUCENT;
                params.windowAnimations = toast.getView().getAnimation().INFINITE;
                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;
                manger.addView(contentView, params);
                timer = new Timer();
            }else{
                timer.cancel();
                oldToast.setText(text);
            }
            timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    handler.sendEmptyMessage(1);
                }
            }, time);
        }
    
        public void cancel(){
            manger.removeView(contentView);
            timer.cancel();
            oldToast.cancel();
            timer = null;
            toast = null;
            oldToast = null;
            contentView = null;
            handler = null;
        }
        public void setText(CharSequence s){
            toast.setText(s);
        }
    }
    

    上面就是EToast2的全部源码了是不是很少,就那么点点。看构造函数,其实我们也是借用了Google的Toast,拿到里面的布局喽这样就是原汁原味了,至少,是吧。然后其余设置其实和Toast没有什么区别,唯一不同的是没有队列了,然后显示的规则是就是:
    由于在Android5.0以下无法获取是否打开系统通知权限,所以为了防止用户看不到Toast,最终的逻辑优化了一下:

    1. 当用户是5.0以下的机器时,永远只显示EToast
    2. 当用户是5.0以上的机器是,如果打开了通知权限,则显示系统Toast;反之则显示Etoast
    3. 当一个Toast在屏幕中显示,这时又弹出Toast的话会直接改变Toast上的文字,并且重置计时器,在规定的时间后消失。

    然后Toast这边我也做了一点优化,舍弃了一些接口,比如setText(Resid)。因为Toast显示的内容一般变化比较大,所以一般不会通过String写在本地吧~真的要用,我想你也有办法用的,是吧。(别说我偷懒)

    /**
     * Author: Blincheng.
     * Date: 2017/6/30.
     * Description:
     */
    
    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 Object mToast;
        private Toast(Context context, String message, int duration) {
            checkNotification = isNotificationEnabled(context) ? 0 : 1;
            if (checkNotification == 1) {
                mToast = EToast2.makeText(context, message, duration);
            } else {
                mToast = android.widget.Toast.makeText(context, message, duration);
            }
        }
        private Toast(Context context, int resId, int duration) {
            if (checkNotification == -1){
                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();
            }
        }
        public void setText(CharSequence s){
            if(mToast instanceof EToast2){
                ((EToast2) mToast).setText(s);
            }else if(mToast instanceof android.widget.Toast){
                ((android.widget.Toast) mToast).setText(s);
            }
        }
        /**
         * 用来判断是否开启通知权限
         * */
        private static boolean isNotificationEnabled(Context context){
            if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT){
                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;
                }
            }else{
                return false;
            }
        }
    }
    

    ##总结
    最后呢,经过这么一波三折,东西终于弄完了,这边也要特别感谢xiaogaofudao 帮忙测试和提供一些建议,非常感谢。
    v2.0.1最新版本你已经上线了哦经过本人和其他的一些用户使用体验,应该是可以的,如果大家在使用中发现什么问题,欢迎及时联系哦(づ ̄ 3 ̄)づ

    展开全文
  • 关闭常驻通知:左上角主界面----软件设置----通知栏图标,关闭。 希望可以帮到您!微笑为您解答,如果有什么问题还可以继续咨询哦!O(∩_∩)O 追问 这个方法应该是旧版本app的方法吧?我现在使用的是...

    2013-10-19 10:08 提问者采纳
    您好,感谢您对金山网络的支持:
    
          关闭常驻通知:左上角主界面----软件设置----通知栏图标,关闭。
    
    希望可以帮到您!微笑为您解答,如果有什么问题还可以继续咨询哦!O(∩_∩)O
    追问

    这个方法应该是旧版本app的方法吧?我现在使用的是3.1.2版本,设置菜单如图所示,并没有“通知栏图标”这一项,而且也只在骚扰拦截和防御监控这两项里找到控制通知栏在拦截到骚扰信息/隐私行为时是否提醒的选项,常驻通知如何关闭还是没有发现

    回答
    新版在如下位置:主界面----设置----通知栏图标,关闭。最新的是有的,您尝试卸载手机毒霸,再次下载安装试试。
    提问者评价
    重装后还是这样,无法关闭。联系了金山才知道是因为4.3系统本身的原因无法隐藏常驻图标。望尽早优化
          
    浪涛_朵朵 2014-1-1 20:29
    看我的评论 回复
    浪涛_朵朵 2014-1-1 20:27
    这是4.3为了让垃圾软件的后台进程无从遁形,这样恶意程序就容易被发现,当然如果属于正常的可信任程序,在应用程序管理器里面把相应程序“显示通知”的选项关闭,就不会在通知栏占地方了 回复
    展开全文
  • Windows 10怎么彻底关闭消息通知

    千次阅读 2017-08-15 11:43:00
    可能很多Win10用户都知道,可以在“设置-系统-通知和操作”中将各种通知选项关闭掉,但是该方法不够彻底,对于一些系统安全方面的通知并不奏效,该来的通知照样来,还是会影响大家手头的工作。下面小编将给大家分享...

    Win10怎么彻底关闭消息通知?可能很多Win10用户都知道,可以在“设置-系统-通知和操作”中将各种通知选项关闭掉,但是该方法不够彻底,对于一些系统安全方面的通知并不奏效,该来的通知照样来,还是会影响大家手头的工作。下面小编将给大家分享彻底关闭Win10通知的操作方法,斩草就要除根,大家快来看看吧!  

    Windows 10怎么彻底关闭消息通知?

    1、Win+R输入regedit后进入注册表编辑器;

    2、直接定位到:HKEY_LOCAL_MACHINE/Software/Microsoft/Windows/CurrentVersion/ImmersiveShell,在右侧新建个“UseActionCenterExperience”的DWORD(32位)值,数值数据默认为0不用修改,就算是彻底屏蔽弹出的通知消息了。  

    3、随后再定位到:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ImmersiveShell\Launcher(就是刚才那个位置下面的项),在右侧新建个名为“DisableLightDismiss”的DWORD(32位)值,数值数据设置为1。  

    4、重启资源管理器或注销当前账户后,再点击通知中心,就可以看到效果了,它处于开启状态,驻留在屏幕的边缘,不会因其他操作而自动隐藏(想要回收就再点下任务栏的通知中心图标)。  

    5、之后只要让其处于免打扰的状态(通知中心上鼠标右键选择“打开免打扰时间”),就可以安静的看着它将各种各样的信息罗列在一遍,而不会对正在使用电脑的我们造成任何影响。  

    关于彻底关闭Win10通知的操作方法就给大家分享到这里了,如果你还在被Win10通知困扰着,那就赶紧按照小编介绍的方法关闭吧,不过大家在操作之前请记得备份下注册表。






    本文作者:佚名
    来源:51CTO
    展开全文
  • 当APP有推送功能时,需要判断当前app在手机中是否开启了允许消息推送,否则即使添加了推送代码仍然收不到通知,所以需要要么跳转至设置界面设置,要么自定义消息通知。 效果图 方法一:跳转到应用程序设置界面...
  • iOS推送通知关闭

    千次阅读 2018-12-27 14:12:40
     message:@"未开启通知,会错过很多重要信息。请到【设置】中开启"  delegate:self  cancelButtonTitle:nil  otherButtonTitles:@"确定", nil];    alert.tag = 111;    [alert show];...
  • 2016-11-10 yiguo_blin 鸿洋 本文由yiguo_blin投稿。...不知道大家是否遇到了当你们的App在5.0以上系统中被用户关闭消息通知后(其实用户本身只是想关闭Notification的,猜测),系统的Toast也神
  • DownDetector 服务或漫游器关闭时在Discord上通知您。
  • 只是一般用户不会怎么注意,开发人员也不会很在意APP的通知开关,因为GOOGLE还没有在通知上大做文章,进入到APP信息中,通知的开关也不是很起眼。但是8.0上针对通知部分(主要针对下拉通知)做了较大修改,其中牵连...
  • close:用户关闭通知时触发。 error:通知出错时触发(通知无法正确显示时出现)。 这些事件有对应的onshow、onclick、onclose、onerror方法,用来指定相应的回调函数。addEventListener方法也可以用来为这些...
  • 通知栏声音的开启与关闭

    千次阅读 2015-09-09 11:22:28
    问题引出:在项目中牵涉到设置,里面有对推送信息的声音的设置。 思路:1、开始觉得既然是用 notification.defaults = Notification.DEFAULT_SOUND; 来开启声音,那么关闭就应该用类似notification.defaults = null;...
  • 短信使用阿里云,邮箱使用swiftmailer插件。 支持php~~~ 功能点: 用户注册通知 用户注册类: public function actionCreateUsers(){ //数据过滤 数据判断 这个省略了。。。。 直接看重点 if($model->save()){...
  • 最近接了激光推送,完成后测试这边提出了这么一个问题:app退出后,通知还在通知栏,这个时候点击通知后,手机会从桌面跳转到对应的订单页面,接着app直接闪退.原因分析:因为app中跳转掉订单页面,获取订单信息需要用户信息...
  • wordpress博客每次有新用户注册都会给我们管理邮箱发一邮件告诉你有新用户注册了,那么如果我们不想接受要如何取消呢,下面我来介绍关闭wordpress新用户注册邮件通知具体方法。打开wp-includes/pluggable.php 文件...
  • 如何在Apple Watch上关闭应用程序通知 (How to Turn Off App Notifications on Apple Watch) Starting with watchOS 5, Apple Watch gained the ability to quiet and disable notifications right from the ...
  • 为了增加系统的安全性,微软windows系统是默认开启安全警报的,当我们在安装某些软件或游戏等等时,系统总是会弹出各种安全警报,...重要的经常提示安全信息甚是烦人,下面给大家介绍下关闭win10系统安全警报两种方法。
  • Android通知栏微技巧,8.0系统中通知栏的适配

    万次阅读 多人点赞 2018-04-17 07:39:11
    大家好,今天我们继续来学习Android 8.0系统的适配。...在上一篇文章当中,我们学习了Android 8.0系统应用图标的适配,那么本篇文章,我们自然要将重点放在通知栏上面了,学习一下Android 8.0系统的通知栏适配
  • 如何关闭mcafee软件McAfee, like most other modern antivirus programs, doesn’t stay out of your way. It installs browser extensions and shows various alert messages you might not want to see. If McAfee...
  • app常驻通知通知

    热门讨论 2013-03-16 11:29:10
    android环境下,进入APP,通知栏挂起一条通知,不可被自动清除,且在软件运行过程中,可以直接通过点击通知栏进入APP堆栈内最后一个activity。在软件退出时,通知栏自动关闭
  • Python 短信通知系统开发实战

    千次阅读 2018-07-03 02:45:09
    作为学生,你想不想要这样一种服务:教务系统更新成绩后,你的手机上会自动收到成绩通知? 作为白领,你想不想要这样一种服务:公司发布了晋升、放假等新闻时,你的手机上会第一时间收到新闻? 作为…… 什么,你还...
  • 如今项目中集成的 阿里云推送,不过app在后台被kill后就不能收到推送了,怎么办, 按照老板的意思,通知就是在这种情况下才重要啊,而且QQ和微信都可以,我以无力反驳 只能来求助大佬们了
  • 注册139邮箱,打开email,stop以及start数据库上面的mysql进程服务,就会收到报警email以及短信通知,报警email如下:     记得开启短信提示功能,短信免费。           ...
  • 1.frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, ...
  • android 如何在完全退出APP后延时调出通知(类似于保活) 很好,项目总监在我开心的YY的时候过来提了一个需求:当游戏退出时,请注意不是退出后台,是...实现定时推送信息通知栏 分析: 1.如何触发通知,那肯...
  • 本例实现了利用短信远程关闭计算机的功能,利用这一功能,我曾编制了一个远程安全监控系统--把摄像头与短信功能结合,只要发现异常,就通过短信通知主人。
  • selenium访问csdn, Chrome浏览器总会有个通知,如下: 解决方法: #!/usr/bin/env python # -*- coding:utf-8 -*- from selenium import webdriver import time options = webdriver.ChromeOptions() prefs =...
  • 异步通知

    千次阅读 2017-08-18 14:28:02
    一、什么是异步通知 异步通知类似于中断的机制。当设备可写时,设备驱动函数发送一个信号给内核,告知内核有数据可读,在条件不满足之前,并不会造成阻塞。而不像之前学的阻塞型IO和poll,它们是调用函数进去检查,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 262,160
精华内容 104,864
关键字:

如何关闭通知信息