android 5.0 屏保唤醒源码分析,Android Widget 源码解析——锁屏为例

本文主要从系统层怎样加载一个widget分析,不包含怎样创建一个含有widget的app。

所谓widget,梗概流程就是App开发者传给系统一个自定义的RemoteView,锁屏或者桌面把这个RemotView解析出来放在自己的界面上,以widget的形式显示出来。

文章有点长,没耐心只想看大概结构可以翻到最后看小结中的图。

widget相关类

相关的类主要有以下几个

AppWidgetHostView

AppWodgetHostView是一个View,是容纳App传来的View的容器。

由于RemoteView并不是真正的View,只是一个View的描述,所以需要通过updateAppWidget(RemoteViews views)这个方法把View创建出来,然后加到AppWidgetView里。

public void updateAppWidget(RemoteViews remoteViews) {

if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);

...//此处有省略,详见源码

// Try normal RemoteView inflation

if (content == null) {

try {

/// M: add for using customer view, migrated from GB to ICS to JB

remoteViews.setHasUsedCustomerView(mInfo.hasUsedCustomerView);

content = remoteViews.apply(mContext, this, mOnClickHandler);//重点,在这里apply"

if (LOGD) Log.d(TAG, "had to inflate new layout");

} catch (RuntimeException e) {

exception = e;

}

}

...//此处有省略,详见源码

if (!recycled) {

prepareView(content);

addView(content);

}

...//此处有省略,详见源码

}

以上代码中的mView就是要加到AppWidgetHostView中的View,remoteViews.apply创建了实际的View.

AppWidgetHost

是容纳appwidget的地方,它有以下功能:

监听来自AppWidgetService的事件,这是主要处理update和provider_changed两个事件,根据这两个事件更新widget。(此处加代码)

另外一个功能就是创建AppWidgetHostView。这里先创建AppWidgetHostView,然后通过AppWidgetService查询 appWidgetId对应的RemoteViews,最后把RemoteViews传递给AppWidgetHostView去 updateAppWidget。(此处加代码)

以上两个类是基类,用户可以根据这两个类来定制自己的widget的。

AppWidgetProvider

AppWidgetProvider,其本质BroadcastReceiver。用户用这个类去创建自己的widget,用户可以通过继承AppwidgetProvider的一个或者几个方法来定义自己的widget以及控制widget的更新

主要的function以及作用可以参照开发者文档

AppWidgetService

Widget的核心类,首先在系统启动以后在systemReady做了以下工作:

loadAppWidgetListLocked()通过PackageManager从Android系统中查找所有已经被安装的AppWidget(包含"android.appwidget.action.APPWIDGET_UPDATE" 的Action和meta-data标签),解析AppWidget的配置信息,封闭成对象,保存到List集合。

addProviderLocked 从/data/system/appwidgets.xml文件读取已经被添加到Launcher的AppWidget信息,封闭成对象,保存到List集合中。

systemRunning(boolean safeMode) 注册四个广播接收器:第一. Android系统启动完成,第二. Android配置信息改变,第三. 添加删除应用,第四. sdcard的安装与缷载。

AppWidgetService承担着所有widget的管理工作。Widget安装,删除,更新等等都需要经过AppWidgetService,它是开机就启动的.

AppWidgetManager

Updates AppWidget state; gets information about installed AppWidget providers and other AppWidget related state.

这是代码中对这个类的注释,可以看出,这个类的主要作用是更新AppWidget的状态以及得到appwidget的信息。

这个类中定义了一些很重要的intent,例如

ACTION_KEYGUARD_APPWIDGET_PICK:Setting中创建这个Activity,会列出当前系统中可以供添加到锁屏的所有widget,用户可以在列表中选择一个widget添加到锁屏上。具体实现参照Setting中的类KeyguardAppWidgetPickActivity。

ACTION_APPWIDGET_BIND:允许绑定一个widget到系统中。参见Setting中的AllowBindAppWidgetActivity

等等。

另外这个类中有一些很重要的接口,定义了widget的加载。更新等操作,如

updateAppWidget(int appWidgetId, RemoteViews views)//更新widget,在第一次加载widget的时候,加载widget

notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId)//监听widget的改变。

bindAppWidgetId(int appWidgetId, ComponentName provider) //绑定widget到系统中。需要BIND_APPWIDGET权限

