定义一个自己的AutofillService
//此填充服务以name作为主键,通过此字段来去重,也通过此字段来作为显示时的表单标题
//当应用没有name字段时,也可以设置一个隐藏的自动填充控件,来设置自己想要的主键和表单标题
//当用户保存表单时,如果确实没有name字段,服务将会根据当前时间来生成一个name主键
//此填充服务不支持同一个控件拥有多个字段,当同一控件拥有多个字段时,将以第一个字段为准
//此填充服务不支持多个控件使用同一个字段,当多个控件使用同一字段时,只有最后的控件内容会被保存
public class AutofillService extends android.service.autofill.AutofillService {
public static final String HINT_TYPE_NAME = "name";
public static final String HINT_TYPE_PASSWORD = "password";
public static final String HINT_TYPE_PHONE = "phone";
public static final String HINT_TYPE_PACKAGE = "package";
public static final List HINT_TYPE_COLLECTIONS = new ArrayList();
static {
HINT_TYPE_COLLECTIONS.add(HINT_TYPE_NAME);
HINT_TYPE_COLLECTIONS.add(HINT_TYPE_PASSWORD);
HINT_TYPE_COLLECTIONS.add(HINT_TYPE_PHONE);
HINT_TYPE_COLLECTIONS.add(HINT_TYPE_PACKAGE);
}
//模拟数据库中的数据,实际应用中,应该将这个数据保存在数据库中
private static final List suggestions = new ArrayList();
static {
//模拟数据库中的数据,实际应用中,应当把表单保存到数据库中,填充时再从数据库读取
Map suggestion1 = new HashMap();
suggestion1.put(HINT_TYPE_NAME, "表单1");
suggestion1.put(HINT_TYPE_PASSWORD, "123456");
suggestion1.put(HINT_TYPE_PHONE, "18420015500");
suggestions.add(suggestion1);
Map suggestion2 = new HashMap();
suggestion2.put(HINT_TYPE_NAME, "表单2");
suggestion2.put(HINT_TYPE_PASSWORD, "abcdefg");
suggestion2.put(HINT_TYPE_PHONE, "17935842251");
suggestions.add(suggestion2);
}
@Override
public void onSaveRequest(SaveRequest request, SaveCallback callback) {
//获取所有自动填充的节点
List structures = request.getFillContexts().stream().map(FillContext::getStructure).collect(toList());
List viewNodes = new ArrayList();
parseAllAutofillNode(structures, viewNodes);
//保存表单内容
//每条建议记录对应一个Map,Map每个键值対代表控件的HintType和文本值
Map suggestion = new HashMap();
for (AssistStructure.ViewNode viewNode : viewNodes) {
String hintType = viewNode.getAutofillHints()[0];
String value = viewNode.getText().toString();
suggestion.put(hintType, value);
}
suggestions.add(suggestion);
//成功
callback.onSuccess();
}
@Override
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) {
//获取所有自动填充节点的AutofillId和HintType
List structures = request.getFillContexts().stream().map(FillContext::getStructure).collect(toList());
Map hintTypeMap = parseAllHintType(structures);
//每条建议记录对应填充服务中的一个Dataset对象
//每个Dataset代表了一套数据,包含name,password,phone等所有保存的字段
//我们用Map来记录Dataset的数据,从而可以方便得将其存储到数据库或内存中
FillResponse.Builder fillResponseBuilder = new FillResponse.Builder();
for (Map suggestion : suggestions) {
Dataset.Builder datasetBuilder = new Dataset.Builder();
String name = suggestion.get(HINT_TYPE_NAME);
RemoteViews presentation = createPresentation(name);
for (AutofillId autofillId : hintTypeMap.keySet()) {
//将suggestion中的单个字段加入dataset
String hintType = hintTypeMap.get(autofillId);
String value = suggestion.get(hintType);
if (value != null)
datasetBuilder.setValue(autofillId, AutofillValue.forText(value), presentation);
//设置需要保存的表单节点,这一步一定要有,否则Activity退出时不会保存表单
SaveInfo.Builder saveInfoBuilder = new SaveInfo.Builder(HINT_TYPE_COLLECTIONS.indexOf(hintType), new AutofillId[]{autofillId});
//设置关联的节点,如果不设置,只有所有节点值发生变化时,系统才认为表单发生了变更,才会询问是否要保存表单
saveInfoBuilder.setOptionalIds(hintTypeMap.keySet().stream().toArray(AutofillId[]::new));
SaveInfo saveInfo = saveInfoBuilder.build();
fillResponseBuilder.setSaveInfo(saveInfo);
}
Dataset dataset = datasetBuilder.build();
fillResponseBuilder.addDataset(dataset);
}
FillResponse fillResponse = fillResponseBuilder.build();
//成功
callback.onSuccess(fillResponse);
}
//获取所有自动填充的节点
private void parseAllAutofillNode(List structures, List autofillNodes) {
for (AssistStructure structure : structures) {
int windowNodeCount = structure.getWindowNodeCount();
for (int i = 0; i < windowNodeCount; i++) {
AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i);
AssistStructure.ViewNode rootViewNode = windowNode.getRootViewNode();
parseAllAutofillNode(rootViewNode, autofillNodes);
}
}
}
//获取所有自动填充的节点
private void parseAllAutofillNode(AssistStructure.ViewNode viewNode, List autofillNodes) {
if (viewNode.getAutofillHints() != null)
autofillNodes.add(viewNode);
int childCount = viewNode.getChildCount();
for (int i = 0; i < childCount; i++)
parseAllAutofillNode(viewNode.getChildAt(i), autofillNodes);
}
//获取所有Autofill节点的HintType
private Map parseAllHintType(List structures) {
Map hintTypeMap = new HashMap();
for (AssistStructure structure : structures) {
int windowNodeCount = structure.getWindowNodeCount();
for (int i = 0; i < windowNodeCount; i++) {
AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i);
AssistStructure.ViewNode rootViewNode = windowNode.getRootViewNode();
parseAllHintType(rootViewNode, hintTypeMap);
}
}
return hintTypeMap;
}
//获取所有Autofill节点的HintType
private void parseAllHintType(AssistStructure.ViewNode viewNode, Map hintTypeMap) {
if (viewNode.getAutofillHints() != null)
hintTypeMap.put(viewNode.getAutofillId(), viewNode.getAutofillHints()[0]);
int childCount = viewNode.getChildCount();
for (int i = 0; i < childCount; i++)
parseAllHintType(viewNode.getChildAt(i), hintTypeMap);
}
//创建一个表单建议对应的View
private RemoteViews createPresentation(String name) {
RemoteViews presentation = new RemoteViews(APP.ctx.getPackageName(), R.layout.item_autofill_value);
presentation.setTextViewText(R.id.text, name);
return presentation;
}
}
表单下拉框的布局代码,item_autofill_value.xml
在应用清单中注册自己的Service
在手机【设置】-【系统】-【语言和输入法】-【自动填充服务】里,选择自己的Service作为默认填充服务 在其它应用里面使用自己的AutofillService
EditText et1;
EditText et2;
et1.setAutofillHints(AutofillService.HINT_TYPE_NAME);
et2.setAutofillHints(AutofillService.HINT_TYPE_PASSWORD);
运行结果 清除过往的表单记录
如果表单是保存在数据库里面,在AutofillService所在的APP提供一个界面,将数据库数据全部清除即可
通过代码直接跳转到自动填充服务设置界面
//打开自动填充服务设置界面
Intent intent = new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE);
intent.setData(Uri.parse("package:com.android.settings"));
startActivityForResult(intent, 0);
参考资料
这篇博客是参考官方Demo写的,代码完整,运行成功,网上很多关于Autofill的博客都是不完整的
由于官方的Demo功能考虑比较完善,所以代码比较复杂,这篇博客只保留了核心功能代码,方便大家理解
有精力的同学,可以研究下官方文档和Demo:
文档:https://developer.android.google.cn/reference/android/service/autofill/AutofillService
Demo:https://github.com/googlesamples/android-AutofillFramework