化石原创文章,转载请注明来源并保留原文链接


Unity侧:

AAR文件可以放在assets目录的任何地方

AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");     

AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");     

AndroidJavaObject unityContext = currentActivity.Call<AndroidJavaObject>("getApplicationContext");

这三步能拿到大多数aar需要的context,activity

AndroidJavaClass.CallStatic(methodName, parameter0, parameter1, pameter2);

使用最上面的方法得到Android Class后,内部的static方法,用这个方法调用。多个参数依次排列在方法名字后面即可(官方文档没有例子,使用了数组形式,没测过)

AndroidJavaClass.Call(methodName, parameter0, parameter1, pameter2);

调用实例的方法。

Java侧:

com.unity3d.player包中,有类

UnityPlayer,提供方法UnitySendMessage(GameObjectName, MethodName, parameter)

该类的反编译结果可以在这里看到。截取方法的定义如下:

public static native void UnitySendMessage(String var0, String var1, String var2); 

写插件的过程中,如果不想让java侧代码一定跟Unity3d绑定,那么可以使用java的反射来取得上面的方法。

Class playerClass = Class.forName("com.unity3d.player.UnityPlayer");

Log.d("MyTag", playerClass.toString());

Method sendMessageMethod = playerClass.getMethod("UnitySendMessage", String.class, String.class, String.class);

Log.d("MyTag", sendMessageMethod.getName() + ", " + sendMessageMethod.toString());


化石原创文章,转载请注明来源并保留原文链接



化石原创文章,转载请注明来源并保留原文链接


制作好一个aar后,可以在项目中使用。具体方式是:

1、放置aar文件到Project files下的app/libs中 (Android Studio项目的Project面板上开启Project Files视图,就能看到libs目录)


2、Gradle Scripts/build.gradle文件中,depencies内容中,implementation fileTree里的include内容,添加*.aar。直接修改的话就把原来的jar改成aar即可。

比如:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

这样Android Studio编译的时候就知道去找这个aar中的内容,包括class等。


化石原创文章,转载请注明来源并保留原文链接



化石原创文章,转载请注明来源并保留原文链接


在vs(visual studio)的C++、C#,或者XCode的Object-C中,配置一个库的工程比较直观。库(library)和项目(project)工程是平等的,在创建的时候就会有分开的选项让用户选择,当前的项目是库还是项目。

使用Android Studio的话,库的创建相对比较隐晦。

有两种方式:

第一种

1、点击File > New > New Module
2、在出现的Create New Module窗口中,可以看到Android Library

利用这种方式,可以在当前项目中创建出一个跟项目级别并行的Libary项目来。

第二种

1、先创建项目
2、改动build.gradle(module)文件

apply plugin: 'com.android.application'

改成

apply plugin: 'com.android.library'

3、注释掉 applicationId 那一行

这样看来,AAR跟application没有太多区别,也就是没有applicationId而已(所以Android Studio知道这个不是个应用)。


化石原创文章,转载请注明来源并保留原文链接



化石原创文章,转载请注明来源并保留原文链接


工作线程(work thread、background thread)是不能直接操作(访问)UI的。这个现在基本上是通识。C#使用Invoke可能是语言中最方便的方式。Android中的使用Handler的方式也比较方便。

这里记录步骤:

1、Activity类中,创建Handler成员变量,写好处理Message的回调。

2、在工作线程中,从Message.obtainMessage()得到一个实例的message,然后填充相应的内容,内容可以是简单的int、复杂的object,android都帮你想好了。很方便。

3、上面的工作线程一般作为activity类的inner class就比较方便,这样我们可以访问Handler成员变量。要发送第2步产生的message,直接使用handler.sendMessage(Message msg)。

4、第3步消息发送后,第1步的回调就会被调用,这里可以直接访问ui(view)。

示例代码:

package com.example.bluetuth;

import androidx.appcompat.app.AppCompatActivity;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.EditText;
import android.widget.TableLayout;
import android.widget.TableRow;

