应用小部件(App Widget)---- 基础篇(2)

文章内容


1. 实例代码分析

 

         经过《应用小部件(App Widget)---- 基础篇(1)》的介绍后,并对Android 4.0.3上的一个实例代码进行

了部分修改,然后将其作为例子进行具体地讲解。

        实例代码来源可以参考:http://developer.android.com/resources/samples/WiktionarySimple/index.html


1.  实例代码分析

        

       正如《应用小部件(App Widget)---- 基础篇(1)》中的第二部分(App Widgets的基本要素 )所述,在该

实例程序中可以很明显区分出构成部件的基本组成要素。


       1.1  应用小部件提供器信息对象(AppWidgetProviderInfo object)

      

       在工程目录res/xml/下的widget_word.xml文件中设置部件提供器信息对象的基本信息,内容如下:

 

       
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="146dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="100000" 
    android:initialLayout="@layout/widget_message" 
    android:resizeMode="horizontal"
/>


         在此,我们设置了部件的大小尺寸,更新周期,初始布局,及调整大小的模式。

        

        1.2  小部件视图布局(View layout)


        在工程目录res/layout/下的widget_word.xml文件中定义了部件,内容如下:


       
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/widget"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:focusable="true"
    style="@style/WidgetBackground">

   <TextView 
        android:id="@+id/time_update"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dip"
        style="@style/Text.Time"/>
    
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:src="@drawable/star_logo" />

    <TextView
        android:id="@+id/word_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/time_update"
        android:layout_marginBottom="1dip"
        android:includeFontPadding="false"
        android:singleLine="true"
        android:ellipsize="end"
        style="@style/Text.WordTitle" />

    <TextView
        android:id="@+id/word_type"
        android:layout_width="0dip"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/word_title"
        android:layout_toLeftOf="@id/icon"
        android:layout_alignBaseline="@id/word_title"
        android:paddingLeft="4dip"
        android:includeFontPadding="false"
        android:singleLine="true"
        android:ellipsize="end"
        style="@style/Text.WordType" />

    <TextView
        android:id="@+id/show_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/word_title"  
        android:paddingRight="4dip"
        android:includeFontPadding="false"
        android:singleLine="true"
        style="@style/BulletPoint" />
         
    <TextView
        android:id="@+id/definition"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/word_title"
        android:layout_toRightOf="@id/show_id"
        android:paddingRight="5dip"
        android:paddingBottom="4dip"
        android:includeFontPadding="false"
        android:lineSpacingMultiplier="0.9"
        android:maxLines="4"
        android:fadingEdge="vertical"
        style="@style/Text.Definition" />
   
</RelativeLayout>

        这个小部件里主要包括了显示更新时间的视图元素,标题视图元素,标题的定义视图元素,显示部件实例ID的视图元素及一个小图标视图元素。


       1.3  小部件提供器(AppWidgetProvider)类


       该类的定义如下:


        

package com.example.android.simplewiktionary;

import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.IBinder;
import android.os.SystemClock;
import android.text.format.DateFormat;
import android.text.format.Time;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;

import com.example.android.simplewiktionary.SimpleWikiHelper.ApiException;
import com.example.android.simplewiktionary.SimpleWikiHelper.ParseException;

/**
 * Define a simple widget that shows the Wiktionary "Word of the day." To build
 * an update we spawn a background {@link Service} to perform the API queries.
 */
public class WordWidget extends AppWidgetProvider 
{
	
	public final static String  MARK_WIDGET_ID = "appWidgetId";
	public final static String  ACTION_UPDATE = "android.test.appwidget.action_updated";
	public Intent intent;
	
