代码分为两部分:

Part One 将预置的联系人插入到数据库中;

Part Two 保证预置联系人仅仅读,无法被编辑删除(在三个地方屏蔽对预置联系人进行编辑处理:联系人详情界面、联系人多选界面、新建联系人选择合并联系人时)。

【注意】假设您不须要限制预置联系人的删除/编辑操作,增加Part One部分代码就可以,并去掉第三步”新增函数“  中的语句:contactvalues.put(RawContacts.IS_SDN_CONTACT, -1);

 

Part One:

File:AbstractStartSIMService.java

Path: alps\packages\apps\Contacts\src\com\mediatek\contacts\simcontact

 

1.引入包

import android.provider.ContactsContract.PhoneLookup;

 

2.添加变量

private static boolean sIsRunningNumberCheck = false;

private static final int INSERT_PRESET_NUMBER_COUNT = xxx;           //预置联系人的个数

private static final String  INSERT_PRESET_NAME[]    = {"xxx1","xxx2",...};  //各预置联系人的姓名 

private static final String  INSERT_PRESET_NUMBER[] = {"xxx1","xxx2",...};  //各预置联系人的号码

 

3.添加函数(将预置联系人信息写入数据库中):

  private void importDefaultReadonlyContact() {

      new Thread(new Runnable() {

 

          @Override

          public void run() {

              Log.i(TAG, "isRunningNumberCheck before: " + sIsRunningNumberCheck);

              if (sIsRunningNumberCheck) {

                   return;

              }

              sIsRunningNumberCheck = true;

              for(int i = 0;i < INSERT_PRESET_NUMBER_COUNT; i++)

              {

                Log.i(TAG, "isRunningNumberCheck after: " + sIsRunningNumberCheck);

                Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri

                        .encode(INSERT_PRESET_NUMBER[i]));

                Log.i(TAG, "getContactInfoByPhoneNumbers(), uri = " + uri);

 

                Cursor contactCursor = getContentResolver().query(uri, new String[] {

                        PhoneLookup.DISPLAY_NAME, PhoneLookup.PHOTO_ID

                }, null, null, null);

                try {

                    if (contactCursor != null && contactCursor.getCount() > 0) {

                        return;

                    } else {

                        final ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();

                        ContentProviderOperation.Builder builder = ContentProviderOperation

                                .newInsert(RawContacts.CONTENT_URI);

                        ContentValues contactvalues = new ContentValues();

                        contactvalues.put(RawContacts.ACCOUNT_NAME,

                                AccountType.ACCOUNT_NAME_LOCAL_PHONE);

                        contactvalues.put(RawContacts.ACCOUNT_TYPE,

                                AccountType.ACCOUNT_TYPE_LOCAL_PHONE);

                        contactvalues.put(RawContacts.INDICATE_PHONE_SIM,

                                ContactsContract.RawContacts.INDICATE_PHONE);

                        contactvalues.put(RawContacts.IS_SDN_CONTACT, -1);

                        builder.withValues(contactvalues);

                        builder.withValue(RawContacts.AGGREGATION_MODE,

                                RawContacts.AGGREGATION_MODE_DISABLED);

                        operationList.add(builder.build());

 

                        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);

                        builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);

                        builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);

                        builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);

                        builder.withValue(Phone.NUMBER, INSERT_PRESET_NUMBER[i]);

                        builder.withValue(Data.IS_PRIMARY, 1);

                        operationList.add(builder.build());

 

                        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);

                        builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);

                        builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);

                        builder.withValue(StructuredName.DISPLAY_NAME, INSERT_PRESET_NAME[i]);

                        operationList.add(builder.build());

 

                        try {

                            getContentResolver().applyBatch(

                                    ContactsContract.AUTHORITY, operationList);

                        } catch (RemoteException e) {

                            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));

                        } catch (OperationApplicationException e) {

                            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));

                        }

 

                    }

                } finally {

                    // when this service start,but the contactsprovider has not been started yet.

                    // the contactCursor perhaps null, but not always.(first load will weekup the provider)

                    // so add null block to avoid nullpointerexception

                    if (contactCursor != null) {

                        contactCursor.close();

                    }

                }

              }//for

              Log.i(TAG, "isRunningNumberCheck insert: " + sIsRunningNumberCheck);

              sIsRunningNumberCheck = false;

          }

      }).start();

 

  }

   

