Using JavaFaces in Android

纵饮孤独 提交于 2019-12-26 15:05:55

问题


I download and test JavaFaces as a java project in Windows.

but i'am tring to use it in android framework , but BufferedImage is not available in android !

how can i make this project compatible with android ? here is FaceRec.java that contain errors about BufferedImage.

public class FaceRec {

    private FaceBundle bundle;
    private double[][] weights;
    Handler            fh;
    Logger             logger = Logger.getLogger("facerecognition.javafaces.FaceRec");


    public FaceRec() {
        try {
            fh = new FileHandler("facerec.log");
            fh.setFormatter(new SimpleFormatter());
            logger.addHandler(fh);
        }
        catch (SecurityException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }


    public MatchResult findMatchResult(String imageFileName, int selectedeigenfaces, double thresholdVal) {
        boolean match = false;
        String message = null;
        String matchingFileName = "";
        double minimumDistance = 0.0;
        try {
            checkImageSizeCompatibility(imageFileName);
            Matrix2D inputFace = getNormalisedInputFace(imageFileName);

            inputFace.subtract(new Matrix2D(bundle.getAvgFace(), 1));
            Matrix2D inputWts = getInputWeights(selectedeigenfaces, inputFace);
            double[] distances = getDistances(inputWts);
            ImageDistanceInfo distanceInfo = getMinimumDistanceInfo(distances);
            minimumDistance = Math.sqrt(distanceInfo.getValue());
            matchingFileName = getMatchingFileName(distanceInfo);

            if (minimumDistance > thresholdVal) {
                message = "no match found, try higher threshold";
            } else {
                match = true;
                message = "matching image found";
            }
        }
        catch (Exception e) {
            return new MatchResult(false, "", Double.NaN, e.getMessage());
        }
        return new MatchResult(match, matchingFileName, minimumDistance, message);
    }


    private void checkImageSizeCompatibility(String fileName) throws IOException, FaceRecError {

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(fileName, options);

        int height = bitmap.getHeight();
        int width = bitmap.getHeight();

        int facebundleWidth = this.bundle.getImageWidth();
        int facebundleHeight = this.bundle.getImageHeight();
        if ((height != facebundleHeight) || (width != facebundleWidth)) {
            throw new FaceRecError("selected image dimensions does not match dimensions of other images");
        }

    }


    private String getMatchingFileName(ImageDistanceInfo distanceInfo) {
        List<String> imageFileNames = this.bundle.getImageFileNamesList();
        String matchingFileName = imageFileNames.get(distanceInfo.getIndex());
        return matchingFileName;
    }


    private Matrix2D getInputWeights(int selectedeigenfaces, Matrix2D inputFace) {
        double[][] eigenFacesArray = this.bundle.getEigenFaces();
        Matrix2D eigenFacesMatrix = new Matrix2D(eigenFacesArray);
        Matrix2D eigenFacesMatrixPart = eigenFacesMatrix.getSubMatrix(selectedeigenfaces);
        Matrix2D eigenFacesMatrixPartTranspose = eigenFacesMatrixPart.transpose();
        Matrix2D inputWts = inputFace.multiply(eigenFacesMatrixPartTranspose);
        return inputWts;
    }


    private Matrix2D getNormalisedInputFace(String imageFileName) throws FaceRecError {
        double[] inputFaceData = getImageData(imageFileName);
        Matrix2D inputFace = new Matrix2D(inputFaceData, 1);
        //logger.info("inputface");
        //logger.info(inputFace.toString());
        inputFace.normalise();
        //logger.info("normalised inputface");
        //logger.info(inputFace.toString());
        return inputFace;
    }


    private ImageDistanceInfo getMinimumDistanceInfo(double[] distances) {
        double minimumDistance = Double.MAX_VALUE;
        int index = 0;
        for (int i = 0; i < distances.length; i++) {
            if (distances[i] < minimumDistance) {
                minimumDistance = distances[i];
                index = i;
            }
        }
        return new ImageDistanceInfo(distances[index], index);
    }


    private double[] getDistances(Matrix2D inputWt) {
        Matrix2D tempWt = new Matrix2D(this.weights);
        double[] inputWtData = inputWt.flatten();
        tempWt.subtractFromEachRow(inputWtData);
        tempWt.multiplyElementWise(tempWt);
        double[][] temp = tempWt.toArray();
        double[] distances = new double[temp.length];
        for (int i = 0; i < temp.length; i++) {
            double sum = 0.0;
            for (int j = 0; j < temp[0].length; j++) {
                sum += temp[i][j];
            }
            distances[i] = sum;
        }
        return distances;
    }


    private double[] getImageData(String imageFileName) throws FaceRecError {
        BufferedImage bi = null;
        double[] inputFace = null;
        try {
            bi = ImageIO.read(new File(imageFileName));
        }
        catch (IOException ioe) {
            throw new FaceRecError(ioe.getMessage());
        }
        if (bi != null) {
            int imageWidth = bi.getWidth();
            int imageHeight = bi.getHeight();
            inputFace = new double[imageWidth * imageHeight];
            bi.getData().getPixels(0, 0, imageWidth, imageHeight, inputFace);
        }
        return inputFace;
    }


    private void doCalculations(String dir, List<String> imglist, int selectedNumOfEigenFaces) throws FaceRecError, IOException {
        FaceBundle b = createFaceBundle(imglist);
        double[][] wts = calculateWeights(b, selectedNumOfEigenFaces);
        this.bundle = b;
        this.weights = wts;
        writeCache(dir, b);
    }


    private double[][] calculateWeights(FaceBundle b, int selectedNumOfEigenFaces) {
        Matrix2D eigenFaces = new Matrix2D(b.getEigenFaces());
        Matrix2D eigenFacesPart = eigenFaces.getSubMatrix(selectedNumOfEigenFaces);
        Matrix2D adjustedFaces = new Matrix2D(b.getAdjustedFaces());
        Matrix2D eigenFacesPartTr = eigenFacesPart.transpose();
        Matrix2D wts = adjustedFaces.multiply(eigenFacesPartTr);
        return wts.toArray();
    }


    public FaceBundle createFaceBundle(List<String> filenames) throws FaceRecError, IOException {
        BufferedImage[] bufimgs = getGrayScaleImages(filenames);
        checkImageDimensions(filenames, bufimgs);
        Matrix2D imagesData = getNormalisedImagesData(bufimgs);
        double[] averageFace = imagesData.getAverageOfEachColumn();
        imagesData.adjustToZeroMean();
        //logger.info("imagesData adjusted ToZeroMean");
        //logger.info(imagesData.toString());
        EigenvalueDecomposition egdecomp = getEigenvalueDecomposition(imagesData);
        double[] eigenvalues = egdecomp.getEigenValues();
        double[][] eigvectors = egdecomp.getEigenVectors();

        //logger.info("eigenvalues");
        //logger.info(new Matrix2D(eigenvalues,1).toString());

        //logger.info("eigvectors");
        //logger.info(new Matrix2D(eigvectors).toString());

        TreeSet<ValueIndexPair> pairList = getSortedPairs(eigenvalues, eigvectors);
        eigenvalues = getSortedVector(pairList);
        eigvectors = getSortedMatrix(eigvectors, pairList);

        //logger.info("AFTER SORTING");
        //logger.info("eigenvalues");
        //logger.info(new Matrix2D(eigenvalues,1).toString());

        //logger.info("eigvectors");
        //logger.info(new Matrix2D(eigvectors).toString());

        Matrix2D eigenFaces = getNormalisedEigenFaces(imagesData, new Matrix2D(eigvectors));
        int imageWidth = bufimgs[0].getWidth();
        createEigenFaceImages(eigenFaces, imageWidth);
        int imageHeight = bufimgs[0].getHeight();
        FaceBundle b = new FaceBundle(filenames, imagesData.toArray(), averageFace, eigenFaces.toArray(), eigenvalues, imageWidth, imageHeight);
        return b;
    }


    public double[] getSortedVector(TreeSet<ValueIndexPair> pairSet) {
        double[] sortedVector = new double[pairSet.size()];
        ValueIndexPair[] viArray = pairSet.toArray(new ValueIndexPair[0]);
        for (int i = 0; i < pairSet.size(); i++) {
            sortedVector[i] = viArray[i].getVectorElement();
        }
        return sortedVector;
    }


    public double[][] getSortedMatrix(double[][] origmatrix, TreeSet<ValueIndexPair> pairSet) {
        int rows = pairSet.size();
        int cols = origmatrix[0].length;
        double[][] sortedMatrix = new double[rows][cols];
        ValueIndexPair[] viArray = pairSet.toArray(new ValueIndexPair[0]);
        //fill a 2D array using data from rows of original matrix
        for (int i = 0; i < pairSet.size(); i++) {
            sortedMatrix[i] = origmatrix[viArray[i].getMatrixRowIndex()];
        }

        return sortedMatrix;
    }


    public TreeSet<ValueIndexPair> getSortedPairs(double[] aVector,
                                                  double[][] aMatrix) {
        TreeSet<ValueIndexPair> pairSet = createPairs(aVector, aMatrix);
        return pairSet;
    }


    public TreeSet<ValueIndexPair> createPairs(double[] aVector, double[][] aMatrix) {
        TreeSet<ValueIndexPair> pList = null;
        if (aVector.length != aMatrix.length) {
            printError("matrix rows don't match items in vector ");
        } else {
            pList = new TreeSet<ValueIndexPair>();
            for (int i = 0; i < aVector.length; i++) {
                ValueIndexPair dp = new ValueIndexPair(aVector[i], i);
                pList.add(dp);
            }
        }
        return pList;
    }


    private EigenvalueDecomposition getEigenvalueDecomposition(
                                                               Matrix2D imagesData) {
        Matrix2D imagesDataTr = imagesData.transpose();
        Matrix2D covarianceMatrix = imagesData.multiply(imagesDataTr);
        EigenvalueDecomposition egdecomp = covarianceMatrix.getEigenvalueDecomposition();
        return egdecomp;
    }


    public void createEigenFaceImages(Matrix2D eigenfaces, int imgwidth) throws IOException {
        logger.info("creating eigenfaces");
        double[][] eigenfacesArray = eigenfaces.toArray();
        String fldrname = ".." + File.separator + "eigenfaces";
        makeNewFolder(fldrname);
        String prefix = "eigen";
        String ext = ".png";
        for (int i = 0; i < eigenfacesArray.length; i++) {
            double[] egface = eigenfacesArray[i];
            String filename = fldrname + File.separator + prefix + i + ext;
            createImageFromArray(filename, egface, imgwidth);
        }
        logger.info("created eigenfaces.");
    }


    private Matrix2D getNormalisedEigenFaces(Matrix2D imagesData, Matrix2D eigenVectors) {
        Matrix2D eigenFaces = eigenVectors.multiply(imagesData);
        double[][] eigenFacesData = eigenFaces.toArray();
        for (int i = 0; i < eigenFacesData.length; i++) {
            double norm = Matrix2D.norm(eigenFacesData[i]);
            for (int j = 0; j < eigenFacesData[i].length; j++) {
                double v = eigenFacesData[i][j];
                eigenFacesData[i][j] = v / norm;
            }
        }
        return new Matrix2D(eigenFacesData);
    }


    private Matrix2D getNormalisedImagesData(BufferedImage[] bufImgs) {
        int imageWidth = bufImgs[0].getWidth();
        int imageHeight = bufImgs[0].getHeight();
        int rows = bufImgs.length;
        int cols = imageWidth * imageHeight;
        double[][] data = new double[rows][cols];
        for (int i = 0; i < rows; i++) {
            bufImgs[i].getData().getPixels(0, 0, imageWidth, imageHeight, data[i]);
        }
        Matrix2D imagesData = new Matrix2D(data);
        //logger.info("images before normalisation");
        //logger.info(imagesData.toString());
        imagesData.normalise();
        //logger.info("images normalised");
        //logger.info(imagesData.toString());
        return imagesData;
    }


    private void checkImageDimensions(List<String> filenames,
                                      BufferedImage[] bufimgs) throws FaceRecError {
        int imgheight = 0;
        int imgwidth = 0;
        for (int i = 0; i < bufimgs.length; i++) {
            if (i == 0) {
                imgheight = bufimgs[i].getHeight();
                imgwidth = bufimgs[i].getWidth();
            }
            if ((imgheight != bufimgs[i].getHeight()) || (imgwidth != bufimgs[i].getWidth())) {
                String response = "all images should have same dimensions! " + filenames.get(i) + " is of diff size";
                logger.log(Level.SEVERE, response);
                throw new FaceRecError(response);
            }
        }
    }


    public BufferedImage[] getGrayScaleImages(List<String> filenames) throws FaceRecError {
        BufferedImage b = null;
        BufferedImage[] bufimgs = new BufferedImage[filenames.size()];
        Iterator<String> it = filenames.iterator();
        int i = 0;
        while (it.hasNext()) {
            String fn = it.next();
            File f = new File(fn);
            if (f.isFile()) {
                try {
                    b = ImageIO.read(new File(fn));
                }
                catch (IOException ioe) {
                    throw new FaceRecError(ioe.getMessage());
                }
                if (b != null) {
                    b = convertToGray(b);
                    bufimgs[i++] = b;
                }
            }
        }
        return bufimgs;
    }


    private BufferedImage convertToGray(BufferedImage img) throws FaceRecError {
        BufferedImage gray = null;
        try {

            gray = new BufferedImage(img.getWidth(), img.getHeight(),
                    BufferedImage.TYPE_BYTE_GRAY);
            ColorConvertOp op = new ColorConvertOp(
                    img.getColorModel().getColorSpace(),
                    gray.getColorModel().getColorSpace(), null);
            op.filter(img, gray);
            return gray;
        }
        catch (Exception e) {
            logger.log(Level.SEVERE, "grayscale conversion failed", e);
            throw new FaceRecError("grayscale conversion failed:\n" + e.getMessage());
        }
    }


    private void writeCache(String dir, FaceBundle cachedata) throws IOException {
        FileOutputStream fout = null;
        ObjectOutputStream fos = null;
        fout = new FileOutputStream(dir + File.separator + "mycache.cache");
        fos = new ObjectOutputStream(fout);
        fos.writeObject(cachedata);
        logger.info("wrote cache");
        //fos.close();
        fout.close();
    }


    private FaceBundle getOldFacesBundle(String dir) throws IOException, ClassNotFoundException {
        FileInputStream fin = new FileInputStream(dir + File.separator + "mycache.cache");
        ObjectInputStream fo = new ObjectInputStream(fin);
        FaceBundle oldBundle = (FaceBundle) fo.readObject();
        fo.close();
        //fin.close();
        return oldBundle;
    }


    private void validateSelectedEigenFacesNumber(int selectedNumOfEigenFaces,
                                                  List<String> newFileNames) throws FaceRecError {
        int numImgs = newFileNames.size();
        if (selectedNumOfEigenFaces <= 0 || selectedNumOfEigenFaces >= numImgs) {
            logger.log(Level.SEVERE, "incorrect number of selectedeigenfaces" + selectedNumOfEigenFaces + "used" + "allowed btw 0-" + numImgs);
            throw new FaceRecError("incorrect number of selectedeigenfaces used..\n use a number between 0 and upto " + (numImgs - 1));
        }
    }


    private List<String> getFileNames(String dir, String[] children) {
        java.util.List<String> imageFileNames = new java.util.ArrayList<String>();
        for (String i: children) {
            String fileName = dir + File.separator + i;
            imageFileNames.add(fileName);
        }
        Collections.sort(imageFileNames);
        return imageFileNames;
    }


    public List<String> parseDirectory(String directoryName, String extension) throws FaceRecError {
        final String ext = "." + extension;
        String[] children = null;
        File directory = new File(directoryName);

        if (directory.isDirectory()) {
            children = directory.list(new FilenameFilter() {

                @Override
                public boolean accept(File f, String name) {
                    return name.endsWith(ext);
                }
            });
        } else {
            throw new FaceRecError(directoryName + " is not a directory");
        }
        return getFileNames(directoryName, children);
    }


    public void checkCache(String dir, String extension, int selectedNumOfEigenFaces) throws FaceRecError, IOException {
        List<String> newFileNames = parseDirectory(dir, extension);
        FaceBundle oldBundle = null;
        try {
            validateSelectedEigenFacesNumber(selectedNumOfEigenFaces, newFileNames);
            oldBundle = getOldFacesBundle(dir);
            processCache(dir, newFileNames, oldBundle, selectedNumOfEigenFaces);
        }
        catch (FileNotFoundException e) {
            logger.info("cache file not found");
            doCalculations(dir, newFileNames, selectedNumOfEigenFaces);
        }
        catch (Exception e) {
            throw new FaceRecError(e.getMessage());
        }
    }


    private void processCache(String dir, List<String> newFileNames, FaceBundle oldBundle, int selectedNumOfEigenFaces) throws FaceRecError, IOException {
        List<String> oldFileNames = oldBundle.getImageFileNamesList();
        if (newFileNames.equals(oldFileNames)) {
            this.bundle = oldBundle;
            this.weights = calculateWeights(oldBundle, selectedNumOfEigenFaces);
        }
        else {
            logger.info("folder contents changed");
            doCalculations(dir, newFileNames, selectedNumOfEigenFaces);
        }
    }


    private void createImageFromArray(String filename, double[] imgdata, int wd) throws IOException {
        BufferedImage bufimg = new BufferedImage(wd, imgdata.length / wd, BufferedImage.TYPE_BYTE_GRAY);
        Raster rast = bufimg.getData();
        WritableRaster wr = rast.createCompatibleWritableRaster();
        double maxValue = Double.MIN_VALUE;
        double minValue = Double.MAX_VALUE;
        for (int i = 0; i < imgdata.length; i++) {
            maxValue = Math.max(maxValue, imgdata[i]);
            minValue = Math.min(minValue, imgdata[i]);
        }
        for (int j = 0; j < imgdata.length; j++) {
            imgdata[j] = ((imgdata[j] - minValue) * 255) / (maxValue - minValue);
        }
        wr.setPixels(0, 0, wd, imgdata.length / wd, imgdata);
        bufimg.setData(wr);
        File newfile = new File(filename);
        ImageIO.write(bufimg, "png", newfile);

    }


    private void makeNewFolder(String fldr) {
        File folder = new File(fldr);
        if (folder.isDirectory()) {
            printError("folder:" + fldr + " exists");
            deleteContents(folder);
        }
        else {
            printError("no such folder as:" + fldr);
            boolean madeFolder = folder.mkdir();
            if ( !madeFolder) {
                printError("could not create folder :" + fldr);

            }
        }
    }


    private void deleteContents(File f) {
        File[] files = f.listFiles();
        for (File i: files) {
            delete(i);
        }
    }


    private void delete(File i) {
        if (i.isFile()) {
            boolean deleted = i.delete();
            if ( !deleted) {
                printError("file:" + i.getPath() + "could not be deleted");
            }
        }
    }


    public void reconstructFaces(int selectedeigenfaces) throws IOException {
        double[][] eigenfacesArray = bundle.getEigenFaces();
        Matrix2D egnfacesMatrix = new Matrix2D(eigenfacesArray);
        Matrix2D egnfacesSubMatrix = egnfacesMatrix.getSubMatrix(selectedeigenfaces);
        double[] eigenvalues = bundle.getEigenValues();
        Matrix2D eigenvalsMatrix = new Matrix2D(eigenvalues, 1);
        Matrix2D eigenvalsSubMatrix = eigenvalsMatrix.transpose().getSubMatrix(selectedeigenfaces);

        /*
         * the term 'phi' is used to denote mean subtracted reconstructed imagedata  
         * since that term appears in T&P doc.The term 'xnew' denotes phi+average_image
         *
         **/
        double[][] phi = getPhiData(egnfacesSubMatrix, eigenvalsSubMatrix);
        double[][] xnew = addAverageFaceData(phi);
        String reconFolderName = ".." + File.separator + "reconfaces";
        String ext = ".png";
        reconstructPhiImages(phi, reconFolderName, ext);
        reconstructOriginalImages(xnew, reconFolderName, ext);
        logger.info("reconstruction over");
    }


    private double[][] getPhiData(Matrix2D egnfacesSubMatrix, Matrix2D eigenvalsSubMatrix) {
        double[] evalsub = eigenvalsSubMatrix.flatten();
        Matrix2D tempEvalsMat = new Matrix2D(weights.length, evalsub.length);
        tempEvalsMat.replaceRowsWithArray(evalsub);
        Matrix2D tempmat = new Matrix2D(weights);
        tempmat.multiplyElementWise(tempEvalsMat);
        Matrix2D phinewmat = tempmat.multiply(egnfacesSubMatrix);
        double[][] phi = phinewmat.toArray();
        return phi;
    }


    private double[][] addAverageFaceData(double[][] phi) {
        double[][] xnew = new double[phi.length][phi[0].length];
        double[] avgface = bundle.getAvgFace();
        for (int i = 0; i < phi.length; i++) {
            for (int j = 0; j < phi[i].length; j++) {
                xnew[i][j] = phi[i][j] + avgface[j];
            }
        }
        return xnew;
    }


    private void reconstructOriginalImages(double[][] xnew, String reconFolderName, String ext) throws IOException {
        String prefix;
        prefix = "xnew";
        int imgwidth = bundle.getImageWidth();
        for (int i = 0; i < xnew.length; i++) {
            double[] xnewdata = xnew[i];
            String filename = reconFolderName + File.separator + prefix + i + ext;
            createImageFromArray(filename, xnewdata, imgwidth);
        }
    }


    private void reconstructPhiImages(double[][] phi, String reconFolderName, String ext) throws IOException {
        int imgwidth = bundle.getImageWidth();
        makeNewFolder(reconFolderName);
        String prefix = "phi";
        for (int i = 0; i < phi.length; i++) {
            double[] phidata = phi[i];
            String filename = reconFolderName + File.separator + prefix + i + ext;
            createImageFromArray(filename, phidata, imgwidth);
        }
    }


    private int getNumofFacesVal(String numofFaces) throws NumberFormatException {
        return new Integer(numofFaces).intValue();
    }


    private double getThresholdVal(String threshold) throws NumberFormatException {
        return new Double(threshold).doubleValue();
    }


    private void validateSelectedImageFileName(String faceimagename) throws FaceRecError {
        if ((faceimagename.length() == 0) || ( !isImage(faceimagename))) {
            throw new FaceRecError("select an imagefile");
        }
    }


    private boolean isImage(String imagefilename) {
        try {
            return Utils.isImageFile(imagefilename);
        }
        catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }


    private void validateSelectedFolderName(String foldername) throws FaceRecError {
        if (foldername.length() == 0) {
            throw new FaceRecError("select a folder");
        }
    }


    public MatchResult processSelections(String faceImageName, String directory, String numofFaces, String threshold) {
        MatchResult result = null;
        int numFaces = 0;
        double thresholdVal = 0.0;
        try {
            validateSelectedImageFileName(faceImageName);
            validateSelectedFolderName(directory);
            numFaces = getNumofFacesVal(numofFaces);
            thresholdVal = getThresholdVal(threshold);
            String extension = getFileExtension(faceImageName);
            checkCache(directory, extension, numFaces);
            reconstructFaces(numFaces);
            result = findMatchResult(faceImageName, numFaces, thresholdVal);
        }
        catch (Exception e) {
            result = new MatchResult(false, null, Double.NaN, e.getMessage());
        }
        return result;
    }


    public static String getFileExtension(String filename) {
        String ext = "";
        int i = filename.lastIndexOf('.');
        if (i > 0 && i < filename.length() - 1) {
            ext = filename.substring(i + 1).toLowerCase();
        }
        return ext;
    }


    public static void printError(String msg) {
        System.err.println(msg);
    }


    public static void debug(String msg) {
        System.out.println(msg);
    }


    public static void startMatcher(String imgToCheck, String imgDir, ResultListener listener) {
        long start = System.currentTimeMillis();
        String numFaces = "4";
        String thresholdVal = "2";
        MatchResult r = new FaceRec().processSelections(imgToCheck, imgDir, numFaces, thresholdVal);
        long end = System.currentTimeMillis();
        double takenTime = (end - start) / 1000.0;
        if (r.getMatchSuccess()) {
            listener.onSuccess(new File(r.getMatchFileName()), r.getMatchDistance(), takenTime);

        } else {
            listener.onFailed(takenTime);
        }

    }
}

and here is list of methods that are not usable because of BufferedImage

getImageData
createFaceBundle
getNormalisedImagesData
checkImageDimensions
getGrayScaleImages
convertToGray
createImageFromArray

回答1:


BufferedImage isn't supported by Android,it belongs to the AWT package.Here's a similar question that can help you: Similar Question

Feel free to ask for further information :)



来源:https://stackoverflow.com/questions/27483541/using-javafaces-in-android

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!