Cordova Android 自定义插件/应用程序 - 自定义插件上未找到类异常

Posted

技术标签:

【中文标题】Cordova Android 自定义插件/应用程序 - 自定义插件上未找到类异常【英文标题】:Cordova Android Custom Plugin/App - Class Not Found Exception on Custom Plugin 【发布时间】:2018-01-12 15:41:57 【问题描述】:

我正在为 Cordova 设计一个自定义插件作为教育演示。该插件与 android PdfRenderer 类集成,并将 API 公开给 Cordova 生态系统。

我的自定义插件正在编译且没有错误,我已将该插件添加到我的测试项目中。但是,当我构建并运行测试项目时,应用程序在启动时崩溃,我收到以下消息:

java.lang.ClassNotFoundException: com.dev.plugin.PdfRendererPlugin

我检查了我的platforms/android/ 文件夹,插件类如预期的那样位于src/com/dev/plugin/PdfRenderPlugin.java

在这种情况下我还应该寻找什么?如果插件编译并添加到项目中没有错误,我的应用程序现在应该可以正常工作了。

这里有一些代码可以使用:

插件.xml

<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
    id="cordova-android-pdf-renderer-plugin" version="0.2.3">
<name>PdfRendererPlugin</name>
<description>Cordova PDF Renderer Plugin</description>
<license>MIT</license>
<keywords>cordova,pdf,renderer</keywords>

<platform name="android">
    <js-module src="www/js/PdfRendererPlugin.js" name="PdfRendererPlugin">
        <runs/>

        <clobbers target="PdfRendererPlugin" />
    </js-module>

    <config-file target="config.xml" parent="/*">
        <feature name="com.dev.plugin.PdfRendererPlugin">
            <param name="android-package" value="com.dev.plugin.PdfRendererPlugin"/>
            <param name="onload" value="true" />
        </feature>
    </config-file>

    <source-file src="src/android/PdfRendererPlugin.java" target-dir="src/com/dev/plugin/" />
</platform>
</plugin>

插件类(PdfRendererService)

package com.dev.plugin.PdfRendererService;

import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.PluginResult;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import android.content.pm.PackageManager;

import android.graphics.Bitmap;
import android.graphics.pdf.PdfRenderer;
import android.graphics.pdf.PdfRenderer.Page;

import android.os.ParcelFileDescriptor;

import android.util.Log;

/**
 * This class handles a pdf file called from javascript and converts a selected 
page (default is first) to a byte array representing a bitmap.
 */
public class PdfRendererPlugin extends CordovaPlugin 

private static final String LOG_TAG = "PdfRendererPlugin";

private ParcelFileDescriptor fileDescriptor = null;
private PdfRenderer renderer = null;
private Page currentPage = null;

private int mWidth = 400, mHeight = 600;
private String mRenderMode = "display";

@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView)
    Log.d(LOG_TAG, "initialize");
    super.initialize(cordova, webView);


@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException 
    Log.d(LOG_TAG, "execute");

    //No Switch -> src 1.6
    if(action.equals("open"))
        return executeOpen(args, callbackContext);
    
    else if(action.equals("renderPage"))
        return executeRenderPage(args, callbackContext);
    
    else if(action.equals("pageCount"))
        callbackContext.success(this.getPageCount());
        return true;
    
    else if(action.equals("close"))
        this.closeRenderer();
        callbackContext.success();
        return true;
    

    return false;


private boolean executeOpen(JSONArray args, CallbackContext callbackContext)
    Log.d(LOG_TAG, "executeOpen");
    String filePath = "";
    try
        if(args.length() < 1)
            Log.e(LOG_TAG, "No arguments provided. Exiting process.");
            callbackContext.error("No arguments provided. Exiting process.");
            return true;
        
        else if(args.length() < 2)
            Log.e(LOG_TAG, "Insufficient arguments provided. Exiting process.");
            callbackContext.error("Insufficient arguments provided. Exiting process.");
            return true;
        
        if(args.length() > 3)
            mWidth = args.getInt(2);
            mHeight = args.getInt(3);
        

        filePath = args.getString(0);
        mRenderMode = args.getString(1);
    
    catch(JSONException je)
        String msg = je.getMessage();
        if(msg == null)
            msg = "Unknown JSONException has occurred";
        Log.e(LOG_TAG, msg);
    

    this.initializeRenderer(filePath, callbackContext);

    boolean isPageOpen = this.openPage(0, callbackContext);
    if(isPageOpen)
        Bitmap bitmap = getBitmap(mWidth, mHeight);
        this.sendBitmapAsBytes(0, bitmap, callbackContext);
    
    return true;