4.onStart中调用这个函数:

 

    public void onStart(Intent intent, int startId) {

        .....           

        //add by MTK---Preset Contacts

        importDefaultReadonlyContact();

           

        log("[onStart]" + intent + ", startId " + startId);

        if (intent == null) {

            return;

        }

        .....

}

 

Part Two

1.File:DefaultContactListAdapter.java  Path:alps\packages\apps\contacts\src\com\android\contacts\list

(1)configureSelection函数中有五处 RawContacts.IS_SDN_CONTACT + " = 0",都改为:RawContacts.IS_SDN_CONTACT + " < 1"

 

(2)configureOnlyShowPhoneContactsSelection函数中例如以下语句:

selection.append(Contacts.INDICATE_PHONE_SIM + "= ?");

        selectionArgs.add("-1");

之后添加以下的代码

selection.append(" AND " + RawContacts.IS_SDN_CONTACT + " > -1");

 

 

2.File:ProfileAndContactsLoader.java  Path:alps\packages\apps\contacts\src\com\android\contacts\list 

loadSDN函数中有两处 RawContacts.IS_SDN_CONTACT + " = 0",都改为:RawContacts.IS_SDN_CONTACT + " < 1"

 

3. File:Contact.java  Path:alps\packages\apps\contacts\src\com\android\contacts\model 

添加例如以下函数:

    //add by MTK---Preset Contacts

    public boolean isReadOnlyContact() {

             return mIsSdnContact == -1;

     }

 

4. File:ContactLoaderFragment.java Path:alps\packages\apps\contacts\src\com\android\contacts\detail 

将isContactEditable函数改动为:

   public boolean isContactEditable() {

        return mContactData != null && !mContactData.isDirectoryEntry()

                && !mContactData.isSdnContacts() &&  !mContactData.isReadOnlyContact() ;

    }

 

5. File:ContactEntryListAdapter.java Path:alps\packages\apps\contacts\src\com\android\contacts\list  

在文件最后添加下面代码:

    public boolean showReadOnlyContact = true;

    public void setShowReadOnlyContact(boolean canDelete) {

        showReadOnlyContact = canDelete;

    }

 

6. File:ContactEntryListFragment.java  Path:alps\packages\apps\contacts\src\com\android\contacts\list

引入例如以下包:

import com.mediatek.contacts.list.ContactsMultiDeletionFragment;

 

在onCreateLoader函数中。倒数第二句mAdapter.configureLoader(loader, directoryId);之前添加语句:   

            mAdapter.setShowReadOnlyContact((this instanceof ContactsMultiDeletionFragment) ?

false : true);

 

8 7.File:MultiContactsBasePickerAdapter.java Path:alps\packages\apps\contacts\src\com\mediatek\contacts\list  在configureSelection函数最后的语句 loader.setSelection(selection.toString());之前添加语句:

        if (!showReadOnlyContact ) {

            selection.append(" AND " + Contacts.IS_SDN_CONTACT + "=0");

        }

      

 

8.File:AggregationSuggestionEngine.java Path:alps\packages\apps\contacts\src\com\android\contacts\editor

在configureSelection函数最后的语句 

在语句:   sb.append(" AND " + Contacts.INDICATE_PHONE_SIM + "=-1");

之后加入: sb.append(" AND " + Contacts.IS_SDN_CONTACT + "!=-1");

9.File:JoinContactListAdapter.java

Path:packages\apps\contacts\src\com\android\contacts\list   

函数:public void configureLoader(CursorLoader cursorLoader, long directoryId) 

将: loader.setSelection(Contacts._ID + "!=?"+" AND " + Contacts.INDICATE_PHONE_SIM + "=-1");‘

改动为:

    loader.setSelection(Contacts._ID + "!=?"+" AND " + Contacts.INDICATE_PHONE_SIM + "=-1" + " AND " + Contacts.IS_SDN_CONTACT + "!=-1");

 

【After JB5】

实现预置联系人(包括姓名、号码信息)至手机中;并保证该联系人是仅仅读的。无法被删除/编辑。

代码分为两部分:

Part One 将预置的联系人插入到数据库中;

Part Two 保证预置联系人仅仅读,无法被编辑删除(在三个地方屏蔽对预置联系人进行编辑处理:联系人详情界面、联系人多选界面、新建联系人选择合并联系人时)。

【注意】假设您不须要限制预置联系人的删除/编辑操作,增加Part One部分代码就可以。并去掉第一步”新增函数“  中的语句:contactvalues.put(RawContacts.IS_SDN_CONTACT, -2);

 

Part One:

1.新建PresetContactsImportProcessor.java

Path: alps\packages\apps\Contacts\src\com\mediatek\contacts\simservice

package com.mediatek.contacts.simservice;

 

import com.mediatek.contacts.simservice.SIMProcessorManager.ProcessorCompleteListener;

 

import android.content.Context;

import android.content.Intent;

import android.util.Log;

import android.content.ContentProviderOperation;

import android.content.ContentValues;

import android.content.OperationApplicationException;

import android.database.Cursor;

import android.net.Uri;

 

import android.provider.ContactsContract;

import android.provider.ContactsContract.CommonDataKinds.Email;//for usim

import android.provider.ContactsContract.CommonDataKinds.GroupMembership;

import android.provider.ContactsContract.CommonDataKinds.Phone;

import android.provider.ContactsContract.CommonDataKinds.StructuredName;

import android.provider.ContactsContract.Data;

import android.provider.ContactsContract.Groups;

import android.provider.ContactsContract.RawContacts;

 

 

import com.android.contacts.common.model.account.AccountType;

import android.os.RemoteException;

 

import java.util.ArrayList;

import com.mediatek.contacts.simservice.SIMProcessorManager.ProcessorCompleteListener;

import com.mediatek.contacts.simservice.SIMServiceUtils;

import com.mediatek.contacts.simservice.SIMServiceUtils.ServiceWorkData;

import com.mediatek.contacts.simcontact.SimCardUtils;

import com.mediatek.contacts.util.LogUtils;

import android.provider.ContactsContract.PhoneLookup;

 

public class PresetContactsImportProcessor extends SIMProcessorBase {

    private static final String TAG = "PresetContactsImportProcessor";

   

    private static boolean sIsRunningNumberCheck = false;

    private static final int INSERT_PRESET_NUMBER_COUNT = xxx;           //预置联系人的个数

    private static final String  INSERT_PRESET_NAME[]    = {"xxx1","xxx2",...};  //各预置联系人的姓名

    private static final String  INSERT_PRESET_NUMBER[] = {"xxx1","xxx2",...};  //各预置联系人的号码

   

    private int mSlotId;

    private Context mContext;

 

    public PresetContactsImportProcessor(Context context, int slotId, Intent intent,

            ProcessorCompleteListener listener) {

        super(intent, listener);

        mContext = context;

        mSlotId = slotId;

    }

 

    @Override

    public int getType() {

        return SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS;

    }

 

    @Override

    public void doWork() {

        if (isCancelled()) {

            LogUtils.d(TAG, "[doWork]cancel import preset contacts work. Thread id=" + Thread.currentThread().getId());

            return;

        }

        importDefaultReadonlyContact();

    }

   

    private void importDefaultReadonlyContact(){

         Log.i(TAG, "isRunningNumberCheck before: " + sIsRunningNumberCheck);

         if (sIsRunningNumberCheck) {

            return;

         }

         sIsRunningNumberCheck = true;

         for(int i = 0;i < INSERT_PRESET_NUMBER_COUNT; i++)

         {

             Log.i(TAG, "isRunningNumberCheck after: " + sIsRunningNumberCheck);

             Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri

                      .encode(INSERT_PRESET_NUMBER[i]));

             Log.i(TAG, "getContactInfoByPhoneNumbers(), uri = " + uri);

 

             Cursor contactCursor = mContext.getContentResolver().query(uri, new String[] {

                      PhoneLookup.DISPLAY_NAME, PhoneLookup.PHOTO_ID

         }, null, null, null);

         try {

             if (contactCursor != null && contactCursor.getCount() > 0) {

                  return;

             } else {

                  final ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();

                  ContentProviderOperation.Builder builder = ContentProviderOperation

                          .newInsert(RawContacts.CONTENT_URI);

                  ContentValues contactvalues = new ContentValues();

                  contactvalues.put(RawContacts.ACCOUNT_NAME,

                          AccountType.ACCOUNT_NAME_LOCAL_PHONE);

                  contactvalues.put(RawContacts.ACCOUNT_TYPE,

                          AccountType.ACCOUNT_TYPE_LOCAL_PHONE);

                  contactvalues.put(RawContacts.INDICATE_PHONE_SIM,

                          ContactsContract.RawContacts.INDICATE_PHONE);

                  contactvalues.put(RawContacts.IS_SDN_CONTACT, -2);

                  builder.withValues(contactvalues);

                  builder.withValue(RawContacts.AGGREGATION_MODE,

                          RawContacts.AGGREGATION_MODE_DISABLED);

                  operationList.add(builder.build());

 

                  builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);

                  builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);

                  builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);

                  builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);

                  builder.withValue(Phone.NUMBER, INSERT_PRESET_NUMBER[i]);

                  builder.withValue(Data.IS_PRIMARY, 1);

                  operationList.add(builder.build());

 

                  builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);

                  builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);

                  builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);

                  builder.withValue(StructuredName.DISPLAY_NAME, INSERT_PRESET_NAME[i]);

                  operationList.add(builder.build());

 

                  try {

                      mContext.getContentResolver().applyBatch(

                               ContactsContract.AUTHORITY, operationList);

                  } catch (RemoteException e) {

                      Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));

                  } catch (OperationApplicationException e) {

                      Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));

                  }

 

             }

         } finally {

             // when this service start,but the contactsprovider has not been started yet.

             // the contactCursor perhaps null, but not always.(first load will weekup the provider)

             // so add null block to avoid nullpointerexception

             if (contactCursor != null) {

                  contactCursor.close();

             }

         }//for

         Log.i(TAG, "isRunningNumberCheck insert: " + sIsRunningNumberCheck);

         sIsRunningNumberCheck = false;

         }

    }

}

 

2. 改动SIMServiceUtils.java

Path:alps\packages\apps\ContactsCommon\src\com\mediatek\contacts\simservice  

加入

public static final int SERVICE_WORK_IMPORT_PRESET_CONTACTS = 5;

3. 改动SIMProcessorManager.java

Path:alps\packages\apps\Contacts\src\com\mediatek\contacts\simservice

在SIMProcessorManager.java中createProcessor函数里加入

else if (workType == SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS) {

            processor = new PresetContactsImportProcessor(context, slotId, intent, listener);

        }

4. 改动BootCmpReceiver.java

Path:alps\packages\apps\Contacts\src\com\mediatek\contacts\simcontact

在BootCmpReceiver.java中processBootComplete()方法最后加入代码

startSimService(-1, SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS);

Part Two

1.  File:DefaultContactListAdapter.java 

Path: alps\packages\apps\ContactsCommon\src\com\android\contacts\common\list

(1)configureOnlyShowPhoneContactsSelection函数中例如以下语句:

selection.append(Contacts.INDICATE_PHONE_SIM + "= ?

");

        selectionArgs.add("-1");

之后添加以下的代码

selection.append(" AND " + RawContacts.IS_SDN_CONTACT + " > -2");

2.  File:Contact.java 

Path: alps\packages\apps\ContactsCommon\src\com\android\contacts\common\model 

添加例如以下函数:

    //add by MTK---Preset Contacts

    public boolean isReadOnlyContact() {

             return mIsSdnContact == -2;

     }

3.  File:ContactLoaderFragment.java

Path:alps\packages\apps\contacts\src\com\android\contacts\detail 

将isContactEditable函数改动为:

   public boolean isContactEditable() {

        return mContactData != null && !mContactData.isDirectoryEntry()&& !mContactData.isSdnContacts()&&  !mContactData.is InternationalDialNumber()&&  !mContactData.isReadOnlyContact() ;

    }

 

4.  File:ContactEntryListAdapter.java 

Path:alps\packages\apps\contactscommon\src\com\android\contacts\common\list  

在文件最后添加下面代码:

    public boolean showReadOnlyContact = true;

    public void setShowReadOnlyContact(boolean canDelete) {

        showReadOnlyContact = canDelete;

    }

 

5.  File:ContactEntryListFragment.java  

Path:alps\packages\apps\contactscommon\src\com\android\contacts\common\list

加入代码:

    protected boolean isInstanceOfContactsMultiDeletionFragment(){

    return false;

    }

在onCreateLoader函数中,倒数第二句mAdapter.configureLoader(loader, directoryId);之前添加语句:   

            mAdapter.setShowReadOnlyContact(isInstanceOfContactsMultiDeletionFragment() ? false : true);

6.  File: ContactsMultiDeletionFragment.java

Path:alps\packages\apps\Contacts\src\com\mediatek\contacts\list

加入代码:

    protected boolean isInstanceOfContactsMultiDeletionFragment(){

    return true;

    }

7.File:MultiContactsBasePickerAdapter.java

Path:alps\packages\apps\contacts\src\com\mediatek\contacts\list 

在configureSelection函数最后的语句 loader.setSelection(selection.toString());之前添加语句:

        if (!showReadOnlyContact ) {

            selection.append(" AND " + Contacts.IS_SDN_CONTACT + "=0");

}

8.File:AggregationSuggestionEngine.java

Path:alps\packages\apps\contacts\src\com\android\contacts\editor

在loadAggregationSuggestions函数最后的语句 

在语句:   sb.append(" AND " + Contacts.INDICATE_PHONE_SIM + "=-1");

之后加入: sb.append(" AND " + Contacts.IS_SDN_CONTACT + "!=-2");

9.File:JoinContactListAdapter.java

Path:packages\apps\contacts\src\com\android\contacts\list   

函数:public void configureLoader(CursorLoader cursorLoader, long directoryId) 

将: loader.setSelection(Contacts._ID + "!=?

"+" AND " + Contacts.INDICATE_PHONE_SIM + "=-1");

改动为:

    loader.setSelection(Contacts._ID + "!=?"+" AND " + Contacts.INDICATE_PHONE_SIM + "=-1" + " AND " + Contacts.IS_SDN_CONTACT + "!=-2");

在手机中预置联系人/Service Number的更多相关文章

  1. 读取手机中的联系人信息(android.provider.ContactsContract)

    本篇开始讲如何从Android中得到本机联系人的信息.由于Android较快的版本升级,部分API已经发生了变化.本篇探究的通过ContentProvider机制获取联系人的API从Android2. ...

  2. Android向手机通讯录中的所有的联系人(包括SIM卡),向手机通讯录中插入联系人

    package com.example.myapi.phonepersion; import java.util.ArrayList; import java.util.List; import an ...

  3. Android-->发送短信页面实现(短信发送以及群发和从电话本中选择联系人)-----------》2

    分析下怎么写 首先,我们需要一个输入框,可以手动的输入手机号码, 其次,很少有人愿意手动输入,那么我们需要提供一个按钮来给我们的用户选择自己电话本中的联系人(一次可以选择多个即群发) 然后,我们需要一 ...

  4. [UWP]UWP中获取联系人/邮件发送/SMS消息发送操作

    这篇博客将介绍如何在UWP程序中获取联系人/邮件发送/SMS发送的基础操作. 1. 获取联系人 UWP中联系人获取需要引入Windows.ApplicationModel.Contacts名称空间. ...

  5. 【转】Android 增,删,改,查 通讯录中的联系人

    一.权限 操作通讯录必须在AndroidManifest.xml中先添加2个权限, <uses-permission android:name="android.permission. ...

  6. 微软BI 之SSIS 系列 - 在 SSIS 中使用 Web Service 以及 XML 解析

    开篇介绍 Web Service 的用途非常广几乎无处不在,像各大门户网站上的天气预报使用到的第三方 Web Service API,像手机客户端和服务器端的交互等都可以通过事先设计好的 Web Se ...

  7. Android 增,删,改,查 通讯录中的联系人

    一.权限 操作通讯录必须在AndroidManifest.xml中先添加2个权限, <uses-permission android:name="android.permission. ...

  8. 从Android手机中取出已安装的app包,导出apk

    从Android手机中取出已安装的app包,导出apk TAG:Android,提取,apk,adb,pm,root,导出apk 假设有这样一个场景,A君看到你手机上一个实用APP,想要安装到自己手机 ...

  9. 【Java EE 学习 24 下】【注解在数据库开发中的使用】【反射+注解+动态代理在事务中的应用service层】

    一.使用注解可以解决JavaBean和数据库中表名不一致.字段名不一致.字段数量不一致的问题. 1.Sun公司给jdbc提供的注解 @Table.@Column.@Id.@OneToMany.@One ...

随机推荐

  1. 7.第一次使用java连接mongodb遇到的问题

    转自:https://blog.csdn.net/u010523770/article/details/54585883 新版本的mongodb的驱动包是依赖bson.jar和mongodb_driv ...

  2. Mybatis传多个参数(推荐)

    Dao层的函数方法 int deleteMsgById(@Param("name") String name,@Param("id") String id); ...

  3. 4.graph.h

    #pragma once #include <stdio.h> #include <graphics.h> #include <mmsystem.h> #pragm ...

  4. Servlet之doPost获取表单参数

    /** * 获取表单参数 */ private void readForm() { // TODO Auto-generated method stub Enumeration e = request ...

  5. 简单STL笔记

    想了好久,还是把自己了解的先整理一下吧,毕竟老是忘,这里主要简单介绍三种容器 set,queue,vector,以及栈 stack,队列queue 的简单用法.一.set 在set中,效率比vecto ...

  6. WPF通用框架ZFS《项目结构介绍01》_模块介绍

    首页介绍: 下图为项目运行首页图片, 大的结构分为三块: 1.Header首部模块(存放通知组件[全局通知.消息管理 ].扩展模块[皮肤.系统设置.关于作者.退出系统]) 2.Left左侧菜单模块(存 ...

  7. HDOJ 5306 Gorgeous Sequence 线段树

    http://www.shuizilong.com/house/archives/hdu-5306-gorgeous-sequence/ Gorgeous Sequence Time Limit: 6 ...

  8. Python: PS 滤镜特效 -- Marble Filter

    本文用 Python 实现 PS 滤镜特效,Marble Filter, 这种滤镜使图像产生不规则的扭曲,看起来像某种玻璃条纹, 具体的代码如下: import numpy as np import ...

  9. ElasticSearch概述和定义

    福利 => 每天都推送 欢迎大家,关注微信扫码并加入我的4个微信公众号:   大数据躺过的坑      Java从入门到架构师      人工智能躺过的坑         Java全栈大联盟   ...

  10. 将BT下载对抗到底

    将BT下载对抗到底      随着互联网业务的多元化,各种P2P应用也越来越多,在企业中多数流量都会被类似于BT的下载所占用,BT之所以会危害到局域网,是因为它占用了大量网络带宽.网络管理员可以通过一 ...