前言
第一次进行 Unity 项目的 SDK 接入,中途踩了很多坑,也学到了很多东西。
在此记录系统性的总结一下自己接入过程,避免以后踩同样的坑。
因为目前需求只要进行纯图片的分享,所以本文只记录了如何分享图片的相关方法。
正文
一、Unity 与 Android 的交互
目前有三种方式可以进行交互:
① AndroidStudio 导出 jar 包,Unity 接入 Jar 包,使用 Jar 包封装好的功能
② AndroidStudio 导出 Arr 包,Unity 接入 Arr 包,使用 Arr 包封装好的功能
③ Unity 导出安卓工程,在 AndroidStudio 中对导出工程进一步改造
由于游戏包体过大,为了节省打包时间,我在这里选择了方式 ③ 进行接入。
1. Unity 调用 Android 的方法
在 Unity 导出 Android 工程的时候勾选 Export Project 
该操作会自动生成一个 UnityPlayerActivity.java,其路径为:
xxx\src\main\java\com\xxx\xxx\UnityPlayerActivity.java
首先要获取 Android 下的 currentActivity,代码如下
private AndroidJavaObject _current_activity = null;
private AndroidJavaObject CurrentActivity
{
get
{
if (_current_activity == null)
{
AndroidJavaClass unity_player = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
_current_activity = unity_player.GetStatic<AndroidJavaObject>("currentActivity");
}
return _current_activity;
}
}
获取到之后,就可以使用CurrentActivity.Call<T>(string func,string parms) 调用Android端的方法
注:调用的方法要声明在MainActivity中。
可以在 Android 端将所有功能封装成一个 “sdkCommand” 方法
在 Unity 中通过传入方法名,来调用不同方法
private string SendSdkCommand(string strCommand, string P2 = "", string P3 = "", string P4 = "", string P5 = "")
{
return CurrentActivity.Call<string>("sdkCommand", strCommand, P2, P3, P4, P5);
}
2. Android 调用 Unity 的方法
使用 UnitySendMessage 方法
UnityPlayer.UnitySendMessage(String var0, String var1, String var2)
- var0:场景中的 GameObject 名,要调用的方法所在的脚本要挂载在上面
- var1:要调用的方法名,要调用的方法必须是
void类型 - var2:该方法一定有一个String的参数,该参数一定不能为 null !!!
注:我当时在微信SDK的回调中调用 UnitySendMessage 方法,var2 传入的是 resp.errorMsg。 调试半天都卡在 UnitySendMessage 的执行, 最后发现当回调成功的时候 resp.errorMsg 是 null,所以导致调用失败。
这里可以通过通用单例类[1]来创建该 GameObject
并将其放到 DontDestroyOnLoad 当中,保证在游戏运行过程中不会被销毁
创建平台管理类 PlatformHelper.cs
public class PlatformHelper : SingletonMonoBehaviour<PlatformHelper>
{
private AndroidJavaObject _current_activity = null;
private AndroidJavaObject CurrentActivity
{
get
{
if (_current_activity == null)
{
AndroidJavaClass unity_player = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
_current_activity = unity_player.GetStatic<AndroidJavaObject>("currentActivity");
}
return _current_activity;
}
}
public void AndroidCallUnity(string str)
{
Debug.Log(str);
}
}
这样就可以这样调用
UnityPlayer.UnitySendMessage("PlatformHelper", "AndroidCallUnity", str)
3. 编写 Unity 端接口
在导出Android工程前,需要现在Unity端写好未来要调用的一些方法
所有的平台交互方法都可以写在统一的平台管理类[2]当中,便于管理
二、Android 端接入微信SDK
1. 申请 AppID
到 微信开放平台 进行登记
登记并选择移动应用进行设置后将该应用提交审核
只有审核通过的应用才能进行开发
2. 下载 SDK 及 API 文档
我使用的是 Android Studio 进行接入,所以在 build.gradle 文件中,添加微信的SDK依赖即可
dependencies {
api 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
}
3. 代码中使用开发工具包
[1] 在 AndroidManifest.xml 中添加权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
[2] 注册到微信
这里为了区分多个 SDK,我将微信用到的方法都封装到 WeChatSDK.java [3] 中
并且将方法和相关 API 声明为静态,便于在 MainActivity 当中调用
在 UnityPlayerActivity.java 的中合适的地方(我这里选择的是在生命周期 onCreate )初始化
WeChatSDK.Init(activity);
[3] 发送请求或响应到微信
现在,你的程序要发送请求或发送响应到微信终端
可以通过 IWXAPI 的 sendReq 和 sendResp 两个方法来实现
对于这两个方法的实现和使用可以参考微信的官方文档
[4] 注册响应回调
在你的包名相应目录下新建一个 wxapi 目录
在该 wxapi 目录下新增一个 WXEntryActivity 类  该类继承自 Activity,实现 IWXAPIEventHandler 接口
public class WXEntryActivity extends Activity implements IWXAPIEventHandler {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (WeChatSDK.GetWXAPI() != null){
WeChatSDK.GetWXAPI().handleIntent(getIntent(), this);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
WeChatSDK.GetWXAPI().handleIntent(getIntent(), this);
}
@Override
public void onReq(BaseReq baseReq) {
}
@Override
public void onResp(BaseResp resp) {
if (resp != null) {
WeChatSDK.ShareCallback(resp);
}
finish();
}
}
主要有几个注意点:
- 要重写 onNewIntent 方法,调用 WXAPI.handleIntent()
- 如果 onResp 执行之后,黑屏停留在微信的 Activity 不回到游戏,记得检查一下在 onResp 执行完逻辑后调用 finish() 方法结束该 Activity
并在 AndroidManifest.xml 添加如下属性
<activity
android:name=".wxapi.WXEntryActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:exported="true"
android:taskAffinity="com.xxx.xxx"
android:launchMode="singleTask">
</activity>
4. 微信分享图片
官方文档的方法是提供文件路径,将文件路径处理成二进制数据
我这里由于是分享 Unity StreamingAssets 下的资源
所以可以直接由 Unity 加载然后处理成二进制数据直接传入
public static void ShareImg(byte[] data, int type)
{
mActivity.runOnUiThread(new Runnable()
{
@Override
public void run()
{
if (data == null)
{
Log.d(TAG, "Image not exist");
return;
}
Bitmap shareBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
WXImageObject imgObj = new WXImageObject(shareBitmap);
WXMediaMessage msg = new WXMediaMessage();
msg.mediaObject = imgObj;
Bitmap thumbBmp = Bitmap.createScaledBitmap(shareBitmap, THUMB_SIZE, THUMB_SIZE, true);
msg.thumbData = bmpToByteArray(thumbBmp);
SendMessageToWX.Req req = new SendMessageToWX.Req();
req.transaction = buildTransaction("img");
req.message = msg;
req.scene = type;
sendReq(req);
}
});
}
private static byte[] bmpToByteArray(Bitmap bmp)
{
ByteArrayOutputStream output = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.PNG, 100, output);
byte[] result = output.toByteArray();
try
{
output.close();
}
catch (Exception e)
{
e.printStackTrace();
}
return result;
}
private static String buildTransaction(String type)
{
return (type == null) ? String.valueOf(System.currentTimeMillis()) : type + System.currentTimeMillis();
}
注:如果发送消息时报错,可以使用runOnUiThread进行尝试
5. 总结
总的来说,接入微信的时候没有遇到什么比较大的问题
只要规规矩矩按照文档上的来就可以调通
三、Android 端接入QQSDK
1. 申请 AppID
第一步还是去 QQ互联官网 登记
2. 搭建SDK环境
在项目的 build.gradle 中添加互联sdk maven依赖
dependencies {
implementation 'com.tencent.tauth:qqopensdk:3.52.0'
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
}
注:此处还需要引入 androidx 包,因为 QQ 的文件操作会使用到 FileProvider[4]
配置 AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application>
<activity
android:name="com.tencent.tauth.AuthActivity"
android:noHistory="true"
android:launchMode="singleTask" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tencent你的AppId" />
</intent-filter>
</activity>
<activity
android:name="com.tencent.connect.common.AssistActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="behind"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<application>
注:“tencent你的AppId” ,如果你的appId是1234567,那么此处应该填写 “tencent1234567”。
3. 在代码中使用
[1] 创建实例
首先还是要创建 QQAPI 的实例,同样将代码都封装在 QQSDK.java[5] 中
mTencent = Tencent.createInstance(APP_ID, mActivity.getApplicationContext(), APP_AUTHORITIES);
在实例化的时候,还需传入一个 APP_AUTHORITIES 参数
该参数为 Manifest 文件中注册 FileProvider 时设置的 authorities 属性值
即 com.xxx.xxx.fileprovider
[2] 初始化 QQAPI
同样的,在 UnityPlayerActivity.java 的中合适的地方调用封装好的方法进行初始化
QQSDK.Init(activity);
[3] 注册回调函数
需要在 onActivityResult 下注册监听(要在 QQAPI 运行的 Activity 中改写才可以)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Tencent.onActivityResultData(requestCode, resultCode, data, QQSDK.GetListener());
}
[4] 分享图片
通过shareToQQ(Activity var1, Bundle var2, IUiListener var3) 方法发送分享请求
QQ 分享纯图片只能通过图片的路径
所以要在 Unity 端将要分享的图片存入手机中
我选择的是存入 persistentDataPath 下,并将保存的路径传入
string savePath = Application.persistentDataPath + "/" + id.ToString() + "moments.png";
using (FileStream fs = File.Create(savePath))
{
fs.Write(imageData, 0, webRequest.downloadHandler.data.Length);
}
CurrentActivity.Call("QQShareImage", savePath);
注:各个路径的读写权限和路径位置可以看【Unity3D】数据持久化及方法
5. 总结
总体接入过程中,遇到两个问题
- 关于 FileProvider 的配置:原先的 android.support.v4 在高版本已经弃用,转而使用 androidX
在更换这个包的时候有一个兼容问题需要在 gradle.properties 中做配置。 - 如果分享图片的路径传入的是 streamingAssetsPath 下的,很可能 QQ 因为权限问题而访问不到,建议存入persistentDataPath 下。
附录
通用单例类 SingletonMonoBehaviour.cs
using UnityEngine;
namespace Utilities
{
public class SingletonMonoBehaviour<T> : MonoBehaviour where T : SingletonMonoBehaviour<T>
{
private static T _instance;
public static T instance
{
get
{
CreateInstance();
return _instance;
}
}
public static void CreateInstance()
{
if (_instance == null)
{
_instance = FindObjectOfType<T>();
if (_instance == null)
{
var go = new GameObject(typeof(T).Name);
_instance = go.AddComponent<T>();
}
if (!_instance.initialized)
{
_instance.Initialize();
_instance.initialized = true;
}
}
}
public virtual void Awake ()
{
if (Application.isPlaying)
{
DontDestroyOnLoad(this);
}
if (_instance != null)
{
DestroyImmediate (gameObject);
}
}
protected bool initialized;
protected virtual void Initialize() { }
}
}
微信SDK类 WeChatSDK.java
public class WeChatSDK
{
private static final String TAG = "WeiXinSDK";
private static final String APP_ID = "*************";
private static IWXAPI m_wxApi;
private static Activity mActivity;
public static void Init(Activity activity)
{
mActivity = activity;
m_wxApi = WXAPIFactory.createWXAPI(mActivity, APP_ID);
m_wxApi.registerApp(APP_ID);
mActivity.registerReceiver(new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
m_wxApi.registerApp(APP_ID);
}
}, new IntentFilter(ConstantsAPI.ACTION_REFRESH_WXAPP));
}
public static IWXAPI GetWXAPI()
{
return m_wxApi;
}
public static void sendReq(SendMessageToWX.Req req)
{
m_wxApi.sendReq(req);
}
public static void ShareImg(...)
{
......
}
public static void ShareCallback(BaseResp resp)
{
......
}
}
配置 FileProvider
A. 添加 androidx 依赖
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
}
B. 在 AndroidManifest.xml 中配置
<provider
android:authorities="com.xxx.xxx.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
C. 在 res/xml 路径下创建 file_paths.xml 文件并输入
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="opensdk_external" path="Images/tmp"/>
<root-path name="opensdk_root" path=""/>
</paths>
注:如果运行发现找不到 android.support.v4 包,在 gradle.properties 中配置
android.enableJetifier=true
android.useAndroidX=true
QQSDK类 QQSDK.java
public class QQSDK
{
private static final String TAG = "QQSDK";
private static final String APP_ID = "1234567";
private static final String APP_AUTHORITIES = "com.xxx.xxx.fileprovider";
private static Tencent mTencent;
private static Activity mActivity;
private static IUiListener mShareListener;
public static void Init(Activity activity)
{
mActivity = activity;
mTencent = Tencent.createInstance(APP_ID, mActivity.getApplicationContext(), APP_AUTHORITIES);
if (mTencent == null)
{
Log.e(TAG, "Tencent instance create fail!");
}
mShareListener = new DefaultUiListener()
{
@Override
public void onComplete(Object o)
{
Log.d(TAG, "onComplete");
UnityPlayer.UnitySendMessage("PlatformHelper", "QQShareCallBack", "onComplete");
}
@Override
public void onError(UiError uiError)
{
Log.d(TAG, "onError");
}
@Override
public void onCancel()
{
Log.d(TAG, "onCancel");
}
};
}
public static IUiListener GetListener()
{
return mShareListener;
}
public static void ShareImg(String url)
{
......
}
}
|