1. 程式人生 > >Unity 開啟Android手機相簿和攝像頭

Unity 開啟Android手機相簿和攝像頭

需求:要實現開啟手機的相簿和攝像頭,選擇照片或者拍照後,在unity進行。

 

1.android外掛

我使用的是AndroidStuido來寫外掛,下面是一步步介紹流程

(1)建立android工程

注意紅框裡面的東西,要修改兩個地方:

1.將com.android.application 改為 com.android.library

2.將applicationId "com.niko.myunityplugin" 刪除掉

刪除掉這兩個目錄,不需要他們

 

(2)加入我們要依賴的unity的jar包,它的路徑在:

C:\Program Files\Unity5.6.4p3\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes

然後將他放到我們的android工程下的libs目錄下,如下圖:

此時還要讓工程知道它的存在,所以我們要:

注意:紅框選擇的是Compile only, 而不是Implemetation,這樣選擇的原因是最後我們打包出來的aar檔案將不會包含這個jar包,如果使用Implemetation 將會把這個jar放到最後打出來的aar包中,我們得手動刪除掉,不然我們打Apk的時候會出錯,因為unity會使用自己的這個jar包。

 

同時我們刪除掉下面紅框的東西,我們不需要他們:

到這一步我們已經成功將這個jar加入到工程了,以後就可以使用它裡面的介面了,接下來就開始寫程式碼

 

(3)寫外掛程式碼

package com.niko.myunityplugin;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.util.Log;

import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;

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

import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;

public class MainActivity extends UnityPlayerActivity {