private boolean executeRenderPage(JSONArray args, CallbackContext callbackContext)
    Log.d(LOG_TAG, "executeRenderPage");
    int pageNo = -1;
    try 
        if (args.length() < 1) 
            Log.e(LOG_TAG, "No arguments provided. Exiting process.");
            callbackContext.error("No arguments provided. Exiting process.");
            return true;
        
        if (args.length() > 1) 
            mRenderMode = args.getString(1);
        
        if (args.length() > 3) 
            mWidth = args.getInt(2);
            mHeight = args.getInt(3);
        

        pageNo = args.getInt(0);
    
    catch(JSONException je)
        String msg = je.getMessage();
        if(msg == null)
            msg = "Unknown JSONException has occurred";
        Log.e(LOG_TAG, msg);
    

    if(pageNo < 0)
        return false;

    boolean isPageOpen = this.openPage(pageNo, callbackContext);
    if(isPageOpen) 
        Bitmap bitmap = getBitmap(mWidth, mHeight);
        this.sendBitmapAsBytes(pageNo, bitmap, callbackContext);
    
    return true;


/*
// Requests the permission to read from external storage if not already available
private void validatePermissions()
    Log.d(LOG_TAG, "validatePermissions");
    if(!cordova.hasPermission(READ_EXTERNAL_STORAGE))
        Log.i(LOG_TAG, "Requesting External Storage Read Permission...");
        cordova.requestPermission(this, CODE_READ_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE);
    

*/

private int getPageCount() 
    Log.d(LOG_TAG, "getPageCount");
    if(renderer == null)
        return 0;

    return renderer.getPageCount();


private void initializeWriteFileDescriptor(String filePath, CallbackContext callbackContext) throws FileNotFoundException, FileFormatException 
    Log.d(LOG_TAG, "initializeWriteFileDescriptor");
    fileDescriptor = null;

    if(filePath == null || filePath.length() < 1)
        throw new FileNotFoundException("The file path provided is not a valid file path.");

    String[] pathArr = filePath.split(".");
    int numSections = pathArr.length;
    String ext = pathArr[numSections - 1];

    if(!ext.equals("pdf"))
        throw new FileFormatException("Invalid File Extension provided to Pdf Render Service: " + ext);

    fileDescriptor = getWriteFileDescriptor(filePath);


private void initializeRenderer(String filePath, CallbackContext callbackContext)
    Log.d(LOG_TAG, "initializeRenderer");
    renderer = null;

    try 
        initializeWriteFileDescriptor(filePath, callbackContext);

        renderer = new PdfRenderer(fileDescriptor);
    
    catch(IOException io)
        String msg = io.getMessage();
        if(msg == null)
            msg = "An error has occurred while loading the requested file.";

        Log.e(LOG_TAG, msg);
        callbackContext.error(msg);
    


private void closeRenderer() 
    Log.d(LOG_TAG, "closeRenderer");
    if(renderer == null) 
        Log.w(LOG_TAG, "Attempted to close null renderer. Skipping operation.");
        return;
    

    renderer.close();


private boolean openPage(int index, CallbackContext callbackContext)
    Log.d(LOG_TAG, "openPage");
    currentPage = null;

    int pageCount = getPageCount();
    if(pageCount < 1) 
        Log.e(LOG_TAG, "Requested document has no pages to display.");
        callbackContext.error("Requested document has no pages to display.");
        return false;
    

    if(index >= pageCount || index < 0) 
        Log.e(LOG_TAG, String.format("No page was found at page number %d/%d", index, pageCount));
        callbackContext.error(String.format("No page was found at page number %d/%d", index, pageCount));
        return false;
    

    currentPage = renderer.openPage(index);
    return true;


private void sendBitmapAsBytes(int index, Bitmap bitmap, CallbackContext callbackContext)
    Log.d(LOG_TAG, "sendBitmapAsBytes");
    if(renderer == null) 
        Log.e(LOG_TAG, "Renderer was not properly initialized.");
        callbackContext.error("Renderer was not properly initialized.");
        return;
    

    if(currentPage == null) 
        Log.e(LOG_TAG, "Requested page could not be rendered.");
        callbackContext.error("Requested page could not be rendered.");
        return;
    

    int renderMode = mRenderMode.equals("print") ? Page.RENDER_MODE_FOR_PRINT : Page.RENDER_MODE_FOR_DISPLAY;
    currentPage.render(bitmap, null, null, renderMode);

    byte[] output = toByteArray(bitmap);
    if(output == null || output.length < 1) 
        Log.e(LOG_TAG, "Bitmap Error has occurred: Invalid Output Format Detected");
        callbackContext.error("Bitmap Error has occurred: Invalid Output Format Detected");
    
    else 
        Log.i(LOG_TAG, "Bitmap Conversion Successful");
        callbackContext.success(output);
    


private static byte[] toByteArray(Bitmap bitmap)
    Log.d(LOG_TAG, "toByteArray");
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);

    return stream.toByteArray();


private static Bitmap getBitmap(int width, int height)
    Log.d(LOG_TAG, "getBitmap");
    return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);


private static ParcelFileDescriptor getWriteFileDescriptor(String filePath) throws FileNotFoundException 
    Log.d(LOG_TAG, "getWriteFileDescriptor");
    File file = new File(filePath);
    final int fileMode = ParcelFileDescriptor.MODE_TRUNCATE |
                         ParcelFileDescriptor.MODE_CREATE   |
                         ParcelFileDescriptor.MODE_WRITE_ONLY;

    return ParcelFileDescriptor.open(file, fileMode);


class FileFormatException extends IOException 
    FileFormatException(String msg)
        super(msg);
    


