zoukankan      html  css  js  c++  java
  • Android学习笔记:Home Screen Widgets(2):关于Widget

    通过widget定义,我们在widget列表中看到了我们的TestWidget。当我们拖拽widget到主页时,假设在appwidet-provider中定义了android:configure的java类,在widget实例创建后会立即唤起配置activity。

    这个activity主要完毕两个任务:1、配置初始化数据;2、将配置数据适配到widget实例中。

    利用preference中存贮配置数据

    widget数据能够保持在文件、Share preference,或者SQLite3中。widget作为小工具配置数据量小,通常能够方便地存贮在preference中。preference中数据存贮和读取使用BirthDayStoreData类来处理。我们在Pro Android学习笔记(六三):Preferences(7):代码控制首选项中的“利用preference保存状态”已经介绍过怎样实现。在此,复习一下。

    我们须要存贮的内容有widgetID,名字,生日,Preference是以键值对的方式保存。我们以name_widgetID作为key,生日作为value来进行信息存贮。

    public class BirthDayStoreData { 
        private final static String BIRTHDAY_WIDGET_PROVIDER_NAME = "cn.wei.flowingflying.testwidget.provider"; 

        //保存配置数据:创建widget实例,通过configure activity进行配置时,保存相关配置数据 
        public static void storeData(Context context,int widgetId, String name,String value){
            String key = getKey(widgetId,name);            
            //第一个參数是preferences文件。假设不存在则创建之。详细为/data/data/cn.wei.flowingflying.testwidget/shared_prefs/cn.wei.flowingflying.testwidget.provider.xml,能够在DDMS中查看。
            Editor editor = context.getSharedPreferences(BIRTHDAY_WIDGET_PROVIDER_NAME, Context.MODE_PRIVATE).edit();
            editor.putString(key, value); 
            editor.commit();
        
        } 

        //删除配置数据:删除widget实例的同一时候,须要删除该实例的相关数据
        public static void removeData(Context context, int widgetId){ 
            String key = getKeyById(context, widgetId); 
            if(key == null) 
                return; 
            Editor editor = context.getSharedPreferences(BIRTHDAY_WIDGET_PROVIDER_NAME, Context.MODE_PRIVATE).edit();
            editor.remove(key); 
            editor.commit(); 
        } 
        //清空所有的配置数据 
        public static void removeAllData(Context context){ 
            Editor editor = context.getSharedPreferences(BIRTHDAY_WIDGET_PROVIDER_NAME, Context.MODE_PRIVATE).edit();
            editor.clear(); 
            editor.commit();        
        }    

        //显示配置数据:用于我们在LogCat中进行跟踪,在此,我们也演示了怎样通过轮询方式,显示所有的数据。通过类似的方式,我们能够同widgetId查得相应的名字和生日。通过类似的方法,可依据widgetId查询key,名字,生日,相关代码从略。
        public static void showData(Context context){ 
            SharedPreferences prefs = context.getSharedPreferences(BIRTHDAY_WIDGET_PROVIDER_NAME, Context.MODE_PRIVATE);
            Map<String,?

    > pairs = prefs.getAll();  
            Log.d("DATA","Total " + pairs.size() + " widgets:");
            for(String key:pairs.keySet()){ 
               String value = (String)pairs.get(key);
     
                Log.d("DATA",key + " - " + value);
           } 
        }  
         
        public static String getNameById(Context context, int widgetId){ 
            … … 
        } 
        
        public static String getDateById(Context context ,int widgetId){ 
            … …   
        } 
        
        private static String getKey(int widgetId, String name){ 
            return name + "_" + widgetId; 
        }   
         
        private static String getKeyById(Context context,int widgetId){  
            … … 
        }  
        
    }

    配置初始化数据

    配置configure activity的代码例如以下:

    public class ConfigBirthDayWidgetActivity extends Activity{ 
        private static String tag = "ConfigActivity"; 
        private int myWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; 
        
        @Override //配置activity的操作和普通activity的一样,但在被AppWidgetManage唤起时,intent是携带widgetId的信息。我们在onCreate()中获取Widget ID。 
        protected void onCreate(Bundle savedInstanceState) { 
            … …  
            Intent intent = getIntent(); 
            Bundle b = intent.getExtras(); 
            if(b != null){ 
                myWidgetId = b.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);           
            } 
            
            if(myWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID){  
                Toast.makeText(this, "Widget Error : 无有效widget ID", Toast.LENGTH_LONG).show();
                finish(); 
            }  

        } 
        
        .. ….  
          
       //点击配置button后调用的方法 
        private void getAndStoreConfigInfo(){  
            … …  String name为用户输入名字,String date为用户输入的有效日期
            //【1】在preference中保持数据,并显示全部数据 
            BirthDayStoreData.storeData(this, myWidgetId, name, date);
            BirthDayStoreData.showData(this); 
            //【2】将配置数据与详细的widget实例相关联,详细实现见后面
            BirthDayStoreData.updateAppWidget(this, myWidgetId,name, date); 
            
            //【3】将结果返回给AppWidget Manager,以通知它Configurator已经完毕。

    作用如同startActivityForResult()给出返回值,通知AppWidgetManager某个widgetId已经完毕配置,能够在主页上显示创建的widget实例 
            Intent resultIntent = new Intent(); 
            resultIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, myWidgetId);
            setResult(RESULT_OK, resultIntent);
     
            //【4】关闭activity
            finish(); 
        }      
    }

    配置数据适配到widget实例中

    Widget实例的view要通过RemoteViews进行控制,小样例採用静态方法的方式。代码片段例如以下:

    public class BirthDayStoreData {  
        ... ... 
          
        public static void updateAppWidget(Context context,int widgetId,String name, String date){
           //【1】设置Remote view的信息 
            // 1.1)、获得remote view对象
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.birday_widget);
            // 1.2)、对remote view进行setText()设置
            views.setTextViewText(R.id.bd_name, widgetId + ":" + name);
            views.setTextViewText(R.id.bd_date, date);  
            views.setTextViewText
    (R.id.bd_days, Long.toString(Utils.howFarInDays(Utils.getDate(date))));//Utils是处理日期的类
            // 1.3)、通过PendingIntent设置某个view的点击处理,採用intent方式,能够打开activity。service,receiver等等。

    本小样例将打开某个网页 
            Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("http://www.taobao.com"));
            PendingIntent pi = PendingIntent.getActivity(context, 0, intent, 0); 
            views.setOnClickPendingIntent(R.id.bd_buy, pi);
     
            
            //【2】通过AppWidgetManger。详细实施到widgetId实例上。


            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
            appWidgetManager.updateAppWidget(widgetId,views);
     
        } 
        
        public static void updateAppWidget(Context context,int widgetId){
            … …  
        }
     

    }

    依据widget定义,我们App Widget Provider的Java类为BirthDayWidgetProvider。这个类用于管理Widget的各个生命周期。

    public class BirthDayWidgetProvider extends AppWidgetProvider{ 
        private static String tag = "BirthDayWidgetProvider"; 

        @Override /* 在3种情况下会调用OnUpdate()。onUpdate()是在main线程中进行,因此假设处理须要花费时间多于10秒,处理应在service中完毕。


    (1)在时间间隔到时调用,时间间隔在widget定义的android:updatePeriodMillis中设置; 
    (2)用户拖拽到主页。widget实例生成。
    不管有没有设置Configure activity,我们在Android4.4的測试中,当用户拖拽图片至主页时。widget实例生成,会触发onUpdate()。然后再显示activity(假设有)。这点和资料说的不一样,资料觉得假设设置了Configure acitivity,就不会在一開始调用onUpdate(),而实验显示当实例生成(包含创建和重新启动时恢复)。都会先调用onUpate()。

    在本例,因为此时在preference尚未有相关数据。创建实例时不能有效进行数据设置。
    (3)机器重新启动。实例在主页上显示,会再次调用onUpdate()*/ 
       public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 
            Log.d(tag,"onUpdate() called. 有 " + appWidgetIds.length + "个widgets");   
            for(int i = 0 ; i< appWidgetIds.length; i ++){ 
                Log.d(tag,"update widget ID " + appWidgetIds[i]);
                BirthDayStoreData.updateAppWidget(context, appWidgetIds[i]); 
            } 
        } 

        @Override  /* 某个/些widget从主页中删除,在此删除该widget的相关数据  */
        public void onDeleted(Context context, int[] appWidgetIds) { 
            Log.d(tag,"onDeleted() called"); 
            for(int i = 0 ; i < appWidgetIds.length; i ++){ 
                Log.d(tag,"delete widget " + appWidgetIds[i] + " data");
                BirthDayStoreData.removeData(context, appWidgetIds[i]); 
            } 
            BirthDayStoreData.showData(context); 
        } 
        
        @Override /* 一般无需重写此方法。App Widget provider本质是receiver,在此能够跟踪收到什么消息。这些消息包含AppWidgetManager.ACTION_APPWIDGET_DELETED/UPDATE/ENABLED/DISABLED,super.onReceiver()会依据消息类型触发不同的回调函数。假设採用AlarmManager或者自己定义的广播,能够再次进行处理。 */ 
        public void onReceive(Context context, Intent intent) {
      
            Log.i(tag,"onReceive() : " + intent);  
            super.onReceive(context, intent);  
        }      

        @Override  /* 表明至少有一个widget实例被拖到主页上,即当第一个widget出现时的回调函数。

    我们须要同意广播接收器接收消息,第一个widget出现了。

    我们能够在此注冊其他感兴趣的自己定义的广播*/
        public void onEnabled(Context context) {
      
            Log.d(tag,"onEnabled() called, context " + context.toString());          
            // setComponentEnabledSetting相当于在AndriodMenifest.xml文件里队组件设置android:enabled为true|false。此处是对receiver进行设置,假设true。则同意进行监听,包含开机重新启动。 
            PackageManager pm = context.getPackageManager(); 
          /*使用new ComponentName("cn.wei.flowingflying.testwidget",".BirthDayWidgetProvider")出现不明原因错误,
            * 可对类名採用全然名称。及new ComponentName("cn.wei.flowingflying.testwidget", 
            *                               "cn.wei.flowingflying.testwidget.BirthDayWidgetProvider"),
            * 或通过系统获取组件名的方式new ComponentName(context, getClass())*/
     
            pm.setComponentEnabledSetting(new ComponentName(context, getClass()),
                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 
                    PackageManager.DONT_KILL_APP);         
        } 

        @Override  /*最后一个widget已从主页中删除,在此,确保删除全部配置数据。无需进行广播监听,色织enabled=false。假设有注冊的自己定义广播,在此unregister */
        public void onDisabled(Context context) {  
            BirthDayStoreData.removeAllData(context); 
            PackageManager pm = context.getPackageManager(); 
            pm.setComponentEnabledSetting(new ComponentName(context, getClass()), 
                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 
                    PackageManager.DONT_KILL_APP);          
        } 

    }

    假设主页没有实例,新实例的生成触发顺序为:onEnabled() –>onUpdate() –>Configure Activity,头两个顺序可能会出现变化。预计是AppWidgetManager的异步处理导致广播消息出现的先后顺序问题。假设已经有实例,新实例生成触发顺序为onUpdate() –> Configure activity。配置后,等待定义的时间间隔,进行定期触发onUpdate()。

    机器重新启动 onEnabled() –>onUpdate() –> onUpdate(),相同头两个顺序可能会交换,此后。等待widget定义的时间间隔,进行定期触发onUpdate()。

    假设我们更新或重装apk,实例并不会被删除,会触发onUpdate()。

    补充:Widget图标

    Widget在widget列表中显示的通常都是widget的外貌,Android模拟器有一个应用Widget Preview能够帮助我们获取widget的外观图标。例如以下:

    通过adb pull将存贮在模拟器SD卡Download路径下的preview图片获取。作为列表显示图标。



  • 相关阅读:
    Redis 的 5 个常见使用场景
    当别人给你一个wsdl或者webservice接口时
    Java事务
    Java分布式锁的三种实现方案(redis)
    使用Redis数据库(String类型)
    超详细Redis数据库入门教程
    java对redis的基本操作(初识)
    String、StringBuffer与StringBuilder之间区别
    java正则表达式替换空格和换行符
    Linux 批量管理工具
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/5094156.html
Copyright © 2011-2022 走看看