自学内容网 自学内容网

delphi中TBytesToTJavaArray的使用

    写这文章的原因在于,这么多年做安卓开发中使用TBytesToTJavaArray的时候出现了一些误解。而这些是因为在某个APP运行过程中,出现了闪退的情况(需要感谢朋友的测试及抓取运行日志,之前查找问题及测试的过程本文就忽略不写,重点说一下在查看日志发现原因后的分析与处理)。

一、问题情况

我们先看看日志的重点

日志内容

错误日志关键信息内容:Abort message: 'JNI ERROR (app bug): global reference table overflow (max=51200)global reference table dump:

这个错误是由Java本地接口(JNI ERROR)报出的,说明在JNI层面发生了一个错误。具体内容是全局引用表溢出。在JNI中,全局引用(Global Reference)是一种特殊的引用类型,它允许我们从delphi本地代码去访问Java对象,通常情况下这些对象在本地方法执行完毕后可能会被垃圾收集器回收。每个JNI环境都有一个全局引用表,这个表有一个固定的最大容量限制(从日志信息看出,我们的max=51200)。而我们运行的程序创建的全局引用数量应该是超过这个限制,所以出现溢出错误,导致APP闪退。

二、问题原因分析

上图红框内容与我们程序中循环处理的数据内容(长度)一致,加上之前就分析过出现问题的大致方向,再通过这次的日志内容,基本就能定位到问题代码处。

问题代码处

这里是Delphi使用JNI调用JAVA文件输出流的写操作,其中写入的数据类型为TJavaArray<Byte>,而通常在delphi中我们是使用的是TBytes数据类型(上图的vData变量),那么这里就必须要对数据类型进行一次转换。Delphi为我们提供了一个转换函数TBytesToTJavaArray, 在平常的使用中,我们都是如上图那样直接使用TBytesToTJavaArray转换TBytes数据类型后作为函数的参数使用。

上图的代码,之前也一直正常使用(可能是以前没有超出51200条的数据内容),所以从来没怀疑到这里会出现问题,一直认为让系统自动销毁TBytesToTJavaArray处理后的数据就行了。这次出现问题,很明显的就怀疑到这样的使用是否存在问题,先查看了部分delphi系统源码的使用,也基本都是这样直接转换使用的TBytesToTJavaArray(AValue),如下图(Delphi源码中的使用方式):

为了进一步弄清楚,我去查看了TBytesToTJavaArray的处理代码:

class function TAndroidHelper.TBytesToTJavaArray(const ABytes: TBytes): TJavaArray<Byte>;
var
  LLength: Integer;
begin
  LLength := Length(ABytes);
  Result := TJavaArray<System.Byte>.Create(LLength);
  if LLength > 0 then
    Move(ABytes[0], PByte(Result.Data)^, LLength);
end;

从这个转换代码可以看出,他是先创建了一个新的TJavaArray<Byte>,再查看系统TJavaArray的定义,找到了下面这段代码:

destructor TJavaArray<T>.destroy;
begin
  if (FArrayElem <> nil) and Assigned(FArrayReleaser) then
    FArrayReleaser(TJNIResolver.GetJNIEnv, Handle, FArrayElem, 0);
  TJNIResolver.DeleteGlobalRef(Handle);
  inherited;
end;

代码里的TJNIResolver.DeleteGlobalRef(Handle);,正是解决之前日志里错误问题的方法。在使用完全局引用(Global Reference)后,需要使用DeleteGlobalRef来释放它们。也就是说,TJavaArray<System.Byte>.Create(LLength);创建后,使用完成,应该是需要再使用TJNIResolver.DeleteGlobalRef去释放的。

然后继续查看了系统的一些源码,大多数都是直接转换数据,并没有去释放之类的,直到看到一段不同的代码:

procedure TAndroidBluetoothSocket.DoSendData(const AData: TBytes);
var
  LJBytes: TJavaArray<System.Byte>;
begin
  inherited;
  if FJOStream = nil then
    FJOStream := TJOutputStream.Wrap(FJBluetoothSocket.getOutputStream);

  if FJOStream <> nil then
  begin
    LJBytes := TBytesToTJavaArray(AData);
    try
      FJOStream.write(LJBytes);
    finally
      LJBytes.Free;
    end;
  end
  else
    EBluetoothSocketException.Create(SBluetoothSocketOutputStreamError);
end;

这是一段蓝牙数据的发送处理代码,他其中就使用到了变量LJBytes,并且在使用完成后进行了释放,而这里的应用与我们程序里的应用场景是基本一致的,也都是向流发送数据。

三、解决问题

通过这些查看与分析,我们将之前程序里的处理代码修改为:

var
  vSend:TJavaArray<Byte>;
begin
// 写数据
if Assigned(FFileOutputStream) then
begin
        vSend:= TBytesToTJavaArray(vData);
        try
          FFileOutputStream.write(vSend, 0, vLen);
          FFileOutputStream.flush;
        finally
          vSend.Destroy;
        end;
end;
  Result := True;
end;

修改完成后重新编译、测试,发现问题解决。

四、总结

 在使用delphi开发过程中,多数情况下使用TJavaArray<System.Byte>数据类型的时候,应该是可以直接用TBytesToTJavaArray进行数据类型的转换并使用,但涉及到与流处理相关的时候,需要在使用完成后去释放清理。


原文地址:https://blog.csdn.net/tanqth/article/details/143029610

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!