[原创]Android在任意窗口前弹出rss更新提醒对话框


客户有一个需求,当server上的rss进行更新时,希望可以弹出一个提醒对话框,通知用户现在有新的rss更新。
要求是无论客户目前在哪个界面,只要是客户的主程式在开启状态,都可以弹出提示窗口。
最开始想到的解决方案是开启一个service,定时进行rss的抓取和对比,然后弹出提醒对话框。
因为目前对于service的用法还不熟悉,加上项目时间的限制,想先在现有的程式基础上进行修改,看是否能实现。
(关于service的应用有时间要抓紧学习)
要在现有基础上修改的话,就先在home直接打开一个activity或窗口,看能否显示在最前面。
于是,在home的onCreate中另外开启一个线程,定时循环去server上取得rss并于现有rss进行对比。
当发现有更新时,sendMessage给一个handler,由handler打开一个新的activity的提醒窗口。
结果发现一旦离开home进入其他程式后,无法弹出对话框。
到网上寻找答案,意外看到一个论坛中的帖子,如果在打开avtivity时设定flag为Intent.FLAG_ACTIVITY_NEW_TASK,就可以在任意窗口前面打开activity。
实验之后发现,真的可以!!!欣喜若狂中,以为就此完成也太简单了。
另外还发现,在使用Intent.FLAG_ACTIVITY_REORDER_TO_FRONT时也可以实现同样的效果。
但是在接下来的测试中很快发现问题:
第一:新建的循环取rss的线程和画面上rss的更新线程有冲突。
      解决办法:添加一个flag,控制两个线程的执行顺序。
第二:线程在每次执行onCreate时会重复创建,因为屏幕方向每次转换的时候都执行onCreate!!
      解决办法:将线程定义为全局变量,在onCreate时new,可是这样每次旋转屏幕都重新开始计时。于是想到在线程创建后,将线程对象保存到application中,
      每次执行onCreate时都判断是否线程对象为空,如果不为空则不进行创建。
      在调查过程中发现当屏幕翻转时有一下几个方法可以使用:
      参考:http://blog.yam.com/ipray/article/29768416
    1.onSaveInstanceState可以储存程式的状态信息,
      onSaveInstanceState and onRestoreInstanceState
      onCreate()-> onResume() --轉方向--> onSaveInstanceState() -> onDestroy() -> onCreate() -> onRestoreInstance() -> onResume()
    2.onRetainNonConfigurationInstance and getLastNonConfigurationInstance 来储存程式的状态信息。
      onCreate() -> onResume() --轉方向--> onRetainNonConfigurationInstance() -> onDestroy() -> onCreate()裡面getLastNonConfigurationInstance -> onResume()
        故就旋轉方向而言, 使用(II)是比較便捷, 省去onResume()的周期,
        而(I)一定須要onResume(), 因為onRestoreInstance()在onCreate()後才會被呼叫~
        在onCreate中:

  getLastNonConfigurationInstance();
      if(getLastNonConfigurationInstance()!=null)
          myLocation = (ArrayList) getLastNonConfigurationInstance();
      else {
             ...
        }

 

  @Override
      public Object onRetainNonConfigurationInstance() {
        myLocation.add("Beijing");
        return myLocation;
        }

本来想要通过上面的第二中方法来设置flag过滤掉屏幕翻转的动作,由于使用application更简单,就放弃的这个方法。
第三:当用户切换到android 桌面后,rss更新监视进程被阻断,这样用户再回到home无法及时取得rss信息。
       解决办法:因为home被再次从后台提出来时,会执行restart方法。则在restart中另外开一个rss检测线程,不过只执行一次就停止,不用无限循环。
       另外,项目要求在用户最开始进入home就要进行一个rss更新检测,于是,在onCreate中同样新建一个线程,也只是执行一次。
       执行一次的控制方式为,设置一个flag,利用flag来停止进行,让系统自动回收。(不推荐使用stop()停止进程!)

Thread tCheckRssOnCreate = new Thread() {
        boolean checkRssOnCreateFlag = false;
        public void run() {
            while (!checkRssOnCreateFlag) {
                      ...
               }
          }
    };


        另外,在onCreate中的执行一次的进程为了防止在屏幕翻转时重复创建,也是放在取application判断进程是否为空的判断中。
第四:当用户在打开程式的状态下,如果一直不用pad,当设置弹出提醒框时间足够短时,会一直弹出对话框,造成home无法响应,阻塞掉主进程。
      解决方法:因为不明原因,不清楚是否是因为rss更新检测进行和rss的ui更新进程有冲突。简单的解决办法是,设置一个flag为true,在窗口弹出后将其设为false,
      只有在用户点击提醒对话框中button之后,才再次转为true,而在进行sendMessage之前判断是否为ture,只有在flag是true的时候,才进行rss取得和判断。
     if (homeApplication.isRssUpdateDialogShowFlag() && !checkRssUpdate(null) ) {}
       同样,将这个flag保存到application中,application还真是万能无敌的好用!!!
