cocos2d-x实现安卓调用相机相册实现及详解

时间:2024-04-05 10:25:23

 上周主程大大让我试着做做安卓头像这块内容,刚开始简直一脸懵逼,毕竟从来没有做过关于C++和Java交互的工作,经过一周努力查各种资料终于肝出来了。这部分教程网上也有,只是刚开始的小盆友们可能觉得阅读起来略有困难,我打算在这篇文章里详细的描述一下,顺便也当做自己学习历程的笔记了。

  首先先来一点准备工作,因为前几天在网上看别人写的教程的时候,看到下面有人评论说不知道怎么调用,所以这里先写一个层和出来,并在主场景(或者你想放的地方)里加一个菜单项,点击就会弹出这个层,点击别的地方就把层去掉。效果图如下.

  cocos2d-x实现安卓调用相机相册实现及详解

cocos2d-x实现安卓调用相机相册实现及详解

 三个按钮分别是拍照,相册,最后一个是改变头像背景,这个可以不用管,因为是完全在C++里实现的,本文主要研究前2个需要调用Java的,下面贴代码上去。

头文件UserInfo.h

#pragma once
enum
{
Upload_Btn_Photo,
Upload_Btn_Album,
ChangeFrame_Btn,
Upload_Btn_Cancle,
};
//图片获取结果;
enum GetImageResult
{
GetImage_Success = 0,   //成功;
GetImage_Failed = 1,    //失败;
GetImage_Refuse = 2,//无权限;
};
#include "cocos2d.h"
#include "editor-support//cocostudio/CCSGUIReader.h"
#include "Image.h"
#include "ui/CocosGUI.h"
#include "Slot_Machine.h"
using namespace cocos2d;
using namespace cocos2d::ui;
class UploadAvatar :public Layer
{
public:
UploadAvatar();
~UploadAvatar();
CREATE_FUNC(UploadAvatar);
bool init();

         //按下按钮的回调,从点击以后这里开始交互
void UpLoadCallBack(Ref* obj);
//去掉返回层;
static void HideReturnLayer();
virtual bool onTouchBegan(Touch *touch, Event *unused_event);
virtual void onTouchEnded(Touch *touch, Event *unused_event);
virtual void onTouchMoved(Touch *touch, Event *unused_event);
virtual void onTouchCancelled(Touch *touch, Event *unused_event);
private:
bool _bBtCanTouch;
Size _bgSize;
ui::Scale9Sprite*_scaleBg;
Menu* menu;
EventListenerTouchOneByOne* listener;
};


UserInfo.cpp