接口的实现都在AppWidgetService中,使用这个类的主要是锁屏和桌面。

AppWidgetProviderInfo

每个AppWidget都有一个AppWidgetProviderInfo对象,该对象描述了每个AppWidget的基本数据(meta-data)信息 ,其定义在节点信息。

示例如下:

android:minWidth="100dp"

android:minHeight="40dp"

//设置更新时间 毫秒单位

android:updatePeriodMillis="86400000"

//引用的布局文件

android:initialLayout="@layout/widget_layout"

>

不做赘述。

存储位置:

上文可知,系统中所有的widget是通过packageManager从package里查找的(包含"android.appwidget.action.APPWIDGET_UPDATE" 的Action和meta-data标签),那么已经被添加的锁屏的widget的信息或者id存储在哪里呢?

我们再来看KeyguardAppWidgetPickActivity,在添加appwidget以后选择一个widget的时候会调到

mLockPatternUtils.addAppWidget(appWidgetId, 0);

最终调到

private void writeAppWidgets(int[] appWidgetIds) {

Settings.Secure.putStringForUser(mContentResolver,

Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS,

combineStrings(appWidgetIds, ","),

UserHandle.USER_CURRENT);

}

也就是说,我们锁屏相关的widget是通过settings存储在数据库里。(具体存储这块内容在研究)

addWidget流程(绑定一个widget并最终存储到数据库)

以KeyguardHostView为例,添加一个View到数据库的流程

在Keyguard中,添加一个widget是通过start一个Settings中的Activity来实现的,上文已经提及KeyguardAppWidgetPickActivity。

当用户点击添加widget的button的时候,会弹出Activity:KeyguardAppWidgetPickActivity,然后Activity会通过PackageManager得到所有的widget并显示出来。

我们需要关注的是,当某个widget被点击以后发生的事情。

@Override

public void onItemClick(AdapterView> parent, View view, int position, long id) {

Item item = mItems.get(position);

Intent intent = item.getIntent();

int result;

if (item.extras != null) {

// If these extras are present it's because this entry is custom.

// Don't try to bind it, just pass it back to the app.

result = RESULT_OK;

setResultData(result, intent);

} else {

try {

if (mAddingToKeyguard && mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {

// Found in KeyguardHostView.java

final int KEYGUARD_HOST_ID = 0x4B455947;

int userId = ActivityManager.getCurrentUser();

* mAppWidgetId = AppWidgetHost.allocateAppWidgetIdForPackage(KEYGUARD_HOST_ID,

userId, "com.android.keyguard");//重点:关键步骤1

}

*mAppWidgetManager.bindAppWidgetId(

mAppWidgetId, intent.getComponent(), mExtraConfigureOptions);//重点:关键步骤2

} catch (IllegalArgumentException e) {

// This is thrown if they're already bound, or otherwise somehow

// bogus. Set the result to canceled, and exit. The app *should*

// clean up at this point. We could pass the error along, but

// it's not clear that that's useful -- the widget will simply not

// appear.

result = RESULT_CANCELED;

}

setResultData(result, null);

}

if (mAddingToKeyguard) {

onActivityResult(REQUEST_PICK_APPWIDGET, result, mResultData);//重点

} else {

finish();

}

}

allocateAppWidgetIdForPackage最终会调用到AppWidgetServiceImpl的allocateAppWidgetId。

public int allocateAppWidgetId(String packageName, int hostId) {

int callingUid = enforceSystemOrCallingUid(packageName);

Slog.d(TAG, "allocateAppWidgetId uid"+callingUid+" packageName "+packageName);

synchronized (mAppWidgetIds) {

if (!mHasFeature) {

return -1;

}

ensureStateLoadedLocked();

int appWidgetId = mNextAppWidgetId++;//1.创建一个id

Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);

AppWidgetId id = new AppWidgetId();

id.appWidgetId = appWidgetId;

id.host = host;

host.instances.add(id);

mAppWidgetIds.add(id);2.//添加这个id

saveStateAsync();

if (DBG) log("Allocating AppWidgetId for " + packageName + " host=" + hostId

+ " id=" + appWidgetId);

return appWidgetId;//3.返回创建的id

}

}

然后就给这个widget分配了唯一的id。

