2 AppWidget Framework
2.1 定义
(1)AppWidgetService是框架的的核心类,是系统 service之一,它负责widgets的管理工作。加载,删除,定时事件等都需要AppWidgetService的处理。开机自启动的。
(2)AppWidgetManager 负责widget视图的实际更新以及相关管理。
2.2 AppWidget Framework的大致流程
1. 编写一个widget(先不考虑后台服务以及用户管理界面等)
实际是写一个事件监听类即一个BroadcastReceiver子类,当然框架已经提供了一个辅助类AppWidgetProvider,实现的类只要实现其方法即可,其中必须实现的方法是onUpdate ,其实就是一个定时事件,widget监听此事件
另外就是规划好视图(layout),将此widget打包安装。
2. 当android系统启动时,AppWidgetService 就将负责检查所有的安装包,将检查
AndroidManifest.xml(不要告诉我不知道,如果不知道可要看看基本开发知识了)
文件中有<metadata android:name="android.appwidget.provider" android:resource="@xml/appwidget_info" />
3. 从用户菜单将已经安装的widget添加到桌面,也就是将widget在桌面上显示出来,这个应该是由AppWidgetService和 AppWidgetManager完成的,其中AppWidgetManager 将负责将视图发送到桌面显示出来,并将此widget记录到系统文件中
4. AppWidgetService将根据widget配置中的updatePeriodMillis属性来定时发送 ACTION_APPWIDGET_UPDATE事件,此事件将激活widget的事件监听方法onUpdate,此方法将通过 AppWidgetManager完成widget内容的更新和其他操作。
3 AppWidgetHost
AppWidgetHost 是实际控制widget的地方,大家注意,widget不是一个单独的用户界面程序,他必须寄生在某个程序(activity)中,这样如果程序要支持 widget寄生就要实现AppWidgetHost,桌面程序(Launcher)就实现了这个接口。
o 监听来自AppWidgetService的事件。
o 另外一个功能就是创建AppWidgetHostView。
Host的实现者
LauncherAppWidgetHostVie w扩展了AppWidgetHostView,实现了对长按事件的处理。
LauncherAppWidgetHost扩展了AppWidgetHost,这里只是重载了onCreateView,创建LauncherAppWidgetHostVie w的实例。
4 AppWidgetHostView
5 AppWidgetProvider
1
public
void
onUpdate(Context context, AppWidgetManager appWidgetManager,
int [] appWidgetIds)
2
public
void
onDeleted(Context context,
int [] appWidgetIds)
3
public
void
onEnabled(Context context)
4
public
void
onDisabled(Context context)
这几个函数用来响应AppWidgetService发出的相应的广播消息。
AppWidgetProvider的实现者
1
void
onUpdate(Context context, AppWidgetManager appWidgetManager,
int
onUpdate的实现基本上遵循下面的流程:
o 创建RemoteViews
o 调用AppWidgetManager的updateAppWidget去更新widget.
6 RemoteViews
RemoteViews并不是一个真正的View,它没有实现View的接口,而只是一个用于描述View的实体。
现在我们可以看出,Android中的AppWidget与google widget和中移动的widget并不是一个概念, 这里的AppWidget只是把一个进程的控件嵌入到别外一个进程的窗口里的一种方法。
组成部分
1 AppWidgetProviderInfo
描述一个App Widget,如App Widget的布局, 更新频率
一个App Widget的基本属性是通过AppWidgetProviderInfo去定义的,例如它的最小尺寸的布局,它的初始layout,多久更新App Widget appwidget_provider.xml文件:
<?xml version="1.0" encoding="utf-8"
<!-- Copyright (C) 2006 The Android
Licensed under the Apache
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="60dp"
android:minHeight="30dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/appwidget_provider"
android:configure="com.example.android.apis.appwidget.ExampleAppWidgetConfigure"
>
</appwidget-provider
<!-- 86400000 is the value of AlarmManager.INTERVAL_DAY - or once per day. -->
configure 属性是当用户添加一个App Widget前启动的Activity,这个Activity的作用就是配置App Widget的属性。(Optional)
updatePeriodMillis 属性定义了App Widget应该多久向AppWidgetProvider请求更新。实际更新的时间未必能保证及时更新,并且建议尽量不要频繁更新---比如一小时一次去更新电量。也可以容许用户去自定义更新的时间。
重大消息!!! SDK1.5之后此方法android:updatePeriodMillis就失效了
2 AppWidgetProvider
AppWidgetProvider是BroadcastReceiver的子类,这个类处理App Widget广播。AppWidgetProvider只接收于App Widget有关系的广播,比如App Widget在updated, deleted, enabled, and disabled。当这些广播发生的时候,AppWidgetProvider会调用一下回调方法:
onUpdate(Context, AppWidgetManager, int[])
间隔调用此方法去更新App Widget,间隔时间的设置是在AppWidgetProviderInfo下的updatePeriodMillis属性,同样当用户添加App Widget的时候也被调用。如果你已经声明了一个configuration Activity,用户添加App Widget的时候就不会调用onUpdate,但是在随后的更新中依然会被调用。
onDeleted(Context, int[])
当App Widget 从App Widget host 中删除的时候调用。
onEnabled(Context)
当App Widget第一次创建的时候调用。比如,当用户增加两个同样的App Widget时候,这个方法只在第一次去调用。如果你需要打开一个新的数据库或者其他的设置,而这在所有的App Widgets只需要设置一次的情况下,这个是最好的地方去实现它们。
onDisabled(Context)
当App Widget的最后一个实例从App Widget host中被删除的时候调用。这里可以做一些在onEnabled(Context)中相反的操作,比如删除临时数据库。
onReceive(Context, Intent)
每一个广播的产生都会调用此方法,而且是在上面方法之前被调用。通常不需要实现此方法。
ApiDemo中的例子,ExampleAppWidgetProvider.java文件
package com.example.android.apis.appwidget;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.SystemClock;
import android.util.Log;
import android.widget.RemoteViews;
// Need the following import to get access to the app resources, since this
// class is in a sub-package.
import com.example.android.apis.R;
public class ExampleAppWidgetProviderextends AppWidgetProvider {
// log tag
private static final String TAG = "ExampleAppWidgetProvider" ;
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int [] appWidgetIds) {
Log.d(TAG, "onUpdate" );
// For each widget that needs an update, get the text that we should display:
// - Create a RemoteViews object for it
// - Set the text in the RemoteViews object
// - Tell the AppWidgetManager to show that views object for the widget.
final int N = appWidgetIds.length;
for (int i= 0 ; i< N; i++ ) {
int appWidgetId = appWidgetIds[i];
String titlePrefix = ExampleAppWidgetConfigure.loadTitlePref(context, appWidgetId);
updateAppWidget(context, appWidgetManager, appWidgetId, titlePrefix);
}
}
@Override
public void onDeleted(Context context, int [] appWidgetIds) {
Log.d(TAG, "onDeleted" );
// When the user deletes the widget, delete the preference associated with it.
final int N = appWidgetIds.length;
for (int i= 0 ; i< N; i++ ) {
ExampleAppWidgetConfigure.deleteTitlePref(context, appWidgetIds[i]);
}
}
@Override
public void onEnabled(Context context) {
Log.d(TAG, "onEnabled" );
// When the first widget is created, register for the TIMEZONE_CHANGED and TIME_CHANGED
// broadcasts. We don't want to be listening for these if nobody has our widget active.
// This setting is sticky across reboots, but that doesn't matter, because this will
// be called after boot if there is a widget instance for this provider.
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(
new ComponentName("com.example.android.apis" , ".appwidget.ExampleBroadcastReceiver" ),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
}
@Override
public void onDisabled(Context context) {
// When the first widget is created, stop listening for the TIMEZONE_CHANGED and
// TIME_CHANGED broadcasts.
Log.d(TAG, "onDisabled" );
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(
new ComponentName("com.example.android.apis" , ".appwidget.ExampleBroadcastReceiver" ),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
}
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, String titlePrefix) {
Log.d(TAG, "updateAppWidget appWidgetId=" + appWidgetId + " titlePrefix=" + titlePrefix);
// Getting the string this way allows the string to be localized. The format
// string is filled in using java.util.Formatter-style format strings.
CharSequence text = context.getString(R.string.appwidget_text_format,
ExampleAppWidgetConfigure.loadTitlePref(context, appWidgetId),
"0x" + Long.toHexString(SystemClock.elapsedRealtime()));
// Construct the RemoteViews object. It takes the package name (in our case, it's our
// package, but it needs this because on the other side it's the widget host inflating
// the layout from our package).
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider);
views.setTextViewText(R.id.appwidget_text, text);
// Tell the widget manager
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
public class ExampleAppWidgetProvider extends AppWidgetProvider {
public void onUpdate ( Context context , AppWidgetManager appWidgetManager , int [] appWidgetIds ) {
final int N = appWidgetIds . length ;
// Perform this loop procedure for each App Widget that belongs to this provider
for ( int i = 0 ; i < N ; i ++) {
int appWidgetId = appWidgetIds [ i ];
// Create an Intent to launch ExampleActivity
Intent intent = new Intent ( context , ExampleActivity . class );
PendingIntent pendingIntent = PendingIntent . getActivity ( context , 0 , intent , 0 );
// Get the layout for the App Widget and attach an on-click listener to the button
RemoteViews views = new RemoteViews ( context . getPackageName (), R . layout . appwidget_provider_layout );
views . setOnClickPendingIntent ( R . id . button , pendingIntent );
// Tell the AppWidgetManager to perform an update on the current App Widget
appWidgetManager . updateAppWidget ( appWidgetId , views );
}
}
}
3 View layout
只要你熟悉用 xml 怎么去定义 layout 的话,为 App Widget 定义一个 layout 还是很简单的。但是由于App Widget 的布局是基于 RemoteViews ,所以只能使用 RemoteViews 所支持的 layout 或者 view
RemoteViews 支持的 layout 和 view
Layout – FrameLayout 、LinearLayout、 RelativeLayou
View -- Analog、Clock、 Button、 Chronometer 、ImageButton、 ImageView、 ProgressBar 、TextView
注意:继承这些类的子类同样 不支持。
ApiDemo例子,appwidget_provider.xml文件。
<?xml version="1.0" encoding="utf-8"
<!-- Copyright (C) 2006 The Android
Licensed under the Apache
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/appwidget_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ff000000"
/>
4 App Widget Configuration Activity
这个 Activity 将通过 App Widget 自动启动,用户可以给 App Widget 设置有用的参数,比如 App Widget
在 AndroidManifes.xml 中定义这个 Activity 和一般定义 Activity 基本没有区别, App Widget host 启动这个 Activity 需要一个 Action
<activity android:name = ".ExampleAppWidgetConfigure" >
<intent-filter>
<action android:name = "android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</ activity >
同样这个 Activity 必须在 AppWidgetProviderInfo 文件中定义 android:configure
值得注意的是 App Widget host 调用 configuration Activity , configuration Activity 必须要返回一个结果 ( 必须包含 App Widget ID ) saved in the Intent extras as EXTRA_APPWIDGET_ID。
ApiDemo例子,ExampleAppWidgetConfigure.java文件:
package com.example.android.apis.appwidget;
import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import java.util.ArrayList;
// Need the following import to get access to the app resources, since this
// class is in a sub-package.
import com.example.android.apis.R;
public
class ExampleAppWidgetConfigur
e
extends Activity {
static
final String TAG
=
"ExampleAppWidgetConfigure" ;
private
static
final String PREFS_NAME
=
"com.example.android.apis.appwidget.ExampleAppWidgetProvider" ;
private
static
final String PREF_PREFIX_KEY
=
"prefix_" ;
int mAppWidgetId
= AppWidgetManager.INVALID_APPWIDGET_ID;
EditText mAppWidgetPrefix;
public ExampleAppWidgetConfigur
e() {
super ();
}
@Override
public
void onCreate(Bundle icicle) {
super .onCreate(icicle);
// Set the result to CANCELED. This will cause the widget host to cancel
// out of the widget placement if they press the back button.
setResult(RESULT_CANCELED);
// Set the view layout resource to use.
setContentView(R.layout.appwidget_configure);
// Find the EditText
mAppWidgetPrefix
= (EditText)findViewById(R.id.appwidget_prefix);
// Bind the action for the save button.
findViewById(R.id.save_button).setOnClickListener(mOnClickListener);
// Find the widget id from the intent.
Intent intent
= getIntent();
Bundle extras
= intent.getExtras();
if (extras
!= null) {
mAppWidgetId
= extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
}
// If they gave us an intent without the widget id, just bail.
if (mAppWidgetId
== AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
}
mAppWidgetPrefix.setText(loadTitlePref(ExampleAppWidgetConfigur
e.
this , mAppWidgetId));
}
View.OnClickListener mOnClickListener
=
new View.OnClickListener() {
public
void onClick(View v) {
final Context context
= ExampleAppWidgetConfigur
e.
this ;
// When the button is clicked, save the string in our prefs and return that they
// clicked OK.
String titlePrefix
= mAppWidgetPrefix.getText().toString();
saveTitlePref(context, mAppWidgetId, titlePrefix);
// Push widget update to surface with newly set prefix
AppWidgetManager appWidgetManager
= AppWidgetManager.getInstance(context);
ExampleAppWidgetProvider
.updateAppWidget(context, appWidgetManager,
mAppWidgetId, titlePrefix);
// Make sure we pass back the original appWidgetId
Intent resultValue
=
new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
}
};
// Write the prefix to the SharedPreferences object for this widget
static
void saveTitlePref(Context context,
int appWidgetId, String text) {
SharedPreferences.Editor prefs
= context.getSharedPreferences(PREFS_NAME,
0 ).edit();
prefs.putString(PREF_PREFIX_KEY
+ appWidgetId, text);
prefs.commit();
}
// Read the prefix from the SharedPreferences object for this widget.
// If there is no preference saved, get the default from a resource
static String loadTitlePref(Context context,
int appWidgetId) {
SharedPreferences prefs
= context.getSharedPreferences(PREFS_NAME,
0 );
String prefix
= prefs.getString(PREF_PREFIX_KEY
+ appWidgetId, null);
if (prefix
!= null) {
return prefix;
}
else {
return context.getString(R.string.appwidget_prefix_default);
}
}
static
void deleteTitlePref(Context context,
int appWidgetId) {
}
static
void loadAllTitlePrefs(Context context, ArrayList
< Integer
> appWidgetIds,
ArrayList
< String
> texts) {
}
}
标签:widget,Context,App,Widget,context,Android,Frame,android From: https://blog.51cto.com/u_548275/6885687