#include "UserInfo.h"
UploadAvatar::UploadAvatar()
{
_bBtCanTouch = true;
}
UploadAvatar::~UploadAvatar()
{
}
bool UploadAvatar::init()
{
if (!Layer::init())
{
return false;
}
/*背景*/
_scaleBg = ui::Scale9Sprite::create("Return_bg.png");
_scaleBg->setContentSize(Size(250.0f, 210.0f));
_scaleBg->setPosition(Vec2(150.0f, 550.0f));
this->addChild(_scaleBg);
_bgSize = _scaleBg->getContentSize();
/*照相*/
Sprite* photoNormalSprite = Sprite::create();
photoNormalSprite->setContentSize(Size(250.f, 60.f));


Sprite* img1Sp = Sprite::create("ModalLayer_Photo.png");
img1Sp->setPosition(Vec2(50.f, 30.f));
photoNormalSprite->addChild(img1Sp);


Label* photoLabel1 = Label::create("photo", "arial.ttf", 22);
photoLabel1->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
photoLabel1->setPosition(Vec2(90.f, 30.f));
photoNormalSprite->addChild(photoLabel1);


Sprite* photoPressSprite = Sprite::create();
photoPressSprite->setContentSize(Size(250.f, 60.f));


Sprite* imgSp1 = Sprite::create("ModalLayer_Photo.png");
imgSp1->setPosition(Vec2(50.f, 30.f));
photoPressSprite->addChild(imgSp1);
Label* photoLabel2 = Label::create("photo", "arial.ttf", 22);
photoLabel2->setPosition(Vec2(90.f, 30.f));
photoLabel2->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
photoPressSprite->addChild(photoLabel2);


MenuItemSprite* item1Menu = MenuItemSprite::create(photoNormalSprite, photoPressSprite, NULL, CC_CALLBACK_1(UploadAvatar::UpLoadCallBack, this));
item1Menu->setTag(Upload_Btn_Photo);
item1Menu->setPosition(Vec2(_scaleBg->getContentSize().width / 2.f, 155));
/*相册*/
Sprite* AlbumNormalSprite = Sprite::create();
AlbumNormalSprite->setContentSize(Size(250.f, 60.f));


Sprite* img2Sp = Sprite::create("ModalLayer_Albnum.png");
img2Sp->setPosition(Vec2(50.f, 30.f));
AlbumNormalSprite->addChild(img2Sp);


Label* albumLabel1 = Label::create("album", "arial.ttf", 22);
albumLabel1->setPosition(Vec2(90.f, 30.f));
albumLabel1->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
AlbumNormalSprite->addChild(albumLabel1);


Sprite* albumPressSprite = Sprite::create();
albumPressSprite->setContentSize(Size(250.f, 60.f));


Sprite* imgSp2 = Sprite::create("ModalLayer_Albnum.png");
imgSp2->setPosition(Vec2(50.f, 30.f));
albumPressSprite->addChild(imgSp2);
Label* albumLabel2 = Label::create("album", "arial.ttf", 22);
albumLabel2->setPosition(Vec2(90.f, 30.f));
albumLabel2->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
albumPressSprite->addChild(albumLabel2);


MenuItemSprite* item2Menu = MenuItemSprite::create(AlbumNormalSprite, albumPressSprite, NULL, CC_CALLBACK_1(UploadAvatar::UpLoadCallBack, this));
item2Menu->setTag(Upload_Btn_Album);
item2Menu->setPosition(Vec2(_scaleBg->getContentSize().width / 2.f, 100));
/*跟换头像框*/
Sprite* changeNormalSprite = Sprite::create();
changeNormalSprite->setContentSize(Size(250.f, 60.f));


Sprite* img3Sp = Sprite::create("ModalLayer_ChangeFrame.png");
img3Sp->setPosition(Vec2(50.f, 30.f));
changeNormalSprite->addChild(img3Sp);


Label* changeLabel1 = Label::create("change photo", "arial.ttf", 22);
changeLabel1->setPosition(Vec2(90.f, 30.f));
changeLabel1->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
changeLabel1->setVerticalAlignment(TextVAlignment::CENTER);
changeLabel1->setHorizontalAlignment(TextHAlignment::LEFT);
changeLabel1->setDimensions(155, 60);
changeNormalSprite->addChild(changeLabel1);


Sprite* changePressSprite = Sprite::create();
changePressSprite->setContentSize(Size(250.f, 60.f));


Sprite* imgSp3 = Sprite::create("ModalLayer_ChangeFrame.png");
imgSp3->setPosition(Vec2(50.f, 30.f));
changePressSprite->addChild(imgSp3);
Label* changeLabel2 = Label::create("change photo", "arial.ttf", 22);
changeLabel2->setPosition(Vec2(90.f, 30.f));
changeLabel2->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
changeLabel2->setVerticalAlignment(TextVAlignment::CENTER);
changeLabel2->setHorizontalAlignment(TextHAlignment::LEFT);
changeLabel2->setDimensions(155, 80);
changePressSprite->addChild(changeLabel2);


MenuItemSprite* item3Menu = MenuItemSprite::create(changeNormalSprite, changePressSprite, NULL, CC_CALLBACK_1(UploadAvatar::UpLoadCallBack, this));
item3Menu->setTag(ChangeFrame_Btn);
item3Menu->setPosition(Vec2(_scaleBg->getContentSize().width / 2.f, 45));


menu = Menu::create(item1Menu, item2Menu, item3Menu, nullptr);
menu->setPosition(Vec2::ZERO);
_scaleBg->addChild(menu);
/*添加监听*/
listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchBegan = CC_CALLBACK_2(UploadAvatar::onTouchBegan, this);
listener->onTouchCancelled = CC_CALLBACK_2(UploadAvatar::onTouchCancelled, this);
listener->onTouchEnded = CC_CALLBACK_2(UploadAvatar::onTouchEnded, this);
listener->onTouchMoved = CC_CALLBACK_2(UploadAvatar::onTouchMoved, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
return true;
}
/*菜单按下的回调
作为接口判断类型并调用函数*/
void UploadAvatar::UpLoadCallBack(Ref* obj)
{
if (_bBtCanTouch == false)
{
return;
}
MenuItem* item0 = (MenuItem*)obj;
GetImageResult result = GetImageResult::GetImage_Success;
if (item0->getTag() == Upload_Btn_Album)
{
ImageCrop::getInstance()->openPhoto([=](std::string path)
{
CCLOG("photo path:%s", path.c_str());
Sprite* _img = Sprite::create(path);
_img->setContentSize(Size(60, 60));
Director::getInstance()->getRunningScene()->addChild(_img, 10);
_img->setPosition(Vec2(65, 690));
});
}
if (item0->getTag() == Upload_Btn_Photo)
{
ImageCrop::getInstance()->openCamera([=](std::string path)
{
CCLOG("photo path:%s", path.c_str());
Sprite* _img = Sprite::create(path);
_img->setContentSize(Size(60, 60));
Director::getInstance()->getRunningScene()->addChild(_img, 10);
_img->setPosition(Vec2(65, 690));
});
}
if (item0->getTag() == ChangeFrame_Btn)
{
HideReturnLayer();
}
}


/*触摸监听按下*/
bool UploadAvatar::onTouchBegan(Touch *touch, Event *unused_event)
{
Rect TouchRect = Rect(_scaleBg->getPosition().x - _scaleBg->getContentSize().width / 2.f, _scaleBg->getPosition().y - _scaleBg->getContentSize().height / 2.f, _bgSize.width, _bgSize.height);
Vec2 pos = touch->getLocation();


if (!TouchRect.containsPoint(pos))
UploadAvatar::HideReturnLayer();


return true;
}
/*触摸监听取消*/
void UploadAvatar::onTouchCancelled(Touch *touch, Event *unused_event)
{
}
/*触摸监听结束*/
void UploadAvatar::onTouchEnded(Touch *touch, Event *unused_event)
{
}
/*触摸监听移动*/
void UploadAvatar::onTouchMoved(Touch *touch, Event *unused_event)
{
}
/*去掉返回层*/
void UploadAvatar::HideReturnLayer()
{
auto runningScene = Director::getInstance()->getRunningScene();
runningScene->removeChildByTag(555, true);
}


然后在主场景里(不要忘了include头文件)

cocos2d-x实现安卓调用相机相册实现及详解

这样定义一个用户按钮,就是下面这个按钮了,

cocos2d-x实现安卓调用相机相册实现及详解

回调函数如下:

cocos2d-x实现安卓调用相机相册实现及详解


现在点击这个按钮,就会产生文章开头的效果,点击层外去掉这个层,这样准备工作就完成了,开始进入正题。

本文的功能主要是在安卓手机里点下photo,然后启动本地相机拍照,截取,然后保存这个图片的路径,最后用路径创建经理并在c++里添加到头像的位置,具体实现如下:

 Image.h头文件如下

#ifndef Image_H_
#define Image_H_


#include <stdio.h>
#include "cocos2d.h"
using namespace cocos2d;


#define kImageCropEvent "ImageCropEvent"


class ImageCrop
{
public:
ImageCrop();
/*设置为单例类*/
static ImageCrop* getInstance();
/*销毁单例类*/
static void destroyInstance();
/*打开照相机*/
/*这里使用std::function封装
 返回值类型为void
 接收一个字符串类型的参数
 新封装的类型名为callback
 openCamera的形参为std::function类型*/
void openCamera(std::function<void(std::string)> callback);
/*打开相册
 同上*/
void openPhoto(std::function<void(std::string)> callback);

private:
/*创建单例*/
static ImageCrop* _instance;
/*std::function类型封装*/
std::function<void(std::string)> _callback;
};

#endif


这个头文件没什么好讲的,大概需要讲的就是std::functin了,std::function在cocos2d-x里大家见的次数应该不少了,了解它功能的大佬们可以跳过这部分。

简单来讲std::function就是通用、多态的函数封装,可以对目标、实体进行存储、赋值、调用等操作,也就是可以对普通函数、lambda表达式等等进行包装,形成一个新的std::function对象,这个对象是可以调用的,让一切变得简单暴力。

怕自己讲不清楚,具体还是请大家去看这篇文章,比较细致简洁,具体的用法也很明显了.

点击打开链接

Image.cpp代码如下

#include "Image.h"


/*引用JniHelper使C++可以调用Java*/
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "platform/android/jni/JniHelper.h"
#include <jni.h>
/*定义宏如下*/
/*ImageCrop是Java中的类*/
#define JAVA_CLASS "org/cocos2dx/cpp/ImageCrop"
#define JAVA_FUNC_OPEN_PHOTO    "openPhoto"
#define JAVA_FUNC_OPEN_CAMERA   "openCamera"
#endif
/*构造函数后加:表示对对象成员赋值,把_callback置空*/
ImageCrop::ImageCrop() :_callback(nullptr)
{
/*观察者模式,对消息kImageCropEvent添加监听,并截取它的值*/
Director::getInstance()->getEventDispatcher()->addCustomEventListener(kImageCropEvent, [=](EventCustom *event)
{
                //定义一个imgPath保存路径,把值赋给_callback;
std::string *imgPath = (std::string*)event->getUserData();
_callback(*imgPath);
});
}
/*初始化*/
ImageCrop* ImageCrop::_instance = nullptr;
ImageCrop* ImageCrop::getInstance()
{
if (!_instance)
{
_instance = new (std::nothrow) ImageCrop();
}
return _instance;
}
void ImageCrop::destroyInstance()
{
CC_SAFE_DELETE(_instance);
}


void ImageCrop::openCamera(std::function<void(std::string)> callback)
{
_callback = callback;
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo info;
/*调用JniHelper的getSataicMethodInfo函数(bool)*/
/*参数分别为:写入Jnienv*、jclass、jmethodid,分别对应引用Java的指针,Java类名,C++中为classID,Java方法,C++中名为methodID
 调用的Java类名,需要详细到包里的类比如org.cocos2d.。。。
 调用的Java方法名,需要在C++里使用的Java方法名
 该Java方法的返回值类型与参数,()V表示Void类型无参数*/
bool ret = JniHelper::getStaticMethodInfo(info, JAVA_CLASS, JAVA_FUNC_OPEN_CAMERA, "()V");
if (ret)
{
/*此时已经写入成功,对Java里jclass、jmethodid都可以用classID,methodID来引用了*/
/*通过env*指针来调用Java方法,给出调用的类名和方法名*/
info.env->CallStaticVoidMethod(info.classID, info.methodID);
}
#endif
}


void ImageCrop::openPhoto(std::function<void(std::string)> callback)
{
_callback = callback;


#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo info;
bool ret = JniHelper::getStaticMethodInfo(info, JAVA_CLASS, 


JAVA_FUNC_OPEN_PHOTO, "()V");
if (ret)
{
info.env->CallStaticVoidMethod(info.classID, info.methodID);
}
#endif
}




//--------Java回调C++--------native方法
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)