2.bindAppWidgetId最终调用到AppWidgetServiceImpl的bindAppWidgetIdImp

private void bindAppWidgetIdImpl(int appWidgetId, ComponentName provider, Bundle options) {

if (DBG) log("bindAppWidgetIdImpl appwid=" + appWidgetId

+ " provider=" + provider);

final long ident = Binder.clearCallingIdentity();

try {

synchronized (mAppWidgetIds) {

...//此处有省略,详见源码

id.provider = p;

if (options == null) {

options = new Bundle();

}

id.options = options;

// We need to provide a default value for the widget category if it is not specified

if (!options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {

options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,

AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);

}

p.instances.add(id);

int instancesSize = p.instances.size();

if (instancesSize == 1) {

// tell the provider that it's ready

sendEnableIntentLocked(p);

}

// send an update now -- We need this update now, and just for this appWidgetId.

// It's less critical when the next one happens, so when we schedule the next one,

// we add updatePeriodMillis to its start time. That time will have some slop,

// but that's okay.

sendUpdateIntentLocked(p, new int[] { appWidgetId });

// schedule the future updates

registerForBroadcastsLocked(p, getAppWidgetIds(p));

saveStateAsync();

}

} finally {

Binder.restoreCallingIdentity(ident);

}

}

这个方法主要就是把刚刚得到的id与这个widget的信息绑定在一起

注意,当id与widget绑定以后需要调用

sendUpdateIntentLocked(p, new int[] { appWidgetId })

来更新这个widget的内容,

void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) {

log("sendUpdateIntentLocked");

if (appWidgetIds != null && appWidgetIds.length > 0) {

Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);

///M: added by mtk for debugging @{

Exception e = new Exception();

e.printStackTrace();

/// @}

intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

intent.setComponent(p.info.provider);

mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));

}

}

这个方法主要是发送一个广播,widget的开发者接收到广播以后来更新这个widget的内容。发广播的时间是比widget添加到界面上的时间稍微提前,不过开发者可以在接收到广播以后再更新,这个时间差并不会影响widget接收广播。

3.当获取了id并且与widget绑定以后,再接着看onItemClick,

会调用到setResultData(int code, Intent intent),这个方法里有这两句

mLockPatternUtils.addAppWidget(appWidgetId, 0);//前文已经提到这个方法的作用是把id添加到数据库里。

finishDelayedAndShowLockScreen(appWidgetId);//重启锁屏

至此,把一个widget的加到数据库的流程已经理清,主要有以下几点:

mAppWidgetId = AppWidgetHost.allocateAppWidgetIdForPackage//申请id

mAppWidgetManager.bindAppWidgetId//绑定id

mLockPatternUtils.addAppWidget(appWidgetId, 0);//添加到数据库

getWidget流程(数据库中的得到widget列表并显示在锁屏)

同样以KeyguardHostView为起点。

上文可知,在添加完widget以后会重启锁屏,这个时候会走到KeyguardHostView的onFinishInflate() ——> updateAndAddWidgets();——>addWidgetsFromSettings()

private void addWidgetsFromSettings() {

if (mSafeModeEnabled || widgetsDisabled()) {

addDefaultStatusWidget(0);//添加一些默认的widget

return;

}

int insertionIndex = getInsertPageIndex();

// Add user-selected widget

final int[] widgets = mLockPatternUtils.getAppWidgets();

if (widgets == null) {

Log.d(TAG, "Problem reading widgets");

} else {

for (int i = widgets.length -1; i >= 0; i--) {

if (widgets[i] == LockPatternUtils.ID_DEFAULT_STATUS_WIDGET) {

addDefaultStatusWidget(insertionIndex);

} else {

// We add the widgets from left to right, starting after the first page after

// the add page. We count down, since the order will be persisted from right

// to left, starting after camera.

addWidget(widgets[i], insertionIndex, true);

}

}

}

}

由前文可知,添加widget到数据库的操作是LockPatternUtils.addAppWidget(int widgetId, int index);

同理,取出数据库中锁屏的widgetid的方法是getAppWidgets

LockPatternUtils.java