    private static final int TAKE_PHOTO = 1;
    private static final int OPEN_GALLERY = 2;
    private static final int CROP_PHOTO = 3;
    private Uri mPhotoUri;
    private Uri mCropPhotoUri;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
    }

    public void TakePhoto(){
        mPhotoUri = GetUri(CreateFile("temp.png"));
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, mPhotoUri);
        startActivityForResult(intent, TAKE_PHOTO);

    }

    //呼叫相簿
    public void OpenGallery()
    {
        Intent intent = new Intent(Intent.ACTION_PICK,null);
        intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");
        startActivityForResult(intent, OPEN_GALLERY);
    }

    private Uri GetUri(File file)
    {
        Uri uri;
        if(Build.VERSION.SDK_INT >= 24)
        {
            uri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", file);
        }
        else
        {
            uri = Uri.fromFile(file);
        }

        return uri;
    }

    private File CreateFile(String name)
    {
        File file = new File(Environment.getExternalStorageDirectory(), name);
        try
        {
            if(file.exists())
            {
                file.delete();
            }
            file.createNewFile();
        }catch(IOException e)
        {
            e.printStackTrace();
        }

        return file;
    }

    private void StartCrop(Uri inputUri)
    {
        mCropPhotoUri = Uri.fromFile(CreateFile("tempCrop.png"));

        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
        intent.addFlags(FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.setDataAndType(inputUri, "image/*");
        intent.putExtra("crop", "true");
        // aspectX aspectY 是寬高的比例
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        // outputX outputY 是裁剪圖片寬高
        intent.putExtra("outputX", 300);
        intent.putExtra("outputY", 300);
        intent.putExtra("scale", true);
        intent.putExtra("return-data", false);
        intent.putExtra("noFaceDetection", true);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, mCropPhotoUri);
        startActivityForResult(intent, CROP_PHOTO);
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        if(resultCode == Activity.RESULT_CANCELED)
        {
            Log.d("unity","user cancel operator!!");
            return;
        }

        switch (requestCode)
        {
            case TAKE_PHOTO:
            {
                StartCrop(mPhotoUri);
            }
            break;
            case OPEN_GALLERY:
            {
                Uri uri = data.getData();
                StartCrop(uri);
            }
            break;
            case CROP_PHOTO:
            {
                try
                {
                    Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(mCropPhotoUri));
                    FileOutputStream fOut = null;

                    try
                    {
                        String path = "/mnt/sdcard/Android/data/com.niko.myunityplugin/files";
                        File destDir = new File(path);
                        if(!destDir.exists())
                        {
                            destDir.mkdirs();
                        }

                        fOut = new FileOutputStream(path + "/" + "image.png");
                    }
                    catch (FileNotFoundException e)
                    {
                        e.printStackTrace();
                    }

                    if(bitmap != null)
                    {
                        bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
                        try {
                            fOut.flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        try {
                            fOut.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        UnityPlayer.UnitySendMessage("UnityPlugin","OnGetPhoto", "image.png");
                    }
                }
                catch(FileNotFoundException e)
                {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

加入上面程式碼後,你會發現其中有報錯,如圖:

意思就是缺少這個類的資訊,那麼我們這回就要引入對應的jar包就可以了,然後我們開啟build.gradle檔案,如下圖新增一個新的jar引用。

新增後我們點選工程的同步按鈕,讓工程開始從遠端倉庫下載這個jar包下來,如圖:

下載好後,此時我們的工程就不會報錯了

 

(4)修改AndroidManifest.xml檔案

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.niko.myunityplugin">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.niko.myunityplugin.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths" />
        </provider>
    </application>

    <!-- 連線網際網路的許可權 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- SDCard寫入資料許可權 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>

 

(5)增加了xml檔案,如圖:

provider_paths.xml的內容是:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

可以開心的開始編譯工程了,編譯好後如下圖所示:

紅框中的兩個東西,是需要拷貝到unity裡面去的。到這裡我們外掛的編寫就完成了,接下來是unity那邊開始呼叫。

 

2.Unity呼叫外掛

(1)設定環境

將剛才編譯出來的東西放到如圖的目錄下:

 

這裡的包名要和我們設定的android外掛的包名一致才可以。

 

(2)寫程式碼

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TestPlugin : MonoBehaviour
{
    public Button mBtnCamera;
    public Button mBtnGallery;
    public RawImage mImage;
    public Text mText;
    private Action<byte[]> mPhotoAction;

    // Use this for initialization
    void Start()
    {
        mBtnCamera.onClick.AddListener(() =>
        {
            TakePhoto((datas) =>
            {

            });
        });

        mBtnGallery.onClick.AddListener(() =>
        {
            OpenGallery((datas) =>
            {

            });
        });
    }

    public void TakePhoto(Action<byte[]> callback)
    {
        AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
        jo.Call("TakePhoto");
        mPhotoAction = callback;
    }

    public void OpenGallery(Action<byte[]> callback)
    {
        AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
        jo.Call("OpenGallery");
        mPhotoAction = callback;
    }

    void OnGetPhoto(string name)
    {
        StartCoroutine(LoadPhoto(name));
    }

    IEnumerator LoadPhoto(string name)
    {
        mText.text = name;
        string path = "file://" + Application.persistentDataPath + "/" + name;
        WWW www = new WWW(path);
        yield return www;
        mImage.texture = www.texture;
        if (mPhotoAction != null)
        {
            mPhotoAction(((Texture2D)mImage.texture).EncodeToPNG());
            mPhotoAction = null;
            //Destroy(texture);
        }
    }
}

將指令碼掛到場景的物件上,然後關聯上按鈕和圖片和文字,如圖:

注意紅框:由於拍照後要在unity這邊顯示,我們要給unity發訊息,由於android程式碼發訊息的物件名字是UnityPlugin,所以我們這裡要修改為UnityPlguin。

然後開始打包apk就可以了,打包過程中我沒有出現錯誤,打出apk後我們安裝執行,恭喜你,一執行就會閃退,哈哈哈哈。報錯如圖:

 

意思就是我們缺少了android.support.v4.content.FileProvider這個類的資訊, 咦?不是我們之前添加了這個庫到android的工程中嗎?為啥還是報錯了,這個問題之前把我坑了好久,網上查了,說的是android工程打包後,是不會將這個jar包一起打包到我們的apk中的,所以出現丟失,但是我們怎麼才能拿到這個jar包,讓他打包到我的apk中呢?下面是解決方法:

找到這個出錯的類包,然後我們按住ctrl+滑鼠左鍵,切入到這個類中去,

然後我們將滑鼠放到這個頁簽上,就可以知道它屬於哪個包了,如圖:

他的路徑如圖所示,這就是android studio為我們從遠端倉庫下載下來的依賴庫,這個路徑是原始碼的包,我們需要編譯後的jar包,這個包我們可以在如圖路徑下找到:

 

 

ok,順利找到了這個jar包,我們將它拷貝出來,並且改名為:support-core-utils-26.1.0-sources.jar,放到我們之前的android 工程的libs目錄下去,如圖:

 

讓工程知道這個jar的存在,和之前的那個新增jar包的方法一下,結果如圖:

 

我們刪除了之前依賴遠端倉庫的包,避免重複,重新同步一下。此時我們重新編譯這個庫

可以看到這個aar包中的libs目錄下有了這個需要依賴的jar包,好了,我們可以重新打包Apk了。打包後一切都好啦,可以拍照了。

 

最後是原始碼地址:原始碼點我啦

注意:這個原始碼使用的包名是另外一個,與這裡我講解的不同,這裡講解的是我重建了一個工程來給大家說明的。