	public  void onUpdateWidgets(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
	{
		final int N = appWidgetIds.length;
		
    	Log.w("wanjf", "Id count is :" + String.valueOf(N));
		for (int i = 0; i < N; i++) 
   	 	{
            int appWidgetId = appWidgetIds[i];
            String strId = String.format("This widget's id = %d ", appWidgetId);
            Intent intent = new Intent(context, UpdateService.class);
            appWidgetManager.getAppWidgetInfo(appWidgetId).updatePeriodMillis = 0;
            intent.putExtra(MARK_WIDGET_ID, appWidgetId);
            context.startService(intent);
            Log.w("wanjf", strId);
   	 	}
	}
	
	@Override
	public void onReceive(Context context, Intent intent)
	{
		Log.w("wanjf","onReceive is called!");
		this.intent = intent;
		
		if(ACTION_UPDATE.equals(intent.getAction()))
		{
			AppWidgetManager gm = AppWidgetManager.getInstance(context);
			ComponentName thisWidget = new ComponentName(context, WordWidget.class);
			int[] appWidgetIds  = gm.getAppWidgetIds(thisWidget);
			Log.w("wanjf", "Recieve the broadcast !!");
			Log.w("wanjf", "Widgets' count is:" + String.valueOf(appWidgetIds.length));
			onUpdateWidgets(context, gm, appWidgetIds);
		}
		else
		{
			//
			super.onReceive(context, intent);
			//
		}
	}
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) 
    {
    	 Log.w("wanjf","onUpdate is called!");
    	 onUpdateWidgets(context, appWidgetManager, appWidgetIds);
    	 super.onUpdate( context,  appWidgetManager, appWidgetIds); 
    }
    
    @Override
    public void onDeleted(Context context,int[] widgetIds)
    {
    	Log.w("wanjf","onDeleted is called!");
    	//
    	CharSequence prompt = "Oh! Widget id : " + String.valueOf(widgetIds[0]) + " id deleted.";
    	Toast.makeText(context, prompt ,Toast.LENGTH_SHORT).show();
    	//
    	context.stopService(new Intent(context, UpdateService.class));
    	// TODO Auto-generated method stub
    	super.onDisabled(context);
    }

    @Override
	public void onDisabled(Context context)
    {
    	Log.w("wanjf","onDisabled is called!");
		//
		AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
	  	PendingIntent sender = PendingIntent.getBroadcast (context, 0, new Intent(ACTION_UPDATE), 0);
		am.cancel(sender);
		//
		CharSequence prompt = "Oh! I am the last Widget";
    	Toast.makeText(context, prompt ,Toast.LENGTH_SHORT).show();	
    	// TODO Auto-generated method stub
    	super.onDisabled(context);
    	//
	}

	@Override
	public void onEnabled(Context context) 
	{
		Log.w("wanjf","onEnabled is called!");
		
		AppWidgetManager gm = AppWidgetManager.getInstance(context);
		ComponentName thisWidget = new ComponentName(context, WordWidget.class);
		int[] appWidgetIds  = gm.getAppWidgetIds(thisWidget);
		//
		PendingIntent sender = PendingIntent.getBroadcast (context, 0, new Intent(ACTION_UPDATE), 0);
	    // We want the alarm to go off 30 seconds from now.
	    long firstTime = SystemClock.elapsedRealtime();
	    firstTime += 20*1000;
	    // Schedule the alarm!
	    AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
	    am.setRepeating(AlarmManager.ELAPSED_REALTIME,firstTime, 15*1000, sender);
	    //
	    CharSequence prompt = "Oh! I am the first Widget id : " + String.valueOf(appWidgetIds[0]);
	    Toast.makeText(context, prompt ,Toast.LENGTH_SHORT).show();
		// TODO Auto-generated method stub
		super.onEnabled(context);
		//
	}

	public static class UpdateService extends Service 
	{
        @Override
        public void onStart(Intent intent, int startId) 
        {
        	Log.w("wanjf","Service's onStart() is called!");
            // Build the widget update for today
        	int appWidgetId = intent.getIntExtra(MARK_WIDGET_ID, -1);
            RemoteViews updateViews = buildUpdate(this, appWidgetId);
           
            if(appWidgetId == -1)
            	stopSelf();
            // Push update for this widget to the home screen
            //ComponentName thisWidget = new ComponentName(this, WordWidget.class);
            AppWidgetManager manager = AppWidgetManager.getInstance(this);
            //manager.updateAppWidget(thisWidget, updateViews);
            manager.updateAppWidget(appWidgetId, updateViews);
        }