插件JS接口

var PLUGIN_NAME = "PdfRendererPlugin";

var SERVICE_OPEN = "open";
var SERVICE_CLOSE = "close";
var SERVICE_PAGE_COUNT = "pageCount";
var SERVICE_RENDER_PAGE = "renderPage";

var RENDER_MODE_DISPLAY = "display";
var RENDER_MODE_PRINT = "print";

var PdfRendererPlugin = 
display: function(filePath, callback)
    cordova.exec(callback, function(err)
        // console.log(err);
    , PLUGIN_NAME, SERVICE_OPEN, [filePath, RENDER_MODE_DISPLAY]);
,

displayWithDimensions: function(filePath, width, height, callback)
    cordova.exec(callback, function(err)
        // console.log(err);
    , PLUGIN_NAME, SERVICE_OPEN, [filePath, RENDER_MODE_DISPLAY, width, height]);
,

print: function(filePath, callback)
    cordova.exec(callback, function(err)
       // console.log(err);
    , PLUGIN_NAME, SERVICE_OPEN, [filePath, RENDER_MODE_PRINT]);
,

printWithDimensions: function(filePath, width, height, callback)
    cordova.exec(callback, function(err)
       // console.log(err);
    , PLUGIN_NAME, SERVICE_OPEN, [filePath, RENDER_MODE_PRINT, width, height]);
,

renderPage: function(pageNo, callback)
    cordova.exec(callback, function(err)
        //console.log(err);
    , PLUGIN_NAME, SERVICE_RENDER_PAGE, [pageNo]);
,

renderPageForDisplay: function(pageNo, callback)
    cordova.exec(callback, function(err)
       // console.log(err);
    , PLUGIN_NAME, SERVICE_RENDER_PAGE, [pageNo, RENDER_MODE_DISPLAY]);
,

renderPageForDisplayWithDimensions: function(pageNo, width, height, callback)
    cordova.exec(callback, function(err)
       // console.log(err);
    , PLUGIN_NAME, SERVICE_RENDER_PAGE, [pageNo, RENDER_MODE_DISPLAY, width, height]);
,

renderPageForPrint: function(pageNo, callback)
    cordova.exec(callback, function(err)
       // console.log(err);
    , PLUGIN_NAME, SERVICE_RENDER_PAGE, [pageNo, RENDER_MODE_PRINT]);
,

renderPageForPrintWithDimensions: function(pageNo, width, height, callback)
    cordova.exec(callback, function(err)
        //console.log(err);
    , PLUGIN_NAME, SERVICE_RENDER_PAGE, [pageNo, RENDER_MODE_PRINT, width, height]);
,

close: function(callback)
    cordova.exec(callback, function(err)
      //  console.log(err);
    , PLUGIN_NAME, SERVICE_CLOSE, []);
,

getPageCount: function(callback)
    cordova.exec(callback, function(err)
       // console.log(err);
    , PLUGIN_NAME, SERVICE_PAGE_COUNT, []);

;

测试应用 index.html

<!DOCTYPE html>
<html>
<head>
    <title>Cordova PDF Generator Plugin Test</title>

    <meta name="viewport" content="user-scalable=no, initial-scale=1,
          maximum-scale=1, minimum-scale=1, width=device-width,
          height=device-height" />
</head>
<body>
    <div class="app">
        <h1>Cordova PDF Generation Plugin Test</h1>

        <div>
            <button id="display-button" onclick="display()">Display (View)</button>
            <button id="print-button" onclick="print()">Display (Print)</button>
        </div>
    </div>

    <script type="text/javascript" src="cordova.js"></script>
    <script type="text/javascript" src="js/index.js"></script>
</body>
</html>

测试应用 index.js

var testFilePath = 'assets/test-file.pdf';

var app = 
// Application Constructor
initialize: function() 
    document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
,

// deviceready Event Handler
//
// Bind any cordova events here. Common events are:
// 'pause', 'resume', etc.
onDeviceReady: function() 
    display();

;

var display = function()
PdfRendererPlugin.display(testFilePath, function(data)
        console.log('Bitmap Bytes');
        console.log(data);
    );
;

var print = function()
PdfRendererPlugin.print(testFilePath, function(data)
        console.log('Bitmap Bytes');
        console.log(data);
    );
;

app.initialize();

【问题讨论】:

【参考方案1】:

我觉得自己很傻。问题出在包声明上。我没有考虑就将类名添加到包声明中(一定是因为我从 plugin.xml 中复制并粘贴了包含类名的路径)。

【讨论】:

以上是关于Cordova Android 自定义插件/应用程序 - 自定义插件上未找到类异常的主要内容,如果未能解决你的问题,请参考以下文章

Android Cordova 插件开发之编写自定义插件

ionic2/cordova自定义插件集成aar包

使用自定义cordova插件找不到类异常

Cordova与现有框架的结合,Cordova插件使用教程,Cordova自定义插件,框架集成Cordova,将Cordova集成到现有框架中

无法在 Cordova/Phonegap 中编辑自定义 Java 插件

Cordova iOS 自定义插件:处理内存警告