【安卓】——Autofill Framework(自动填写)用法详解

本文是基于官方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 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"
          
          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 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自己 注意这里跟保存模块不一样
              //下面这个代码是官网的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显示一个名字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;
          }

      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 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;
      }

      3.3展示给UI选择自动填入

      FillResponse.Builder responseBuilder = new FillResponse.Builder();
      //把每一个Dataset都存进FillResponse包住
      responseBuilder.addDataset(dataset);
      //传递给FillCallback的onSuccess
      callback.onSuccess(responseBuilder.build());

      扩展
      在自动填入之前,可以在Dataset中加入一些安全密码的认证,以防autofill的数据被盗用。

      datasetBuilder.setAuthentication(new Intent(安全码认证的activity));
         
         
         
         

        回调监听

        1.获取Autofill管理器

        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);
          }

          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的数据
          @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);
          }
          
             
             
             
             
          • 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()){
              //可用
          }
          

          你可能感兴趣的:(Android)