        /**
         * Build a widget update to show the current Wiktionary
         * "Word of the day." Will block until the online API returns.
         */
        public RemoteViews buildUpdate(Context context, int widgetId)
        {
            // Pick out month names from resources
            Resources res = context.getResources();
            String[] monthNames = res.getStringArray(R.array.month_names);

            // Find current month and day
            Time today = new Time();
            today.setToNow();

            // Build today's page title, like "Wiktionary:Word of the day/March 21"
            String pageName = res.getString(R.string.template_wotd_title, monthNames[today.month], today.monthDay);
            //Log.w("wanjf",pageName);
            RemoteViews updateViews = null;
            String pageContent = "";

            try 
            {
                // Try querying the Wiktionary API for today's word
                SimpleWikiHelper.prepareUserAgent(context);
                pageContent = SimpleWikiHelper.getPageContent(pageName, false);
                Log.w("wanjf",pageContent);
            } 
            catch (ApiException e) 
            {
                Log.e("WordWidget", "Couldn't contact API", e);
            } 
            catch (ParseException e)
            {
                Log.e("WordWidget", "Couldn't parse API response", e);
            }

            // Use a regular expression to parse out the word and its definition
            Pattern pattern = Pattern.compile(SimpleWikiHelper.WORD_OF_DAY_REGEX);
            Matcher matcher = pattern.matcher(pageContent);
            if (matcher.find()) 
            {
            	
            	CharSequence time = DateFormat.format("hh:mm:ss", Calendar.getInstance()) ;
                // Build an update that holds the updated widget contents
                updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_word);

                String wordTitle = matcher.group(1);
                Log.w("wanjf",wordTitle);
                updateViews.setTextViewText(R.id.word_title, wordTitle);
                updateViews.setTextViewText(R.id.word_type, matcher.group(2));
                updateViews.setTextViewText(R.id.definition, matcher.group(3).trim());
                updateViews.setTextViewText(R.id.show_id, "[" + String.valueOf(widgetId) + "]");
                updateViews.setTextViewText(R.id.time_update, time);
                // When user clicks on widget, launch to Wiktionary definition page
                String definePage = res.getString(R.string.template_define_url,Uri.encode(wordTitle));
                Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(definePage));
                PendingIntent pendingIntent = PendingIntent.getActivity(context,
                        0 /* no requestCode */, defineIntent, 0 /* no flags */);
                updateViews.setOnClickPendingIntent(R.id.widget, pendingIntent);

            } 
            else
            {
                // Didn't find word of day, so show error message
                updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_message);
                CharSequence errorMessage = context.getText(R.string.widget_error);
                updateViews.setTextViewText(R.id.message, errorMessage);
            }
            return updateViews;
        }

        @Override
        public IBinder onBind(Intent intent) 
        {
            // We don't need to bind to this service
            return null;
        }
    }
}

         

       在原始代码的基础上,我们做了如下的修改:

       

                 1.  重写了基类appWidgetProvider的所有方法;

                 2.  设置警报来更新小部件;

                 3.  增加对部件进行操作的提示;

                 4.  对某些操作(警报,服务)的设置和清理做了控制;

          

          

         接下来,我们重点对上述做了修改的部分进行分析

        

       修改后的代码重写了基类的onReceiveonUpdateonDeletedonDisabledonEnabledonUpdate

方法。有了这些方法,我们便很容易发现当他们被调用时在部件上究竟发生了什么。


         首先,当我们把第一个部件实例添加到宿主视图内时,onReceive方法首先被调用,然后是onEnabled方法,接