extern "C"
{
void Java_org_cocos2dx_cpp_ImageCrop_onImageSaved(JNIEnv *env, jobject thiz, jstring path)
{
/*Java的String类型转换成C++的std::string类型*/
std::string strPath = JniHelper::jstring2string(path);
Director::getInstance()->getScheduler()->performFunctionInCocosThread([=]()mutable{
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(kImageCropEvent, &strPath);
});
}
}
#endif


这里是C++的部分了,看到这里肯定还有很多小伙伴一脸懵逼的,不过我还是要在下面再贴上Java的代码,最后再从整个的调用流程开始,每一步都详细的解析。


打开android studio 在org.cocos2dx.cpp包下面新建一个.java类,名为ImageCrop,建议小伙伴们没看懂最后的解析前不要乱改类名、方法名等,具体代码如下。

package org.cocos2dx.cpp;

import java.io.File;
import java.io.FileNotFoundException;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;

public class ImageCrop {

    public static final int NONE = 0;
    public static final int PHOTOHRAPH = 1;   //拍照
    public static final int PHOTOZOOM = 2;    //缩放
    public static final int PHOTORESOULT = 3; //结果
    public static final String IMAGE_UNSPECIFIED = "image/*";

    private static ImageCrop instance = null;
    private static Activity activity = null;
    private static String TAG = "ImageCrop";
    //用JNI交互需要定义为native
    public static native void onImageSaved(String path);

