Autofill Framework(自动填写)用法详解
来源:互联网 发布:程序员怎么选择公司 编辑:程序博客网 时间:2024/05/22 14:14
文/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
在layout中通过autofillHints标记需要记录的控件节点
通过属性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种,分别是:
- AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE 信用卡到期日期
- AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY 信用卡到期日
- AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH 信用卡到期月
- AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR 信用卡到期年
- AUTOFILL_HINT_CREDIT_CARD_NUMBER 信用卡卡号
- AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE 信用卡安全密码
- AUTOFILL_HINT_EMAIL_ADDRESS 邮箱地址
- AUTOFILL_HINT_NAME 用户真名
- AUTOFILL_HINT_PASSWORD 用户密码
- AUTOFILL_HINT_PHONE 电话号码
- AUTOFILL_HINT_POSTAL_ADDRESS 邮寄地址
- AUTOFILL_HINT_POSTAL_CODE 邮寄编号
- AUTOFILL_HINT_USERNAME 用户名
view.setImportantForAutofill(int mode)
设置autofill的重要级别也是一些常量,对应xml的标签是android:importantForAutofill,有5种模式:
- IMPORTANT_FOR_AUTOFILL_AUTO 不管是否重要,都使用autofill
- IMPORTANT_FOR_AUTOFILL_NO 不使用autofill,但是子view可以使用
- IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS 不使用autofill,子view也不使用
- IMPORTANT_FOR_AUTOFILL_YES 使用autofill,包括子view
- IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS 使用autofill,但子view不使用
AutofillService
1.创建自定义AutofileService,继承AutofillService
重写两个方法:
- onSaveRequest(SaveRequest request, SaveCallback callback)
保存需要自动填入记录。
List<FillContext> context = request.getFillContexts();//保存步骤://1.得到最近一条需要填写的表单(表单的所有内容)AssistStructure structure = context.get(context.size() - 1).getStructure();//2.解析记录的数据AssistStructure//3.通过SharedPreferences,数据库,文件等存储方式保存下来
- onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
FillCallback callback)
执行自动填入记录。
//自动填写步骤://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" <!-- autofill的名字,随意设定,最终会显示在系统设置的autofill service上--> 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>
2.获取表单节点并解析AssistStructure
//从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)); } }}
2.1 AutofillValue
AutofillValue就是实际记录了要自动填写的内容,看源码可见,它区分了几种类型:
- CharSequence 字符串,list类型是CharSequence的list,也就是字符串
- boolean 布尔类型
- long 日期时间
AutofillValue.java
public final class AutofillValue implements Parcelable { public static final Creator<AutofillValue> 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类型的值。
3.获取表单节点并自动填入
//从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自己 <strong>注意这里跟保存模块不一样</strong> //下面这个代码是官网的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)); } }}
3.1准备一个RemoteView对象给自动填入list item的布局
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显示一个名字remoteViewsTextpublic 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; }
3.2装载数据Dataset
把定义的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<String> allHints = autofillFieldMetadataCollection.getAllHints(); for (int hintIndex = 0; hintIndex < allHints.size(); hintIndex++) { String hint = allHints.get(hintIndex); List<AutofillFieldMetadata> 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;}
3.3展示给UI选择自动填入
FillResponse.Builder responseBuilder = new FillResponse.Builder();//把每一个Dataset都存进FillResponse包住responseBuilder.addDataset(dataset);//传递给FillCallback的onSuccesscallback.onSuccess(responseBuilder.build());
扩展
在自动填入之前,可以在Dataset中加入一些安全密码的认证,以防autofill的数据被盗用。
datasetBuilder.setAuthentication(new Intent(安全码认证的activity));
回调监听
1.获取Autofill管理器
AutofillManager mAutofillManager = getSystemService(AutofillManager.class);
要注意Autofill的回调在Activity的生命周期情况
@Overrideprotected void onResume() { super.onResume(); mAutofillManager.registerCallback(mAutofillCallback);}@Overrideprotected void onPause() { super.onPause(); mAutofillManager.unregisterCallback(mAutofillCallback);}
2.Autofill的回调
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加入autofill的支持
除了要在指定的子view中设置autofillHints标签以外,还需要重写两个View的方法:
- onProvideAutofillVirtualStructure(ViewStructure structure,int flags)
保存autofill的数据
@Overridepublic 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);}
- autofill(SparseArray values)
将自动填写的信息展示到自定义view中,通过
AutofillValue value = values.valueAt(i);//得到自定义autofill用户选中的值customView.item.setXXX(value.getXXXValue());
其他
强制autofill
public void eventHandler(View view) { AutofillManager afm = context.getSystemService(AutofillManager.class); if (afm != null) { afm.requestAutofill(); }}
检查autofill是否可用
AutofillManager afm = context.getSystemService(AutofillManager.class);if(afm.isEnable()){ //可用}
- Autofill Framework(自动填写)用法详解
- input输入框autofill自动变成黄色
- 短信验证码自动填写认知之ContentObserver详解
- 自动表单填写
- 自动填写版权信息
- 自动表单填写
- 自动填写网页表单
- vfp 自动填写网页
- 自动填写excel表格
- 自动填写日志
- 自动填写表单基本原理
- 网页自动填写 学习
- 自动填写网页表单
- 短信自动填写
- 自动填写问卷
- mysql自动填写id
- 批处理自动填写密码
- chrome浏览器表单自动填充默认样式-autofill
- jstl select <c:if test下拉菜单不能被选中!
- 如何对系统中设置的修改记录增加log日志
- Java 排序 Lambda
- 软件开发与设计
- 自定义控件添加唯一标识
- Autofill Framework(自动填写)用法详解
- 训练样本不平衡对CNN训练结果的影响
- HTML5本地储存--利用storage事件实时监听Web Storage
- 【Java并发编程】之十九:并发新特性—Executor框架与线程池
- 用宏区分操作系统和编译器
- aes加密解密文件,以及计算文件的效验值,附带字符串加密解密
- 关于mips结构中地址窗口的分析(基于loongson3A平台)
- 硬盘基础知道 linux fdisk 分区 柱面等知识
- MVC请求IIS处理过程