着还是onReceive方法,最后是onUpdate方法。由于我们在onEnabled内设置了一个重复触发的警报,因此当警报

在随后被触发时,还将调用onReceive方法。

       

         提醒:由于onEnabled方法只在部件实例的首次被创建时才会被调用,因此在此方法内执行一些对所有部件实例都适用的操作是最佳场所。

         接着,当继续向宿主视图内添加一个(或多个)该部件的实例时,首先调用的是onReceive方法,然后是

onUpdate方法。

     

         然后,从宿主内删除一个该部件的实例时,首先调用的是onReceive方法,然后是onDeleted方法。

       最后,当该部件的所有实例从宿主内被移除时,首先调用的是onReceive方法,然后是onDeleted方法,接着由

onReceive方法,最后是onDisabled方法。


       提醒:onDisabled方法也只被调用一次,因此也是执行清理操作的最佳场所。此例中,在该方法内取消了警报,停止服务。

        

         由于我们重写了基类的诸方法,因此别忘记调用该方法的基类版本。尤其是在重写onReceive方法后,如果忘记

调用基类版本的onReceive,我们重写其他方法也不会被调用。除非在重写的onReceive方法对收到的广播进行过滤

后再手动调用其他方法。


       除了以上方法外,我们需要留意自定义方法onUpdateWidgets。在该方法内我们启动了一个“服务”来执行部

件的更新(主要是更细其上的显示内容)。之所以这么做主要是因为部件上的内容中有些是自于网络上的数据,而对

于网络数据的请求可能耗时几秒钟,甚至更多。在服务内执行网络数据请求可以保证WordWidget的onReceive及时

返回。否则,可能会因为ANR错误,活动管理器在后台将其结束掉(Kill)。


       此例中,“服务”内除了获取网络数据外,还对部件与用户接口上的交互进行了设置。于是当用户点击部件时,

将会打开相应的网页。


         现在我们来看一下运行后的效果:

                                    

                                     

          

         Home Screen(宿主)内的这一大一小两个部件都是我们刚刚创建的,由于在部件提供器信息对象对应的xml文

件内设置了 android:resizeMode="horizontal",因此我们可以在水平方向上调整部件的大小。除此,我们需要留意

在部件上的蓝色数字和红色数字。蓝色数字是每个部件实例最后一次更新的时间,而红色数字是该部件的实例ID。


        

        部件的实例id是由部件管理器维护的一组数字,表示每个实例对应一个不同的id,因此在更新时可以获取他们并

对其代表的部件实例进行更新。

      

       通过观察部件上时间变化可知,每个实例上的时间变化并不是一致进行的。这是因为在onUpdateWidgets方法内通过迭代id数组后,再使用指定的id对其表示的实例进行更新。如果要是所有实例上的时间同时更新,可以将onUpdateWidgets方法修改成:


 public  void onUpdateWidgets(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)  
    {  
                Intent intent = new Intent(context, UpdateService.class);  
                context.startService(intent);  
    }


       然后再将内部类UpdateService的onStart方法修改成:      


  public void onStart(Intent intent, int startId)   
    {  
              Log.w("wanjf","Service's onStart() is called!");  
               // Build the widget update for today  
               RemoteViews updateViews = buildUpdate(this, appWidgetId);  
               updateViews.setViewVisibility(R.id.show_id, View.INVISIBLE);  
               // Push update for this widget to the home screen  
               ComponentName thisWidget = new ComponentName(this, WordWidget.class);  
               manager.updateAppWidget(thisWidget, updateViews);  
    }  
 


      

       至此,创建一个简单的小部件程序就分析到这里。当然,在此实例代码里还有涉及到网络编程及正则表达式方面的内容。关于网络的那部分代码可以参考Android SDK上的介绍来理解具体接口的用法和作用。而关于正则表达的理解可以参考:http://www.java3z.com/cwbwebhome/article/article8/Regex/Java.Regex.Tutorial.html#note01


                                                                                                                                            2012年4月27日,毕



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值