您当前的位置: 首页 >  android

命运之手

暂无认证

  • 2浏览

    0关注

    747博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【Android】【自动填充】自定义AutofillService(二):编写AutofillService代码

命运之手 发布时间:2019-04-19 14:29:58 ,浏览量:2

定义一个自己的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

关注
打赏
1654938663
查看更多评论
立即登录/注册

微信扫码登录

0.1976s