    //拍摄照片保存路径
    //获得外部存储媒体目录
    private static String savePath = Environment.getExternalStorageDirectory() +"/CropImage";
    private static String photoName = "";
    private static Uri imgUri = null;
    //创建单例类的对象
    public static ImageCrop getInstance()
    {
        if(null == instance)
        {
            instance = new ImageCrop();
        }
        return instance;
    }

    //初始化
    public void init(Activity activity)
    {
        ImageCrop.activity = activity;
    }

    //打开相册
    static public void openPhoto()
    {
        //使用Intent.ACTION_PICK显示的是每一张照片和返回每一张的Uri
        Intent intent = new Intent(Intent.ACTION_PICK, null);
        intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_UNSPECIFIED);
        activity.startActivityForResult(intent, PHOTOZOOM);
    }

    //打开相机
    static public void openCamera()
    {
        File destDir = new File(savePath);
        //如果存储路径不存在那么创建一个路径
        if (!destDir.exists())
        {
            destDir.mkdirs();
        }
        photoName = "temp.jpg";
        File file = new File(savePath + "/" + photoName);
        imgUri = Uri.fromFile(file);
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri);
        activity.startActivityForResult(intent, PHOTOHRAPH);
    }

    //回调
    public void onActivityResult(int requestCode,int resultCode,Intent data)
    {
        Log.e(TAG, "onActivityResult:" + requestCode +"," + resultCode);

        if (resultCode == NONE)
            return;

        // 拍照 不能使用data,因为没有返回是空的
        if (requestCode == PHOTOHRAPH)
        {
            if (imgUri == null)
            {
                Log.e(TAG, "PHOTOHRAPH imgUri is null");
                return;
            }
            startPhotoZoom(imgUri);
        }

        if (requestCode == PHOTOZOOM)
        {
            // 读取相册缩放图片
            if (data==null )
            {
                Log.e(TAG, "data is null");
                return;
            }
            if (data.getData()==null)
            {
                Log.e(TAG, "data.getData() is null");
                return;
            }
            startPhotoZoom(data.getData());
        }

        // 处理结果
        if (requestCode == PHOTORESOULT)
        {
            Bitmap bitmap = decodeUriAsBitmap(imgUri);
            if (bitmap == null)
            {
                Log.e(TAG, "bitmap is null");
            }
            Log.e("ImageCrop", "图片已经保存,通知c++层,");
            onImageSaved(savePath + "/" + photoName);
            Log.e(TAG,".....");
        }
    }

    public void startPhotoZoom(Uri uri)
    {
        photoName = System.currentTimeMillis() + ".jpg";
        File file = new File(savePath,photoName);
        imgUri = Uri.fromFile(file);

        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(uri, IMAGE_UNSPECIFIED);
        //在开启的Intent中显示View可剪裁
        intent.putExtra("crop", "true");
        //宽高比
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        //宽高
        intent.putExtra("outputX", 100);
        intent.putExtra("outputY", 100);
        intent.putExtra("return-data", false);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri);

        activity.startActivityForResult(intent, PHOTORESOULT);
    }

    private Bitmap decodeUriAsBitmap(Uri uri)
    {
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeStream(activity.getContentResolver().openInputStream(uri));
        } catch (FileNotFoundException e) {
            // TODO: handle exception
            e.printStackTrace();
            return null;
        }

        return bitmap;
    }

}

(android studio 粘下来居然还是黑的,(⊙﹏⊙)b)

cocos2d-x实现安卓调用相机相册实现及详解

然后打开AndroiMainfest.xml,在最下面<uses-permission>里加上调用相机的权限和读写权限。

最后在org.cocos2d.cpp 下的AppActivity里重写一下

package org.cocos2dx.cpp;

import android.content.Intent;
import android.os.Bundle;

import org.cocos2dx.lib.Cocos2dxActivity;

public class AppActivity extends Cocos2dxActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);

        ImageCrop.getInstance().init(this);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // TODO Auto-generated method stub
        super.onActivityResult(requestCode, resultCode, data);

        ImageCrop.getInstance().onActivityResult(requestCode, resultCode, data);
    }
}
这样功能所需的全部代码都贴上来了,心急的小伙伴可以先复制进去试试水,菜鸡儿试过十几个机型了应该是可以用的,下面开始讲述原理,从流程开始。(先要打包的手机上才可以,不要在PC端点,不会有反应的)

操作流程是:点击拍照(相册也一样,所以下面均以相机为例)->调用本地相机转到拍照->拍照->保存->转到缩略图截取 ->确定->头像贴图成功

调用流程是:点击相机按钮的回调函数

cocos2d-x实现安卓调用相机相册实现及详解

 C++调用Java的openPhoto函数

cocos2d-x实现安卓调用相机相册实现及详解

Java打开相机(这里有一连串的操作,已经懂得可以看上面的源码)

cocos2d-x实现安卓调用相机相册实现及详解

Java回调C++

cocos2d-x实现安卓调用相机相册实现及详解

监听事件获取到保存的图片路径并把值传到std::function模板_callback中,最后把模板_callback的值截取到形参中,贴图即可。

cocos2d-x实现安卓调用相机相册实现及详解

好了,现在调用步骤已经知道了,一共大体分为5步,下面对每一步进行细致的讲解了(搞事).

步骤1 按钮回调中调用openCamera,

ImageCrop::getInstance()->openCamera([=](std::string path)

这里用单例类调用了这个函数,void ImageCrop::openCamera(std::function<void(std::string)> callback),这里要注意的是形参是一个lambda函数,[=]可以截取外部变量引用进来,而现在这一步只是把形参,也就是这个函数封装到了_callback模板里面,但std::string实际上还没有具体的值,可以类比声明了一个变量装入Vector里但并未赋值(但这里是函数,列举出来更容易理解)这点大家一定要理解,但现在只需要知道:1.形参是一个函数;2.封装到了_callback里面;3.此时std::string并没有具体的值。现在流程继续


步骤2 openCamera开始调用Java

 代码就不重复粘贴了,首先需要知道C++如何调用Java,这里cocos引擎已经为我们封装好了,只需要引入头文件

 #if(CC_TARGET_PLATFORM==CC_PLATFORM_ANDROID)

 #include <jni.h>//这是Java里的,不要去引擎里找了。。

#include "platform/android/jni/JniHelper.h"

#endif 

 C++调用Java步骤非常简答,但是首先要明确Java为了能被C++调用,做了两种语言类型对应的类型处理,在JniHelper中,对应关系如下

 Jnienv* 这个指针指向Java引用环境

 classID 对应jclass(jclass 是Java中类的别名)

 methodID 对应 jmethodID(对应Java中的方法)

有了上面的三个参数,才可以调用Java,首先要声明JniMethodInfo info;

 然后确定是否能在Java中找到你指定的类和其中的方法也就是

bool result=JniHelper::getStaticMethodInfo(info,Java类名,Java方法名,pramaCode);

注意:1.如果Java里方法是static静态的话,如上即可,如果没有static修饰,那么上面应该写成getMethodInfo

            2.参数分别是(寻找Java三剑客)结构体别名Info,Java类名,必须详细"org/cocos2dx/cpp/ImageCrop",项目代码中定义了它的宏,Java方法名,最后一个返回码

              '()V"表示这个Java方法是void类型,0个形参。


最后就剩下一句调用,如果寻找到了,那么可以调用,从这里开始就转入到Java代码中开始执行了。

if (ret)
{
/*此时已经写入成功,对Java里jclass、jmethodid都可以用classID,methodID来引用了*/
/*通过env*指针来调用Java方法,给出调用的类名和方法名*/
info.env->CallStaticVoidMethod(info.classID, info.methodID);
}

步骤3 Java内部调用

 关于初始化我想应该不用废话了,

//打开相机
static public void openCamera()
{
    File destDir = new File(savePath);
    //如果存储路径不存在那么创建一个路径
    if (!destDir.exists())
    {
        destDir.mkdirs();
    }
    photoName = "temp.jpg";
    File file = new File(savePath + "/" + photoName);
    imgUri = Uri.fromFile(file);
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri);
    activity.startActivityForResult(intent, PHOTOHRAPH);
}
C++调用Java直接来到这里,Uri是资源表示符,Uri的概念请大家自行百度一下,比较简单。在项目里是首先创建了一个存储路径,如果存在就不必创建,然后开启一个Intent,这个Intent会自动寻找能够响应的Activity也就是系统的相机了。。