private int[] getAppWidgets(int userId) {

String appWidgetIdString = Settings.Secure.getStringForUser(

mContentResolver, Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS, userId);

String delims = ",";

if (appWidgetIdString != null && appWidgetIdString.length() > 0) {

String[] appWidgetStringIds = appWidgetIdString.split(delims);

int[] appWidgetIds = new int[appWidgetStringIds.length];

for (int i = 0; i < appWidgetStringIds.length; i++) {

String appWidget = appWidgetStringIds[i];

try {

appWidgetIds[i] = Integer.decode(appWidget);

} catch (NumberFormatException e) {

Log.d(TAG, "Error when parsing widget id " + appWidget);

return null;

}

}

return appWidgetIds;

}

return new int[0];

}

以上,取出了已经添加到锁屏上的widget的列表,继续看KeyguardHostView的addWidget(widgets[i], insertionIndex, true);

private boolean addWidget(int appId, int pageIndex, boolean updateDbIfFailed) {

AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appId);//通过取到widget对应的AppWidgetProviderInfo

if (appWidgetInfo != null) {

AppWidgetHostView view = mAppWidgetHost.createView(mContext, appId, appWidgetInfo);通过调用AppWidgetHost的CreateView方法将remoteview apply为view再添加到AppWidgetHostView中

addWidget(view, pageIndex);

return true;

} else {

if (updateDbIfFailed) {//widget信息为空或无效,删除id

Log.w(TAG, "*** AppWidgetInfo for app widget id " + appId + " was null for user"

+ mUserId + ", deleting");

mAppWidgetHost.deleteAppWidgetId(appId);

mLockPatternUtils.removeAppWidget(appId);

}

return false;

}

}

AppWidgetHost是通过AppWidgetHostView的view.updateAppWidget(views);方法来获取view的

public final AppWidgetHostView createView(Context context, int appWidgetId,

AppWidgetProviderInfo appWidget) {

Log.d(TAG, "createView appWidgetId "+appWidgetId);

final int userId = mContext.getUserId();

AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget);

view.setUserId(userId);

view.setOnClickHandler(mOnClickHandler);

view.setAppWidget(appWidgetId, appWidget);

synchronized (mViews) {

Log.d(TAG, "createView mViews put "+this);

mViews.put(appWidgetId, view);

}

RemoteViews views;

try {

views = sService.getAppWidgetViews(appWidgetId, userId);

if (views != null) {

views.setUser(new UserHandle(mContext.getUserId()));

}

} catch (RemoteException e) {

throw new RuntimeException("system server dead?", e);

}

view.updateAppWidget(views);

return view;

}

具体请注意以下两句

public void updateAppWidget(RemoteViews remoteViews) {

...//省略,详细请查看源码

// Try normal RemoteView inflation

if (content == null) {

try {

/// M: add for using customer view, migrated from GB to ICS to JB

remoteViews.setHasUsedCustomerView(mInfo.hasUsedCustomerView);

content = remoteViews.apply(mContext, this, mOnClickHandler);

if (LOGD) Log.d(TAG, "had to inflate new layout");

} catch (RuntimeException e) {

exception = e;

}

}

...//省略,详细请查看源码

if (mView != content) {

removeView(mView);

mView = content;//mView即AppWidgetHostView中添加的View

}

...//省略,详细请查看源码

}

经过以上步骤,则remoteView被通过id得到且创建为View放进AppWidgetHostView中。

继续看KeyguardHostView的addWidget(view, pageIndex);

会调用到mAppWidgetContainer.addWidget(view, pageIndex),mAppWidgetContainer是一个KeyguardWidgetPager的实例,我们可以随便定义一个父View,不一定要放在KeyguardWidgetPager里。

KeyguardWidgetPager.java

public void addWidget(View widget, int pageIndex) {

KeyguardWidgetFrame frame;

...//省略,详细请看源码

frame.addView(widget, lp);

...//省略,详细请看源码

if (pageIndex == -1) {

addView(frame, pageLp);

} else {

addView(frame, pageIndex, pageLp);

}

...//省略,详细请看源码

}

这里只是添加上,还不算完成,

注意,在KeyguardHostView中的onAttachedToWindow中还有这么一句

if (!KeyguardViewMediator.isKeyguardInActivity) {

mAppWidgetHost.startListening();

}

startListening一定要写,widget才会被定时更新。

至此,我们的widget被成功添加到锁屏的界面。

主要流程总结如下:

widgets = mLockPatternUtils.getAppWidgets();//从数据库中读取

appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appId);//通过id取到widget对应的AppWidgetProviderInfo

AppWidgetHostView view = mAppWidgetHost.createView(mContext, appId, appWidgetInfo);通过调用AppWidgetHost的CreateView方法将remoteview apply为view再添加到AppWidgetHostView中

addWidget(view, pageIndex);//将AppWidgetHostView添加到我们的父View中

注意在view加载完成以后mAppWidgetHost.startListening().

updatewidget流程(widget的更新)

上文我们提到两个地方,有关widget的更新

在bindAppWidgetId完成以后,调用 sendUpdateIntentLocked(p, new int[] { appWidgetId });发送广播来更新,这个流程比较清晰,不赘述。

在view加载以后mAppWidgetHost.startListening();来保持View一直会更新。

AppWidgetHost的startListening会调用到AppWidgetService的startListening,AppWidgetService的startListening会返回一个需要更新的widgetid的数组。AppWidgetHost会在得到这个数组以后更新一次这个Host上的所有widget。

当然,要保持widget持续刷新,更新一次肯定是不够的,所有我们继续看

第一小步

AppWidgetService:

public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,

List updatedViews) {

Slog.d(TAG, "startListening hostId " + hostId);

。。。//省略

Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);

。。。//省略

return updatedIds;

}

}

再来看lookupOrAddHostLocked里做了如下操作

Host lookupOrAddHostLocked(int uid, String packageName, int hostId) {

final int N = mHosts.size();

for (int i = 0; i < N; i++) {

Host h = mHosts.get(i);

if (h.hostId == hostId && h.packageName.equals(packageName)) {

return h;

}

}

Host host = new Host();

host.packageName = packageName;

Slog.d(TAG, "lookupOrAddHostLocked h.uid"+uid);

host.uid = uid;

host.hostId = hostId;

mHosts.add(host);//重点

return host;

}

这里是把需要更新widget的host加入到AppWidgetService的mHost列表里。

第二小步

由于添加widget导致android配置信息改变会进入AppwidgetService的onConfiguartionchanged,这个方法中做了如下操作

void onConfigurationChanged() {

if (DBG) log("Got onConfigurationChanged()");

Locale revised = Locale.getDefault();

if (revised == null || mLocale == null || !(revised.equals(mLocale))) {

mLocale = revised;

synchronized (mAppWidgetIds) {

ensureStateLoadedLocked();

// Note: updateProvidersForPackageLocked() may remove providers, so we must copy the

// list of installed providers and skip providers that we don't need to update.

// Also note that remove the provider does not clear the Provider component data.

ArrayList installedProviders =

new ArrayList(mInstalledProviders);

HashSet removedProviders = new HashSet();

int N = installedProviders.size();

for (int i = N - 1; i >= 0; i--) {

Provider p = installedProviders.get(i);

ComponentName cn = p.info.provider;

if (!removedProviders.contains(cn)) {

updateProvidersForPackageLocked(cn.getPackageName(), removedProviders);//重点1

}

}

saveStateAsync();//重点2

}

}

}

继续看源码(这里不再粘贴)会发现

updateProvidersForPackageLocked(cn.getPackageName(), removedProviders)

做了一个操作就是根据配置的更新间隔定时发出更新广播。

updateProvidersForPackageLocked——>getAppWidgetIds——>sendUpdateIntentLocked(p, appWidgetIds);

saveStateAsync();会重新更新需要更新的widget的列表的xml。

saveStateAsync()——>writeStateToFileLocked——>parseProviderInfoXml(component, ri);

saveStateAsync就把上文startListening更新的mHost更新到xml里,下次定时更新widet的时候就遍历到这里然后把对应的widget进行更新.

至此,widget的更新流程算是走完了,主要有一下两个

onconfiguarationchange的时候定时读取xml——>向app发送更希widget的广播——>更新xml,写入需要update的widgetHost的列表。

2.startListening的时候把当前Host的Id通知给AppWidgetService保存在mHost列表里等待下次写进xml。

小结:

所以添加一个插件的核心流程为

1d1bec386d74

流程1.png

每个部分的大致流程如下:

添加widget流程

1d1bec386d74

流程2.png

showwidget流程

1d1bec386d74

流程3.png

更新widget流程

1d1bec386d74

流程4.png

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值