文/arjinmc
本文是基于官方demo来分析Autofill Framework的用法(要正常打开这个项目请使用Android Studio Preview 3.0以上版本,并下载Anroid O模拟器镜像)。Autofill Framework最低支持SDK API 26(Android O)+。
在手机中管理autofill服务:
设置->系统->语言与输入->高级->自动填写服务->选择自己想要的服务,点击它旁边的设置按钮可以进入这个autofill的设置界面(如果有给此服务设定了设置界面)
也就是:
setting->system->languages & input -> advanced -> autofill services
通过属性autofillHints在标记需要记录的节点,也就是key-value的形式的key。
例如:标记记录密码框key为password
<EditText
android:id="@+id/passwordField"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:autofillHints="password"
android:inputType="textPassword" />
在代码中可以通过view.setAutofillHints(String… autofillHints) 方法来设置这些标记。
autofillHints的值都是View类(api 26版本)中定义的一些String类型常量,目前有13种,分别是:
view.setImportantForAutofill(int mode)
设置autofill的重要级别也是一些常量,对应xml的标签是android:importantForAutofill,有5种模式:
重写两个方法:
List context = request.getFillContexts();
//保存步骤:
//1.得到最近一条需要填写的表单(表单的所有内容)
AssistStructure structure = context.get(context.size() - 1).getStructure();
//2.解析记录的数据AssistStructure
//3.通过SharedPreferences,数据库,文件等存储方式保存下来
//自动填写步骤:
//1.得到最近一条需要填写的表单(表单的所有内容)
AssistStructure structure = request.getFillContexts()
.get(request.getFillContexts().size() - 1).getStructure();
//2.获取保存的自动填写的表单的结果集Dataset放在FillResponse上
//3.通过FillCallback把FillResponse的内容展示到界面交互
在manifest中,声明AutofillService
<service
-- 自己定义的autofillservie类名 -->
android:name=".multidatasetservice.MyAutofillService"
android:permission="android.permission.BIND_AUTOFILL"
android:label="Multi-Dataset Autofill Service">
<meta-data
android:name="android.autofill"
android:resource="@xml/multidataset_service" />
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
intent-filter>
service>
//从AssistStructure获取view的节点
private void parse(){}
int nodes = mStructure.getWindowNodeCount();
for (int i = 0; i < nodes; i++) {
//得到每一个view节点
WindowNode node = mStructure.getWindowNodeAt(i);
ViewNode view = node.getRootViewNode();
parseLocked(view);
}
}
//遍历保存具有autofillHints属性的view节点
private void parseLocked(ViewNode viewNode) {
if (viewNode.getAutofillHints() != null
&& viewNode.getAutofillHints().length > 0) {
//用一个map来保存这些有标记autofillHints属性的view节点,key-values的形式
//key:AutofillHints - String[]
//values:AutofillValue - viewNode的内容值,也就是数据内容
//但是AutofillValue不能直接使用,需要封装一个对象来存储它的内容
//在这里是FilledAutofillField
mFilledAutofillFieldCollection.setAutofillValuesForHints
(viewNode.getAutofillHints(), new FilledAutofillField(viewNode);
}
int childrenSize = viewNode.getChildCount();
if (childrenSize > 0) {
for (int i = 0; i < childrenSize; i++) {
parseLocked(viewNode.getChildAt(i));
}
}
}
AutofillValue就是实际记录了要自动填写的内容,看源码可见,它区分了几种类型:
AutofillValue.java
public final class AutofillValue implements Parcelable {
public static final Creator CREATOR = null;
AutofillValue() {
throw new RuntimeException("Stub!");
}
public CharSequence getTextValue() {
throw new RuntimeException("Stub!");
}
public boolean isText() {
throw new RuntimeException("Stub!");
}
public boolean getToggleValue() {
throw new RuntimeException("Stub!");
}
public boolean isToggle() {
throw new RuntimeException("Stub!");
}
public int getListValue() {
throw new RuntimeException("Stub!");
}
public boolean isList() {
throw new RuntimeException("Stub!");
}
public long getDateValue() {
throw new RuntimeException("Stub!");
}
public boolean isDate() {
throw new RuntimeException("Stub!");
}
public int hashCode() {
throw new RuntimeException("Stub!");
}
public boolean equals(Object obj) {
throw new RuntimeException("Stub!");
}
public String toString() {
throw new RuntimeException("Stub!");
}
public int describeContents() {
throw new RuntimeException("Stub!");
}
public void writeToParcel(Parcel parcel, int flags) {
throw new RuntimeException("Stub!");
}
public static AutofillValue forText(CharSequence value) {
throw new RuntimeException("Stub!");
}
public static AutofillValue forToggle(boolean value) {
throw new RuntimeException("Stub!");
}
public static AutofillValue forList(int value) {
throw new RuntimeException("Stub!");
}
public static AutofillValue forDate(long value) {
throw new RuntimeException("Stub!");
}
}
为了方便区分这些类型,官网给的demo对这些类型再做了一层封装,详情见com.example.android.autofillframework.multidatasetservice.model.FilledAutofillField类,判断AutofillValue的内容类型并获取它的值value,得到的是string,boolean,long类型的值。
//从AssistStructure获取表单的所有view节点
private void parse() {
int nodes = mStructure.getWindowNodeCount();
for (int i = 0; i < nodes; i++) {
WindowNode node = mStructure.getWindowNodeAt(i);
ViewNode view = node.getRootViewNode();
parseLocked(view);
}
}
//遍历保存具有autofillHints属性的view节点,并得到view节点的属性值
private void parseLocked(ViewNode viewNode) {
if (viewNode.getAutofillHints() != null && viewNode.getAutofillHints().length > 0) {
//用一个map来记录,表单的信息,key-values的形式
//key:AutofillHints - String[]
//values:viewNode - viewNode自己 注意这里跟保存模块不一样
//下面这个代码是官网的demo,mAutofillFields是一个转载器
//AutofillFieldMetadata封装了viewNode一些必要的属性:
//autofillHints和autofillType,autofillId等,但不包括viewNode本身
//实际用途如上述key-value模式。
mAutofillFields.add(new AutofillFieldMetadata(viewNode));
}
int childrenSize = viewNode.getChildCount();
if (childrenSize > 0) {
for (int i = 0; i < childrenSize; i++) {
parseLocked(viewNode.getChildAt(i));
}
}
}
demo中用的是textview。
xml
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffffff"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:textAppearance="?android:attr/textAppearanceListItemSmall" />
//给这个布局item显示一个名字remoteViewsText
public static RemoteViews newRemoteViews(String packageName, String remoteViewsText) {
RemoteViews presentation = new RemoteViews(packageName, R.layout.multidataset_service_list_item);
presentation.setTextViewText(R.id.text1, remoteViewsText);
return presentation;
}
把定义的RemoteView作为Dataset的布局。
Dataset.Builder datasetBuilder = new Dataset.Builder
(newRemoteViews(context.getPackageName(), datasetName));
从存储的SharedPreferences,数据库,文件等存储方式中取出需要自动填入的数据,然后按照autofillHints的标记,分别Dataset传递数据,也就是进行赋值:
datasetBuilder.setValue(autofillHints的标记, 数据内容);
public boolean applyToFields(AutofillFieldMetadataCollection autofillFieldMetadataCollection,
Dataset.Builder datasetBuilder) {
boolean setValueAtLeastOnce = false;
List allHints = autofillFieldMetadataCollection.getAllHints();
for (int hintIndex = 0; hintIndex < allHints.size(); hintIndex++) {
String hint = allHints.get(hintIndex);
List fillableAutofillFields = autofillFieldMetadataCollection.getFieldsForHint(hint);
if (fillableAutofillFields == null) {
continue;
}
for (int autofillFieldIndex = 0; autofillFieldIndex < fillableAutofillFields.size(); autofillFieldIndex++) {
FilledAutofillField filledAutofillField = mHintMap.get(hint);
if (filledAutofillField == null) {
continue;
}
AutofillFieldMetadata autofillFieldMetadata = fillableAutofillFields.get(autofillFieldIndex);
AutofillId autofillId = autofillFieldMetadata.getId();
int autofillType = autofillFieldMetadata.getAutofillType();
switch (autofillType) {
case View.AUTOFILL_TYPE_LIST:
int listValue = autofillFieldMetadata.getAutofillOptionIndex(filledAutofillField.getTextValue());
if (listValue != -1) {
datasetBuilder.setValue(autofillId, AutofillValue.forList(listValue));
setValueAtLeastOnce = true;
}
break;
case View.AUTOFILL_TYPE_DATE:
Long dateValue = filledAutofillField.getDateValue();
if (dateValue != null) {
datasetBuilder.setValue(autofillId, AutofillValue.forDate(dateValue));
setValueAtLeastOnce = true;
}
break;
case View.AUTOFILL_TYPE_TEXT:
String textValue = filledAutofillField.getTextValue();
if (textValue != null) {
datasetBuilder.setValue(autofillId, AutofillValue.forText(textValue));
setValueAtLeastOnce = true;
}
break;
case View.AUTOFILL_TYPE_TOGGLE:
Boolean toggleValue = filledAutofillField.getToggleValue();
if (toggleValue != null) {
datasetBuilder.setValue(autofillId, AutofillValue.forToggle(toggleValue));
setValueAtLeastOnce = true;
}
break;
case View.AUTOFILL_TYPE_NONE:
default:
Log.w(TAG, "Invalid autofill type - " + autofillType);
break;
}
}
}
return setValueAtLeastOnce;
}
FillResponse.Builder responseBuilder = new FillResponse.Builder();
//把每一个Dataset都存进FillResponse包住
responseBuilder.addDataset(dataset);
//传递给FillCallback的onSuccess
callback.onSuccess(responseBuilder.build());
扩展
在自动填入之前,可以在Dataset中加入一些安全密码的认证,以防autofill的数据被盗用。
datasetBuilder.setAuthentication(new Intent(安全码认证的activity));
AutofillManager mAutofillManager = getSystemService(AutofillManager.class);
要注意Autofill的回调在Activity的生命周期情况
@Override
protected void onResume() {
super.onResume();
mAutofillManager.registerCallback(mAutofillCallback);
}
@Override
protected void onPause() {
super.onPause();
mAutofillManager.unregisterCallback(mAutofillCallback);
}
AutofillManager.AutofillCallback mAutofillCallback = new AutofillManager.AutofillCallback() {
@Override
public void onAutofillEvent(View view, int event) {
super.onAutofillEvent(view, event);
if (view instanceof AutoCompleteTextView) {
switch (event) {
//当autofill不可用
case AutofillManager.AutofillCallback.EVENT_INPUT_UNAVAILABLE:
break;
//当autofill隐藏
case AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN:
break;
//当autofill显示
case AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN:
break;
default:
Log.d(TAG, "Unexpected callback: " + event);
}
}
}
@Override
public void onAutofillEvent(View view, int virtualId, int event) {
super.onAutofillEvent(view, virtualId, event);
//事件类型同onAutofillEvent(View view, int event)
}
});
除了要在指定的子view中设置autofillHints标签以外,还需要重写两个View的方法:
@Override
public void onProvideAutofillVirtualStructure(ViewStructure structure,
int flags) {
super.onProvideAutofillVirtualStructure(structure, flags);
//创建一个VirtualStructure对象
structure.setClassName(getClass().getName());
//mVirtualViews是一个装载子view属性的集合
int childrenSize = mVirtualViews.size();
int index = structure.addChildCount(childrenSize);
//关联autofill和viewstructure
for (int i = 0; i < childrenSize; i++) {
Item item = mVirtualViews.valueAt(i);
//创建ViewStructure跟子view的属性关联
ViewStructure child = structure.newChild(index);
child.setAutofillId(structure.getAutofillId(), item.id);
child.setAutofillHints(item.hints);
child.setAutofillType(item.type);
child.setDataIsSensitive(!item.sanitized);
child.setText(item.text);
child.setAutofillValue(AutofillValue.forText(item.text));
child.setFocused(item.focused);
child.setId(item.id, getContext().getPackageName(), null, item.line.idEntry);
child.setClassName(item.getClassName());
index++;
}
//contentIsSetFromResources是否为resouces的静态数据
boolean sensitive = !contentIsSetFromResources();
// 设置是否为敏感信息,默认是
child.setDataIsSensitive(sensitive);
}
AutofillValue value = values.valueAt(i);
//得到自定义autofill用户选中的值
customView.item.setXXX(value.getXXXValue());
public void eventHandler(View view) {
AutofillManager afm = context.getSystemService(AutofillManager.class);
if (afm != null) {
afm.requestAutofill();
}
}
AutofillManager afm = context.getSystemService(AutofillManager.class);
if(afm.isEnable()){
//可用
}