第五:这是个最严重的问题,因为在home中有调用android的browser还有播放器,另外还有书籍阅读程式,在这些程式执行时,提醒对话框无法弹出!!!!
       这时候想到是否是因为这些程式都是在新开的task中,或是权限太高无法将提醒窗口弹到其上呢?
       于是想到利用FLAG_ACTIVITY_NEW_TASK的情况下再加上FLAG_ACTIVITY_TASK_ON_HOME,在其task上面新建task,实验结果失败!
       在网上查找资料发现可以设置窗体的级别,通过
     this.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
     this.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY)
      还要在AndroidManifest.xml这中设置
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
      可因为我是使用的activity模拟窗口,试过这两种都无法打开,另外还实验了其他的窗体级别:
      FIRST_SYSTEM_WINDOW(失败), TYPE_SYSTEM_DIALOG(发生异常提示我权限不够,无解中,不知道如何才能有执行的权限??)
      (想要做成从屏幕最下方状态栏中弹出提醒对话框,一时不知道如何进行,时间紧迫,有时间再研究了。)
      是不是用窗口就可以呢?
      于是彻底转变思路,在handler中弹出真正的窗体而不是弹出activity来模拟窗体。
      代码如下:

  AlertDialog d = new AlertDialog.Builder(context)
                      .setTitle("tanchulai")
                    .setMessage("bucuo de tanchulai")
                    .create();
     d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
     //d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY);
     d.show();

      实验结果,成功了!!!竟然成功穿越了书籍阅读器,弹出了窗体。
      兴奋之下,抓紧时间利用设计好的窗体view再次弹出窗体,惊讶的是,居然又失败了!!!!开发过程,真的是高潮迭起。
      代码如下:

View v = View.inflate(context, R.layout.rss_update_remind, null);
     AlertDialog d = new AlertDialog.Builder(context)
                      .setTitle("tanchulai")
                    .setMessage("bucuo de tanchulai")
                      .setView(v)
                      .setPositiveButton(R.string.to_read, new DialogInterface.OnClickListener() {
                          @Override
                          public void onClick(DialogInterface dialog, int which) {
                             // TODO Auto-generated method stub
                             homeApplication.setRssUpdateDialogShowFlag(true);
                             dialog.dismiss();
                                   }
                                })
                    .create();
     d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
     //d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY);
     d.show();

      是因为在窗体的view中添加了PositiveButton的缘故吗?开始猜想是不是因为alert只能有提醒的功能,完全无解了。
      想到可不可以换另外一种方式,用Toast来实现,于是实验之下发现,Toast可以任意穿越!!!!太牛了。
      可是接下来又是一盘冷水,toast可以调整位置和自定义窗体,(参考:)
      但是无法在其中使用button,因为toast只是设计用来进行提示功能。
      失落之中上网查找资料,无意中发现一个使窗体悬浮和可拖动的blog
      参考网址:http://gundumw100.iteye.com/blog/899977
      心里想着,死马当成活马医,把他的代码考来看看效果也好

public class myFloatView extends Activity {  
        /** Called when the activity is first created. */  
       @Override  
        public void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.main);  
            Button bb=new Button(getApplicationContext());  
            WindowManager wm=(WindowManager)getApplicationContext().getSystemService("window");  
            WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams();  
            wmParams.type=2002;  //type是关键,这里的2002表示系统级窗口,你也可以试试2003。  
            wmParams.format=1;  
                /**
                  *这里的flags也很关键
                  *代码实际是wmParams.flags |= FLAG_NOT_FOCUSABLE;
                  *40的由来是wmParams的默认属性(32)+ FLAG_NOT_FOCUSABLE(8)
                  */  
            wmParams.flags=40;  
            wmParams.width=40;  
            wmParams.height=40;  
            wm.addView(bb, wmParams);//创建View  
          }  
      }

      因为只是随便实验,没有看拖动效果,也没有layout,在书籍阅读器中也无法弹出窗体。正在无奈之余,发现一个惊人的现象,
      就是无论我的页面如何跳转,button始终在最上层,仔细看代码,
    Button bb=new Button(getApplicationContext());
      原来是取出来application的context进行button的初始化!!!!!!!!马上在我的程式中实验:

View v = View.inflate(getApplicationContext(), R.layout.rss_update_remind, null);
     AlertDialog d = new AlertDialog.Builder(getApplicationContext())
                      .setTitle("tanchulai")
                    .setMessage("bucuo de tanchulai")
                      .setView(v)
                      .setPositiveButton(R.string.to_read, new DialogInterface.OnClickListener() {
                          @Override
                          public void onClick(DialogInterface dialog, int which) {
                             // TODO Auto-generated method stub
                             homeApplication.setRssUpdateDialogShowFlag(true);
                             dialog.dismiss();
                                   }
                                })
                    .create();
     d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
     //d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY);
     d.show();

      成功了!!!!竟然可以在任意程式前跳出窗体,既然切换到android桌面,因为home仍然在运行状态,窗体也能弹出!!
      至此、问题几乎全部解决,剩下的是细节上的修改,感谢上面的那位博主无私的分享。
另外,如果是改用activity模拟窗体,应该也是可以实现同样的效果,留待以后进行实验。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM