首页 > Java > java教程 > 正文

解决Android ML Kit文本识别模块下载失败的教程

聖光之護
发布: 2025-10-25 12:20:46
原创
356人浏览过

解决Android ML Kit文本识别模块下载失败的教程

android开发中,集成ml kit文本识别功能时,开发者常遇到“waiting for the text optional module to be downloaded”错误。这通常是由于使用了不恰当的ml kit依赖库版本或类型所致。本文将深入探讨此问题,并提供一个简洁有效的解决方案,确保文本识别模块能够正确下载并初始化,从而顺利实现图像到文本的转换功能。

理解ML Kit文本识别模块下载问题

ML Kit为Android应用提供了强大的机器学习能力,其中文本识别功能允许开发者将图像中的文本提取出来。在使用ML Kit进行文本识别时,特别是对于某些语言或高级功能,ML Kit可能需要下载额外的模型模块。当遇到“Waiting for the text optional module to be downloaded. Please wait.”这样的错误提示时,通常意味着应用未能成功地获取或初始化所需的文本识别模型。

这个问题的核心往往不在于网络连接或设备存储空间,而更多地与项目配置中引入的ML Kit依赖库有关。Google提供了多种方式来集成ML Kit,包括通过Google Play Services(适用于托管模型)和直接通过独立的ML Kit库(适用于设备端模型)。不正确的依赖选择可能导致模块无法按预期下载或加载。

解决方案:使用正确的ML Kit文本识别依赖

解决“Waiting for the text optional module to be downloaded”错误的关键在于确保项目引入了正确的、包含设备端文本识别模型的ML Kit库。

许多开发者可能会错误地引入如下依赖:

implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2'
implementation 'com.google.android.gms:play-services-mlkit-text-recognition-common:18.0.0'
登录后复制

这些play-services-mlkit-*依赖是基于Google Play Services的ML Kit API,它们通常依赖于Google Play Services来管理模型的下载和更新。然而,对于某些设备端(on-device)的文本识别功能,更推荐直接使用ML Kit提供的独立库,它包含了所需的模型,减少了对Play Services特定版本或模块下载的依赖。

正确的解决方案是替换或补充以下依赖:

将上述play-services-mlkit-*依赖替换为(或确保项目中包含)com.google.mlkit:text-recognition。这个库直接包含了设备端的文本识别模型,通常能够避免模块下载的问题。

文心大模型
文心大模型

百度飞桨-文心大模型 ERNIE 3.0 文本理解与创作

文心大模型56
查看详情 文心大模型
// 移除或注释掉:
// implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2'
// implementation 'com.google.android.gms:play-services-mlkit-text-recognition-common:18.0.0'

// 添加或更新为:
implementation 'com.google.mlkit:text-recognition:16.0.0-beta6' // 使用最新的稳定版本,此处为示例版本
登录后复制

请注意,16.0.0-beta6是一个示例版本号,建议查阅ML Kit官方文档以获取最新的稳定版本。

步骤总结:

  1. 打开您的Android Studio项目。
  2. 导航到app模块的build.gradle文件。
  3. 在dependencies块中,找到与ML Kit文本识别相关的依赖。
  4. 确保您使用的是com.google.mlkit:text-recognition库,并将其更新到最新稳定版本。如果同时存在play-services-mlkit-text-recognition,可以尝试先移除它,只保留com.google.mlkit:text-recognition。
  5. 同步Gradle项目。

示例代码中的ML Kit集成

一旦依赖配置正确,您的MainActivity中的ML Kit文本识别逻辑应能正常工作。以下是关键部分的示例代码,展示了如何初始化和使用TextRecognizer:

package com.gorkemtand.textrecognition;

import android.app.ProgressDialog;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.PopupMenu;
import android.widget.Toast;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.imageview.ShapeableImageView;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.text.Text;
import com.google.mlkit.vision.text.TextRecognition;
import com.google.mlkit.vision.text.latin.TextRecognizerOptions; // 确保使用正确的TextRecognizerOptions

import java.io.IOException;
import java.util.Objects;
import android.Manifest; // 确保Manifest导入正确

public class MainActivity extends AppCompatActivity {

    private MaterialButton inputImageBtn;
    private MaterialButton recognizeTextBtn;
    private ShapeableImageView imageIv;
    private EditText recognizedTextEt;

    private static final String TAG = "MAIN_TAG";
    private Uri imageUri = null;

    private static final int CAMERA_REQUEST_CODE = 100;
    private static final int STORAGE_REQUEST_CODE = 101;

    private String[] cameraPermissions;
    private String[] storagePermissions;

    private ProgressDialog progressDialog;

    private TextRecognizer textRecognizer;

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

        // 初始化UI组件
        inputImageBtn = findViewById(R.id.inputImageBtn);
        recognizeTextBtn = findViewById(R.id.recognizeBtn);
        imageIv = findViewById(R.id.imageIv);
        recognizedTextEt = findViewById(R.id.recognizedTextEd);

        // 初始化权限数组
        cameraPermissions = new String[] {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE};
        storagePermissions = new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE};

        // 初始化进度对话框
        progressDialog = new ProgressDialog(this);
        progressDialog.setTitle("请稍候");
        progressDialog.setCanceledOnTouchOutside(false);

        // 初始化TextRecognizer
        // 使用TextRecognizerOptions.DEFAULT_OPTIONS来获取默认的设备端文本识别器
        textRecognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);

        // 设置按钮点击监听器
        inputImageBtn.setOnClickListener(v -> showInputImageDialog());
        recognizeTextBtn.setOnClickListener(v -> {
            if(imageUri == null){
                Toast.makeText(MainActivity.this,"请先选择图片...",Toast.LENGTH_SHORT).show();
            } else {
                recognizeTextFromImage();
            }
        });
    }

    private void recognizeTextFromImage() {
        Log.d(TAG, "recognizeTextFromImage: 开始识别文本");
        progressDialog.setMessage("准备图片...");
        progressDialog.show();

        try {
            // 从Uri创建InputImage
            InputImage inputImage = InputImage.fromFilePath(this, imageUri);
            progressDialog.setMessage("识别文本中...");

            // 处理图片进行文本识别
            textRecognizer.process(inputImage)
                    .addOnSuccessListener(text -> {
                        // 识别成功
                        progressDialog.dismiss();
                        String recognizedText = text.getText();
                        Log.d(TAG, "onSuccess: 识别到的文本: "+recognizedText);
                        recognizedTextEt.setText(recognizedText);
                    })
                    .addOnFailureListener(e -> {
                        // 识别失败
                        progressDialog.dismiss();
                        Log.e(TAG, "onFailure: 文本识别失败", e);
                        Toast.makeText(MainActivity.this,"文本识别失败: "+e.getMessage(),Toast.LENGTH_SHORT).show();
                    });
        } catch (IOException e) {
            // 处理图片准备失败
            progressDialog.dismiss();
            Log.e(TAG, "recognizeTextFromImage: 准备图片失败", e);
            Toast.makeText(MainActivity.this,"准备图片失败: "+e.getMessage(),Toast.LENGTH_SHORT).show();
        }
    }

    // 省略 showInputImageDialog, pickImageGallery, pickImageCamera,
    // ActivityResultLauncher, 权限检查和请求等相关方法,
    // 这些方法在原代码中已经实现,且与ML Kit核心问题无关,但对于完整的应用是必需的。
    // 确保这些方法中的权限处理和图片选择逻辑是健全的。

    private void showInputImageDialog() {
        PopupMenu popupMenu = new PopupMenu(this, inputImageBtn);
        popupMenu.getMenu().add(Menu.NONE,1,1,"相机");
        popupMenu.getMenu().add(Menu.NONE,2,2,"图库");
        popupMenu.show();
        popupMenu.setOnMenuItemClickListener(item -> {
            int id = item.getItemId();
            if(id == 1){
                Log.d(TAG, "onMenuItemClick: 点击相机...");
                if(checkCameraPermissions()){
                    pickImageCamera();
                } else {
                    requestCameraPermissions();
                }
            } else if(id == 2){
                Log.d(TAG, "onMenuItemClick: 点击图库...");
                if(checkStoragePermision()){
                    pickImageGallery();
                } else {
                    requestStoragePermission();
                }
            }
            return false;
        });
    }

    private void pickImageGallery(){
        Log.d(TAG, "pickImageGallery: ");
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType("image/*");
        galleryActivityResultLauncher.launch(intent);
    }

    private ActivityResultLauncher<Intent> galleryActivityResultLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            result -> {
                if(result.getResultCode() == RESULT_OK){
                    Intent data = result.getData();
                    if (data != null) {
                        imageUri = data.getData();
                        Log.d(TAG, "onActivityResult: imageUri "+imageUri);
                        imageIv.setImageURI(imageUri);
                    }
                } else {
                    Log.d(TAG, "onActivityResult: 取消");
                    Toast.makeText(MainActivity.this,"已取消...",Toast.LENGTH_SHORT).show();
                }
            }
    );

    private void pickImageCamera(){
        Log.d(TAG, "pickImageCamera: ");
        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.TITLE, "示例标题");
        values.put(MediaStore.Images.Media.DESCRIPTION, "示例描述");
        imageUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values);
        Intent intent  = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
        cameraActivityResultLauncher.launch(intent);
    }

    private ActivityResultLauncher<Intent> cameraActivityResultLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            result -> {
                if(result.getResultCode() == RESULT_OK){
                    Log.d(TAG, "onActivityResult: imageUri "+imageUri);
                    imageIv.setImageURI(imageUri);
                } else {
                    Log.d(TAG, "onActivityResult: 取消");
                    Toast.makeText(MainActivity.this,"已取消", Toast.LENGTH_SHORT).show();
                }
            }
    );

    private boolean checkStoragePermision(){
        return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == (PackageManager.PERMISSION_GRANTED);
    }

    private void requestStoragePermission(){
        ActivityCompat.requestPermissions(this,storagePermissions,STORAGE_REQUEST_CODE);
    }

    private boolean checkCameraPermissions(){
        boolean cameraResult = ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA) == (PackageManager.PERMISSION_GRANTED);
        boolean storageResult = ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE) == (PackageManager.PERMISSION_GRANTED);
        return cameraResult && storageResult;
    }

    private void requestCameraPermissions(){
        ActivityCompat.requestPermissions(this,cameraPermissions, CAMERA_REQUEST_CODE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case CAMERA_REQUEST_CODE:{
                if (grantResults.length > 0){
                    boolean cameraAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                    boolean storageAccepted = grantResults[1] == PackageManager.PERMISSION_GRANTED;
                    if(cameraAccepted && storageAccepted){
                        pickImageCamera();
                    } else {
                        Toast.makeText(this, "需要相机和存储权限", Toast.LENGTH_SHORT).show();
                    }
                } else {
                    Toast.makeText(this,"已取消",Toast.LENGTH_SHORT).show();
                }
            }
            break;
            case STORAGE_REQUEST_CODE:{
                if(grantResults.length > 0){
                    boolean storageAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                    if (storageAccepted) {
                        pickImageGallery();
                    } else {
                        Toast.makeText(this, "需要存储权限", Toast.LENGTH_SHORT).show();
                    }
                }
            }
            break;
        }
    }
}
登录后复制

AndroidManifest.xml 权限配置: 确保您的AndroidManifest.xml文件中声明了必要的权限:

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 如果目标API级别是33或更高,可以考虑使用READ_MEDIA_IMAGES替代WRITE_EXTERNAL_STORAGE -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
登录后复制

对于Android 13 (API 33) 及更高版本,推荐使用READ_MEDIA_IMAGES权限来访问媒体文件,而不是WRITE_EXTERNAL_STORAGE。

注意事项与最佳实践

  1. 依赖版本管理: 始终关注ML Kit的官方文档,使用最新且稳定的依赖版本。beta版本可能存在不稳定性。
  2. 网络连接: 尽管com.google.mlkit:text-recognition库包含了设备端模型,但在某些情况下,ML Kit可能仍需要网络连接来下载语言包或其他更新。确保设备有稳定的网络连接。
  3. 设备兼容性: ML Kit设备端模型通常在大多数现代Android设备上运行良好。对于非常老的设备,性能可能会有所下降。
  4. 模型选择: TextRecognizerOptions.DEFAULT_OPTIONS会使用一个通用的设备端模型。如果您需要识别特定语言(如中文、日文等),则需要使用对应的TextRecognizerOptions,例如TextRecognizerOptions.CHINESE_SCRIPT。这些特定的语言模型可能需要额外的下载。
  5. 错误处理: 在实际应用中,对ML Kit的识别任务(addOnSuccessListener和addOnFailureListener)进行健壮的错误处理至关重要,以提供良好的用户体验。
  6. 权限管理: 在Android 6.0 (API 23) 及更高版本上,运行时权限是必需的。务必在代码中正确检查和请求相机和存储权限。

总结

“Waiting for the text optional module to be downloaded”错误是ML Kit文本识别集成中常见的陷阱,但通过选择正确的com.google.mlkit:text-recognition依赖库,可以有效地解决此问题。理解不同ML Kit依赖的用途,并遵循官方推荐的集成方式,是确保应用稳定性和性能的关键。通过本文提供的解决方案和注意事项,开发者可以更顺利地在Android应用中实现强大的文本识别功能。

以上就是解决Android ML Kit文本识别模块下载失败的教程的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号