public void onActivityResult(int requestCode,int resultCode,Intent data)
{
    Log.e(TAG, "onActivityResult:" + requestCode +"," + resultCode);

    if (resultCode == NONE)
        return;

    // 拍照 不能使用data,因为没有返回是空的
    if (requestCode == PHOTOHRAPH)
    {
        if (imgUri == null)
        {
            Log.e(TAG, "PHOTOHRAPH imgUri is null");
            return;
        }
        startPhotoZoom(imgUri);
    }

然后在这里,如果请求码为拍照,转到缩略图处理startPhotoZoom,这里就是对照片的裁剪

需要说明的部分

//在开启的Intent中显示View可剪裁
intent.putExtra("crop", "true");
//宽高比
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
//宽高
intent.putExtra("outputX", 100);
intent.putExtra("outputY", 100);
intent.putExtra("return-data", false);

intent.putExtra就是把想要传递的值附加到Intent,方便Activity活动获取和处理,两个值相当于键值对,名字,值这样,注释里也很清楚哪一步是做什么。。

然后回到这里开始处理结果(已裁剪完成)

activity.startActivityForResult(intent, PHOTORESOULT);
把前面一直在用的Uri转换成bitmap位图并返回,这里相信不用多说了((⊙﹏⊙)b),最后调用
onImageSaved(savePath + "/" + photoName);

这里大家没有找到Java的实现吧,只有一个这个

public static native void onImageSaved(String path);
没错,现在Java已经调用完毕,现在要回调C++了,我们的流程也完成一大半了,哪里和普通的Java方法不一样也很清楚了,Java调用C++要定义为native~

C++对其实现如下

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)


