You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

安卓开发:读取SD卡指定目录VCF文件并展示联系人信息

解决安卓读取SD卡VCF文件并展示联系人的问题

先帮你梳理下代码里的几个核心问题,这也是导致无法正常运行的关键:

  • 你错误地用ContentResolver查询系统联系人数据库,但咱们的目标是读取本地SD卡上的VCF文件,这和系统存储的联系人完全是两个数据源,方向一开始就错了
  • Uri.fromFile(new File(path1,""+vfile))这里路径拼接逻辑混乱,vfile已经是基于dir创建好的文件,再拼path1会生成重复路径(比如/sdcard/Mark/Contact//sdcard/Mark/Contact/a.vcf),肯定找不到文件
  • 没处理安卓的存储权限问题:从Android 6.0(API 23)开始,读取外部存储需要动态申请权限,只在Manifest里声明是不够的
  • 没有判断a.vcf文件是否存在,如果文件不存在,后续操作都会直接报错

接下来给你完整的修正方案:

第一步:配置存储权限

先在AndroidManifest.xml里添加必要的权限声明:

<!-- 基础读取外部存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Android 10+ 访问公共目录需要的权限(如果用MediaStore替代直接文件访问可省略) -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    android:minSdkVersion="30" />

如果你的应用目标是Android 6.0+,还需要在Activity里动态申请权限:

private static final int REQUEST_STORAGE_PERMISSION = 100;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 检查并申请存储权限
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                REQUEST_STORAGE_PERMISSION);
    } else {
        // 权限已获取,开始处理VCF文件
        processVCFFile();
    }
}

// 处理权限申请结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_STORAGE_PERMISSION) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            processVCFFile();
        } else {
            Toast.makeText(this, "需要存储权限才能读取VCF文件", Toast.LENGTH_SHORT).show();
        }
    }
}

第二步:解析VCF文件内容

VCF是纯文本格式,我们可以直接读取文件内容,提取FN(姓名)和TEL(手机号)字段。这里写一个通用的解析方法:

private List<Contact> parseVCFFile(File vcfFile) {
    List<Contact> contactList = new ArrayList<>();
    Contact currentContact = null;

    try (BufferedReader reader = new BufferedReader(new FileReader(vcfFile))) {
        String line;
        while ((line = reader.readLine()) != null) {
            line = line.trim();
            // 开始一个新的联系人条目
            if (line.startsWith("BEGIN:VCARD")) {
                currentContact = new Contact();
            }
            // 提取姓名字段
            else if (line.startsWith("FN:")) {
                if (currentContact != null) {
                    currentContact.name = line.substring(3);
                }
            }
            // 提取手机号字段(忽略类型,只取号码内容)
            else if (line.startsWith("TEL;")) {
                if (currentContact != null) {
                    int colonIndex = line.indexOf(':');
                    if (colonIndex != -1) {
                        currentContact.phoneNumber = line.substring(colonIndex + 1);
                    }
                }
            }
            // 结束一个联系人条目,加入列表
            else if (line.startsWith("END:VCARD")) {
                if (currentContact != null) {
                    contactList.add(currentContact);
                    currentContact = null;
                }
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
        Toast.makeText(this, "读取VCF文件失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
    }
    return contactList;
}

// 定义一个简单的联系人实体类
static class Contact {
    String name;
    String phoneNumber;
}

第三步:展示联系人到ListView或TextView

方案1:展示到ListView

先在布局文件里添加ListView:

<ListView
    android:id="@+id/contact_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

然后在processVCFFile()方法里处理文件并设置适配器:

private void processVCFFile() {
    // 注意:Android 10+ 不推荐直接用Environment.getExternalStorageDirectory(),建议改用MediaStore访问公共目录
    String path1 = Environment.getExternalStorageDirectory().getPath() + "/Mark/Contact/";
    File dir = new File(path1);
    
    // 确保目标目录存在
    if (!dir.exists()) {
        boolean created = dir.mkdirs();
        if (!created) {
            Toast.makeText(this, "创建目录失败", Toast.LENGTH_SHORT).show();
            return;
        }
    }
    
    File vcfFile = new File(dir, "a.vcf");
    if (!vcfFile.exists()) {
        Toast.makeText(this, "a.vcf文件不存在", Toast.LENGTH_SHORT).show();
        return;
    }

    // 解析VCF文件
    List<Contact> contactList = parseVCFFile(vcfFile);

    // 设置ListView适配器
    ListView listView = findViewById(R.id.contact_list);
    ArrayAdapter<Contact> adapter = new ArrayAdapter<>(this,
            android.R.layout.simple_list_item_2,
            android.R.id.text1,
            contactList) {
        @NonNull
        @Override
        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
            View view = super.getView(position, convertView, parent);
            TextView text1 = view.findViewById(android.R.id.text1);
            TextView text2 = view.findViewById(android.R.id.text2);
            Contact contact = contactList.get(position);
            text1.setText(contact.name);
            text2.setText(contact.phoneNumber);
            return view;
        }
    };
    listView.setAdapter(adapter);
}

方案2:展示到TextView

如果用TextView展示,直接拼接文本内容即可:

private void processVCFFile() {
    // 前面的目录检查、文件解析代码和上面一致
    List<Contact> contactList = parseVCFFile(vcfFile);

    TextView textView = findViewById(R.id.contact_text);
    StringBuilder sb = new StringBuilder();
    for (Contact contact : contactList) {
        sb.append("姓名:").append(contact.name).append("\n");
        sb.append("手机号:").append(contact.phoneNumber).append("\n\n");
    }
    textView.setText(sb.toString());
}

额外注意事项

  • Android 10(API 29)及以上,系统限制了直接访问外部存储公共目录的权限,如果你需要兼容高版本系统,建议改用MediaStore来查询和读取目标VCF文件,避免出现权限拒绝问题
  • 确保你的VCF文件是标准格式,部分自定义VCF可能字段格式特殊,需要根据实际情况调整解析逻辑

内容的提问来源于stack exchange,提问作者user5050715

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:43:05