public class ServerActivity extends AppCompatActivity {

TableLayout tableLayout;

final Handler handler = new Handler() {
@Override
public void handleMessage(Message message) {
TableRow row = (TableRow) tableLayout.getChildAt(0);
EditText et = (EditText) row.getChildAt(0);
et.setText((String)message.obj);
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_server);

EditText et = findViewById(R.id.editText);
et.setFocusable(false);
et.setText(Const.SERVICEUUID.toString());

tableLayout = findViewById(R.id.clientTable);

AcceptThread acceptThread = new AcceptThread();
acceptThread.start();
}

private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
private final String NAME = "ArtExplorerBlueServer";
private final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();

public AcceptThread() {
BluetoothServerSocket tmp = null;
try {
tmp = adapter.listenUsingRfcommWithServiceRecord(NAME, Const.serviceUuid);
}catch (Exception e) {
e.printStackTrace();
}
mmServerSocket = tmp;
}

public void run() {
BluetoothSocket connectedSocket = null;

while(true) {
try {
connectedSocket = mmServerSocket.accept();
BluetoothDevice device = connectedSocket.getRemoteDevice();
String name = device.getName();
String address = device.getAddress();
//update ui
Message message = handler.obtainMessage();
message.what = 0;
message.obj = name == null ? address : name;

handler.sendMessage(message);
}
catch (Exception e) {
e.printStackTrace();
break;
}

//We can close this server if we only accept one client.
//But we don't since we need to support multiple clients.
//We need a separate thread to handle one client
Thread clientThread = new ConnectedThread(connectedSocket);
clientThread.start();
}
}
}
}

化石原创文章,转载请注明来源并保留原文链接



化石原创文章,转载请注明来源并保留原文链接


要在两台设备上的应用之间创建连接,必须同时实现服务器端和客户端机制,因为其中一台设备必须开放服务器套接字,
 而另一台设备必须发起连接(使用服务器设备的 MAC 地址发起连接)。当服务器和客户端在同一 RFCOMM 通道上分别
 拥有已连接BluetoothSocket 时,二者将被视为彼此连接。 这种情况下,每台设备都能获得输入和输出流式传输,并
 且可以开始传输数据,在有关管理连接的部分将会讨论这一主题。 本部分介绍如何在两台设备之间发起连接。
 服务器设备和客户端设备分别以不同的方法获得需要的 BluetoothSocket。服务器将在传入连接被接受时收到套接字。
  客户端将在其打开到服务器的 RFCOMM 通道时收到该套接字。
 一种实现技术是自动将每台设备准备为一个服务器,从而使每台设备开放一个服务器套接字并侦听连接。然后任一设备
 可以发起与另一台设备的连接,并成为客户端。 或者,其中一台设备可显式“托管”连接并按需开放一个服务器套接字,
 而另一台设备则直接发起连接。 

上面这段是Google文档中的。也就是说,当我们配对好两台蓝牙设备后。我们想要实现设备间的数据传输,那么必须使用Server-Client模式。一台设备做server,另外一台做client。

实际操作中,跟TCP连接差不多。Server和Client通过自定义的uuid来代替TCP连接中的ip(或者说是域名)。也就是说,Server必须绑定自己的服务器到某个uuid上,而client必须知道这个uuid是什么。

作为服务器

1、创建侦听服务

BluetoothServerSocket serverSocket = BluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, Const.serviceUuid);

2、侦听

BluetoothSocket connectedSocket = BluetoothServerSocket.accept();

这个是一个同步方法,只要收到连接后才会返回,所以基本上需要放在一个单独的线程中。

3、有连接后得到相应的remote信息

BluetoothDevice device = connectedSocket.getRemoteDevice();
String name = device.getName();
String address = device.getAddress();

4、跟remote通讯

InputStream inStream = connectedSocket.getInputStream();
OutputStream outStream = connectedSocket.getOutputStream(); 

上述的读,使用InputStream.read();,这是一个同步操作,所以一般也用一个单独的线程处理。

所以,一个服务器,在这里就有三个线程:UI(主线程),侦听线程,读线程

作为客户端

1、连接服务器(通过uuid)

BluetoothSocket socket = BluetoothDevice.createRfcommSocketToServiceRecord(Const.serviceUuid);
socket.connect();

2、发送数据

直接在主线程中使用OutputStream.write();

3、接受数据

跟服务器相似,需要单独的线程

相关代码较多,放在了github,自己最多用2个客户端测试过。

https://github.com/jycgame/AndroidBluetoothCommucate


化石原创文章,转载请注明来源并保留原文链接