extern "C"
{
void Java_org_cocos2dx_cpp_ImageCrop_onImageSaved(JNIEnv *env, jobject thiz, jstring path)
{
/*Java的String类型转换成C++的std::string类型*/
std::string strPath = JniHelper::jstring2string(path);
/*必须这样,否则由于线程问题会显示黑块无法正常创建精灵*/
Director::getInstance()->getScheduler()->performFunctionInCocosThread([=]()mutable{
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(kImageCropEvent, &strPath);
});
}
}


#endif

首先大家看到必须要 extern "c"

然后函数名变长了。。仔细观察可以发现Java_包名_函数名,就这样~,参数变多了,原来的参数不是

onImageSaved(savePath + "/" + photoName);
路径+/+名称吗?

这就是Java调用C++,首先extern "c",然后Java_包名_函数名,然后参数(JNIEne* env,jobject thiz)这就是Java调用C++了,简单暴力,最后的jstring path才是原来方法的参数,到这里步骤三圆满OK,接下来内容稍难,我是从大牛那里拿过来现学现卖cocos2d-x实现安卓调用相机相册实现及详解

步骤4:从线程获取Java中传过来的路径(不是读取路径,是Java保存了路径,再用参数传进来)

std::string strPath = JniHelper::jstring2string(path);

这个相信没什么问题。。

Director::getInstance()->getScheduler()->performFunctionInCocosThread([=]()mutable{
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(kImageCropEvent, &strPath);
});


关键是这里,又见到lambda表达式了,还附加了一个mutable可变的,是否感觉看起来有点复杂。。其实原理。。更复杂

首先说mutable,就是const的反义词,就算一个变量声明为const,也可以通过mutable来改变它,就是这样的一个小小的修饰符。

上面的代码翻译过来就是,对于每一个cocos里的线程,都对指定的消息kImageCropEvent定义消息派发,值为strPath


为什么要这么麻烦呢,要遍历一遍,又要每一个都消息派发,这个原理就是因为线程本身搞事。

首先C++中openGl的线程,发送一个消息给Java,而Java中需要用到的功能都在主线程中,Java要回调C++时,会新开启一个线程,把消息发送到openGl线程里,所以上面遍历一遍其实就是找到Java的线程发送到了openGl的哪一个线程里。。不知道这样解释行不行cocos2d-x实现安卓调用相机相册实现及详解,大家还想深入研究自行百度吧。


现在这个消息派发已经注册成功了,里面也保存了消息的名字和值,只需要截取下来使用就可以。


步骤5:

最后的一步了,

Director::getInstance()->getEventDispatcher()->addCustomEventListener(kImageCropEvent, [=](EventCustom *event)
{
std::string *imgPath = (std::string*)event->getUserData();
_callback(*imgPath);
});

也就是这里,注册一个监听就行了,这里通过名字获取到派发的消息,然后把值保存到了_callback中,看到这里大家应该明白了cocos2d-x实现安卓调用相机相册实现及详解,刚才第一步的空路径值现在经过一系列步骤终于拿到了,也就是ImageCrop::getInstance()->openPhoto([=](std::string path)《---也就是[=]拷贝进来,单例类是不会有别的值的
{
CCLOG("photo path:%s", path.c_str());

                }千难万险终于得到这个path了,简直流泪

大家如果在真机上调试的话,打印信息应该能够看到图片名称。 

photoName = System.currentTimeMillis() + ".jpg";
安卓里根据时间来命名的,如果没有改方法名类名之类的话,应该在手机CropImage文件夹下可以找到所有拍照和相册缩略图得到的图片。



OK终于打完了cocos2d-x实现安卓调用相机相册实现及详解,整个过程和解析就到这里了,对新手还是比较不友好的

打字不易,路过的大神不要喷我这个小菜鸟,程序有什么问题可以私信我,刚开始粘贴的层里用的资源像ModalLayer_Photo.png,我刚开始写CSDN博客不知道在哪上传,不过只有几张,大家可以随意找几个图片试试吧,想要私下发你们也可以。


最后还是。拒绝抄袭,从我做起。