Delphi跨平台开发Android:如何使用JNI JAR和Java并注意关键事项
2Pascal-新时代的Pascal-关于 调用 JNI JAR java 的说明和注意事项,调用第 靠写不下了 - Powered by Discuz!
关于 调用 JNI JAR 的说明和注意事项,调用第三方 JAR SDK 和 翻译 安卓 JAVA 代码 的说明 V2017.10.18
(* ************************************************ *)
(* *)
(* *)
(*设计:爱吃猪头肉 & Flying Wang 2015-04-15 *)
(*上面的版权声明请不要移除。*)
(* *)
(* ************************************************ *)
本人所在的群(① FireMonkey[移动开发] 165232328)
对于安卓系统
谷歌 API 提供的是 JNI 接口。
第三方 JAR SDK 提供的也是 JNI 接口。
您的手机,一般会内置 安卓 SDK 的大部分接口。
但是有些 API 可能没有内置。
FMX 安卓工程默认会给您提供多个 谷歌的 SDK 。
因此 大部分 谷歌的 JAR,您都不需要自己去找。
对于 第三方的 SDK 来说。
基本上分为 jar + so、纯jar 和 纯so 三种。
纯so 就是类似于 dll 的接口。只需要注意 dll 是 stdcall 类型,其他所有平台都是 cdecl 类型。 不写是不行的。因为 delphi 不写的是默认的 pascall 类型。
如果你不会调用 dll 那么也就不会调用 so 。
本文不讨论调用 so。
但是告诉你们 SO 文件文件的路径:编辑 RemotePath 列。路径是(xe5) library\lib\armeabi (xe6或以上) library\lib\armeabi-v7a
以上路径的 so 一般是给 jar 加载用的。
自己加载可以用 LoadLibaray 或者 dlopen 。记得用对应的 FreeLibaray 和 dlclose
也可以像定义 DLL 一样,定义 so 的函数接口。这样就不用写代码加载了。
如果只有你自己用,也可以发布到其他你能访问的路径。但是,只能写代码加载了,路径要写完整。
armeabi-v7a 是 arm32 的,如果你需要支持 64bit 除了这个目录,还需要 arm64-v8a。
64bit 下凡是需要用到 so 的,这两个目录都需要放对应的 so。
对于 jar 无论它有没有提供 so 。我们都只能使用 jar 的对外接口。
除非是安卓基本 API,或者是 DELPHI 已经提供的 JAR 。否则 其他的 JAR ,只要你用到了,就必须加入到 安卓的 工程中。 具体如何添加,请自行百度。
即便是 基本 API,EMBT 也没有全部给大家 转成 JNI 接口。
所以当你用到一个 EMBT 没翻译的 API 的时候,请自行用 工具 翻译。
翻译:可以理解为 语言的转换,接口的导出,也就是变成 pascal语法的 pas 文件
当你得到一个 JAR 的时候,请用工具翻译成 pascal 文件。
目前推荐 2 个 工具。
1. 官方的 java2op.exe 。支持 .jar .java .class 三种格式的文件。
2. 爱吃猪头肉的 JarOrClass2pas 。支持 .jar .class 两种格式的文件。
其他工具都是垃圾。千万别用。否则活该。
当你用工具得到 pas 文件后,注意:
1. 一般会得到 大量的 无用的,错乱的,重复的 接口。请将它们删除。
2. 即便是没问题的接口,如果用不到,也请删除。
3. 转换工具会写出一些 uses 的单元,这些单元可能不存在。
对于jar 或 class 文件引用了别的 jar,就容易出现不认识的 unit 的 uses。请找到这些 jar 继续翻译。
当你 jar 已经加入到 安卓项目中。
jni pas 文件已经准备好,也加入到 安卓项目中。
就可以开始调用 jar 接口了。
接下来说明下基本的类型变化。
Int 就是 Integer ,很多基本对象大家都可以自己想到。
string 是 JString 。
Uri 要翻译成 Jnet_Uri。
上述两个类型 EMBT 提供了互相转换的函数。
还有个别的其他改变名称的类型。这里就无法一一列举了。如果 你发现一些 类名 EMBT 应该提供了,但是找不到,请通过 Signatur 在 Find in Files 对话框中查找。
例如 搜索 java/lang/Class 可以发现 JCalss 也改名了。
int [] 就是 TJavaArray<Integer> ,基本类型用 TJavaArray<>。
但是 string [] 是 TJavaObjectArray<JString>,对象类型的一般都用 TJavaObjectArray<>。
ArrayList<String> 要翻译成 JArrayList
而且任何 ArrayList<PendingIntent> 也就是 ArrayList<某对象> 都要翻译成 JArrayList。
有些类型的名称比较特别。例如 java 的 Phone 类型 EMBT 已经翻译成了 JCommonDataKinds_Phone。这是因为 Phone 是 CommonDataKinds 的内部类。
数据类型 OK 了。那么就谈谈 Java 类的 构造函数 类成员 类方法 和 普通成员 普通方法。
如果不懂这些,请自行百度。建议好好看看 面向对象开发 课程。
Java 类一般会提供默认构造函数,到了 pas 里头,他的函数名叫 init 小写。默认不带参数。
但是很多 java 类 会重载 构造函数,提供带参数的版本。
接下来,我们需要百度下你打算使用的 jni 的 demo 。
安卓开发,最大的优势就是,网上全是 demo ,虽然是 java 语言的。
找到 demo 代码之后,你就需要 按照 代码的逻辑,进行语言的翻译。
下面提供几种常见的 代码 翻译。
关键内容,请回复。
本帖隐藏的内容
1 某对象的 构造。
1.1 java 代码
xxx x = new xxx(参数或没有参数);
1.2 翻译代码
var
x: Jxxx;
begin
x := TJxxx.JavaClass.init(参数或没有参数);
if x = nil then 出错了。
end;
上述代码,演示了构造函数的使用。 注意:
1.3 x 是 Jxxx
1.4 TJxxx.JavaClass 是 TJ 开头的。JavaClass. 能提供给你 这个类的所有类方法,类成员,包括构造函数。
1.5 不是所有类都可以使用默认构造函数的。
1.6 如果你确认可以使用默认构造函数,但是 pas 中没有。可以使用如下代码构造。
x := TJxxx.Create;
2. 某对象的 非默认构造,使用类方法的构造。
2.1 java 代码 通过 new 创建对象。
xxx x = new xxx.yyy(参数或没有参数);
2.2 java 代码 不通过 New 创建对象。
xxx x = xxx.yyy(参数或没有参数);
2.3 翻译代码
var
x: Jxxx;
begin
x := TJxxx.JavaClass.yyy(参数或没有参数);
if x = nil then 出错了。
end;
上述代码,演示了使用类方法来得到类 xxx 的对象。
3. 通过其他类的对象,来得到对象 x。
3.1 java 代码
xxx x = yyy.zzz(参数或没有参数);
3.2 翻译代码
var
x: Jxxx;
begin
x := yyy.zzz(参数或没有参数);
if x = nil then 出错了。
end;
非常简单,就是加了个冒号。yyy 是另一个对象。 zzz 可以是 yyy 对象的类方法、类成员、普通方法或普通成员。
4. 通过强制类型转换,来得到对象 x,一般不常用。
4.1 java 代码
xxx x = (xxx)yyy.zzz(参数或没有参数);
zzz 返回的类型不是 xxx,也不是 xxx 的派生类。
4.2 翻译代码
var
xLocalObject: JObject;
x: Jxxx;
begin
xLocalObject := yyy.zzz(参数或没有参数);
或者
xLocalObject := JObject(yyy.zzz(参数或没有参数));
if xLocalObject = nil then 出错了。
x := TJxxx.Wrap((Obj as ILocalObject).GetObjectID);
if x = nil then 出错了。
end;
注意 Wrap 的开头,也是 TJ
经过 [臺北]wildsky(2590003092) 的验证
x := TJxxx.Wrap((yyy.zzz(参数或没有参数) as ILocalObject).GetObjectID);
这样少一个变量的写法。部分机器发生闪退。
用两个变量来完成,就不会发生闪退,我个人觉得这个肯定不是问题的原因,下面才是。
对于某些 JNI 服务
经过 [深圳]机器猫(5909386) 的验证
TJContext.JavaClass.VIBRATOR_SERVICE
TJActivity.JavaClass.VIBRATOR_SERVICE
用上面个可以,下面的就会闪退。
出现的错误提示可能是:Project xxx.apk raised exception class Aborted(6).
J 开头 TJ 开头 都是 约定俗成。你也可以 SB 开头 TSB 开头。
默认 TJ 开头的是 DELPHI 的类型。
J 开头的才是 JAVA 的类型。
TJxxx.JavaClass 返回 JxxxClass 类型,这个类型专门代表类方法和类属性。 Jxxx 则代表类对象的类型。
5. 不通过变量来操作某类型 xxx 的代码。
5.1 java 代码
xxx.yyy(参数或没有参数).zzz(参数或没有参数);
5.2 翻译代码
TJxxx.JavaClass.yyy(参数或没有参数).zzz(参数或没有参数);
5.3 xxx 是类型 yyy 是类函数。
5.4 可以出现 xxx.JavaClass.yyy(参数或没有参数).zzz(参数或没有参数).nnn(参数或没有参数); 这种多级的调用。
6. 已存在对象 xxx 调用他的方法来操作的代码。
6.1 java 代码
xxx.yyy(参数或没有参数);
6.2 翻译代码
xxx.yyy(参数或没有参数);
6.3 xxx 是对象 yyy 是该对象的类函数或成员函数。
6.4 可以出现 xxx.yyy(参数或没有参数).zzz(参数或没有参数); 这种多级的调用。
7. 使用常量
7.1 java 中的常量,一般都是类的成员。而且一般是类的类成员。
7.2 java 代码
xxx = yyy.zzz;
//xxx 是一个变量 yyy 是一个类名 zzz 是常量名
7.3 翻译代码
xxx := TJyyy.JavaClass.zzz;
7.4 也就是说 zzz 被翻译到了 Jyyy 的 Class 版本的 接口中。
7.4 效果等于
xxx := JyyyClass.zzz;
7.5 不建议用上面的代码。请按 7.3 的版本写。
注意,对于我们 pascal 来说没有参数括号是可以省略的。 C JAVA 等是不允许省略的。
对于 数组 TJavaArray<XXXX> 如果你特别想自己建立对象。
可以写
var
xxx: TJavaArray<XXXX>;
begin
xxx := TJavaArray<XXXX>.Create(个数);
然后用 xxx.Items[编号] 来访问。
end;
TJavaObjectArray 也是如上的办法。
目前没有发现动态修改数量的办法。也就是数组的个数,是建立的时候就确定的。
8. 一些常用的 Jni 和 pascal 类型的互转
打开
unit Androidapi.Helpers;
自己去看吧。
有 TBytes string JCharSequence JURI JLong 等几种常用类型的互转。
如果你使用一个方法,发生Segmentation fault(11) 可能是对象为 nil 或者 函数不存在(一般是版本不同,有的版本函数就不存在)。
如果你使用一个方法,发生非法操作,说明没有这个方法(大概是名称或参数有错误)。
如果提示你 java class xxx could not be found,如果是官方 xxx ,那么是你的手机内部没提供这个接口,你可以自己找官方的 jar 文件来加入、
如果是第三方的 xxx,那更简单了,这个 xxx 对应的 jar 文件,你肯定没加入到 你的工程中。
如果你确认你加了(参考
第二个箭头,确认加了,就删了再加一遍,还不行,可能是你编译出的结果目录,存在垃圾,删除编译结果目录试试。),建议做如下操作
打开你的 Android 工程,点菜单项 Project—>Deployment,打开部署子窗口,点 Revert to Default 按钮,就是那个向左的弯箭头:
出现 Revert to default 对话框:
选中第一项“Revert for all configurationsthe active platform”,点 OK。
注意:不论其默认选项如何,在这里都必须选择其中一个并点OK,否则你的Android程序在调用JAR文件时将会出现“Java Class xxx could not befound”的错误。
以上文字来源于 http://blog.sina.com.cn/s/blog_648d306d0102vfgq.html
如果 Objs 是 TJavaObjectArray<Jxxxx> 的 Objs.Items[x] 或者 Objs[x] 发生错误(Segmentation fault(11)),那么就是不能这样用,改用
TJxxxx.Warp(Objs.GetRawItem(x)) 试试。多谢 [深圳]机器猫(5909386) 的测试。
一般建议
uses
{$IF CompilerVersion >= 27.0} // >= XE6
Androidapi.Helpers,
{$ENDIF}
FMX.Helpers.Android,
Androidapi.JNI.JavaTypes,
Androidapi.NativeActivity;
关键内容,请回复。
本帖隐藏的内容
当需要一个 Activity 对象的时候,我们只能提供 TAndroidHelper.Activity。因为 FMX 只存在这个一个 Activity。java UI 代码中 self(this) 就是 Activity 对象。
当需要一个 View 对象的时候,默认的是 TAndroidHelper.Activity.getWindow.getDecorView。其他的就不知道了。
当需要一个 Context 对象的时候,可以试试 TAndroidHelper.Context,这是全局的。
当你需要一个 getApplicationContext 对象的时候,可以试试 TAndroidHelper.Context.getApplicationContext,这是因为你不懂自己看源码,不是我的错。
{$IF CompilerVersion >= 30.0} // >=RAD10
TAndroidHelper.Activity
TAndroidHelper.Context
{$ELSE}
SharedActivity
SharedActivityContext
{$ENDIF}
无论如何,当你得到一个 java 对象一定要先检查 是不是 nil,否则轻则提示错误,重则闪退。
EMBT 经常忘了检查,所以就闪退,例如你在窗体中放了一个 IAP 支付控件,很多手机上都会闪退,就是因为没检查 nil。
如果是正在开发的 APP 在任何机器上闪退,特别是旧版本升级来的,别人复制给你的。一般是 发布信息混乱,造成的。工程的 发布(部署)信息需要【重新加载】。Deployment 需要 Revert to Default
如果是任何APP。包括新建的空 APP,在特定的机器上一运行就闪退。说明是一个 BUG。请在本群的 不看后悔 系列中 解压 找 XE 修复 APK 启动,提示 Cannot deploy," " file not found.txt。
当你使用一个 jni 对象的时候。如果是个可以显示的对象。很多时候需要:
CallInUiThread(
procedure
begin
jni 代码。
end);
有时候 还得换成 CallInUIThreadAndWaitFinishing。
只有这样 代码才不会死锁。
也就是 如果不这么写。你的 APP 就会出现 未响应。
有些不是显示的 jni 对象,也需要这样写。不过不常见。
如果你收到一个错误 CalledFromWrongThreadException,就是需要 CallInUiThread 了。
如果收到的是
Can't create handler inside thread that has not called Looper.prepare()
也是需要 CallInUiThread 了。多谢 [新会]supermay(15832782) 测试。
注意:不要将大片代码进入上述的代码块中。尽量减少相关代码。最好是用 DEBUG ,找出弹出这类错误提示的代码行。不会 DEBUG 请看书。
在 安卓的世界里,回调函数是不存在的。但是可以使用接口来做到回调。
一般这种接口 会被定义成 Listener。
当你需要继承(实现)一个 java 接口的时候,就需要查看有关代码了。
本帖隐藏的内容
使用 DELPHI IDE,在 Search 菜单打开 Find In Files 对话框。
输入搜索关键字
= class(TJavaLocal,
搜索范围 Search in directories
选择到你的安装目录的源代码目录,选中 Indude subdirectories
好好找吧。
你会找到好多代码。
他们都是继承(实现) java 接口的好例子。
按照面向对象的说法 接口必须实现。所以上面找到的代码是必然的。
然后定义出这个类的 对象,就可以当参数在 jni 中使用了。
不过这种类型,是 delphi 的实现,所以别忘了 free。
个人建议大家好好研读【unit System.Android.Bluetooth;】。
有时候 接口提供的 回调函数 可能是在线程中运行的。
这时候你实现这个函数的时候,要注意。
UI 对象 无论是 FMX 的还是 jni 的。你都需要线程同步。
线程同步的简单方法就是。
...
//线程或回调函数里的一些代码。
TThread.Synchronize(nil, //或者 用线程自己的同步函数。 Synchronize(
procedure
begin
//你的界面交互代码。
end);
//继续线程或回调函数的代码。
...
如果是调用事件,
建议用
TThread.Queue(nil,
procedure
begin
//你的事件调用。
end);
如果你 DEBUG 中收到了 Bitmap size too big 的提示,有可能就是 该用同步,没同步造成的。
10.2 以后,不会再有这个提示。 Bitmap 支持线程中使用了。
10.2 开始,主线程和 UI 线程 为同一个线程了。
CallInUiThread 应该可以大批量的不用了。
有了以上知识,你基本上翻译 java 的代码,就不成问题了。
别说,你不会 顺序、判断、循环、函数调用。
如果你想了解一个 第三方 view 是如何显示到 FMX 中的。
可以参考 unit FMX.WebBrowser.Android 和 unit FMX.Media.Android。
另外,很多操作,都需要对应的权限,别忘了加上。
对于 4.4 以上的系统,想要访问外置存储卡。需要加上
<uses-permission android:name="android.permission.READ_MEDIA_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE"/>
这 2 个权限。
目前 IDE 没有提供。
您需要在 AndroidManifest.template.xml 文档中,自己加入,用 IDE 就能打开,找到 <%uses-permission%>,将上述权限文字加入到 这行下面就行。
但是,不保证所有机器都有效。
如果发现需要使用 安卓下面的 命令行,例如执行 su sh ping 等。
可以参考 QDac http://blog.qdac.cc 的代码,可能叫 QRuntime。
也可以参考 本群的 重启你的手机 源代码。
对于学习 翻译 安卓 java 代码为 pascal 。
最好先看 EMBT 的源代码。然后看 EMBT 的 Samples。
也可以好好看 本群的群共享。
里头好多调用 jni 的 DEMO。
相关工具
一种简单的 jar 转 pas 工具,不如 EMB 官方提供的 工具 强大。
JarOrClass2Pas FlyingWang V1.0.2016.426 附赠 java 转 Jar.zip
2Pascal-新时代的Pascal-JarOrClass2Pas FlyingWang 1.0.2020.1116.27 附赠 java 转 Jar.zip - Powered by Discuz!
(出处: 2Pascal-新时代的Pascal)
如果你希望使用 EMB 官方工具,请打开你的 帮助,搜索 Java2OP.exe 。
RAD10RTM 加载 jar 存在 BUG
Log in - Embarcadero Technologies
QC 中有解决办法。
建议去 EMB 官网注册 EDN 账号,即可登录。
上面的 BUG 新版本已经 FIX 了。
手动翻译 JNI 的老文章
JNI 翻译 转 Delphi 的 经验 方法
http://www.2pascal.com/forum.php ... &tid=1100&fromuid=4
(出处: 2Pascal-新时代的Pascal)
可以参考的 DEMO。
BaiduLocation_百度定位_LBS_定位_5_SDK_DEMO_Add_Jar_BaiduLBS_Android5
2Pascal-新时代的Pascal-BaiduLocation_百度定位_LBS_定位_5_SDK_DEMO_Add_Jar_BaiduLBS_Android5 - Powered by Discuz!
(出处: 2Pascal-新时代的Pascal)
安卓 服务 的 一些 相关代码,自动启动服务或定时启动APP。
2Pascal-新时代的Pascal-安卓 服务 的 一些 相关代码,自动启动服务或定时启动APP。 - Powered by Discuz!
(出处: 2Pascal-新时代的Pascal)
专门的 DELPHI 实现 JAVA 接口 的 DEMO。
消息注册接收 DEMO。
Java 的消息及事件的一般做法的 DELPHI 版 源码。
安卓 接口实现的事件 动态注册 接收 WIFI 变化消息 Demo
http://www.2pascal.com/forum.php ... &tid=3008&fromuid=4
(出处: 2Pascal-新时代的Pascal)
反射调用 java api
java demo
[mw_shl_code=java,true] /**
* VIVO
* <p>
* android.util.FtFeature
* public static boolean isFeatureSupport(int mask);
* <p>
* 参数:
* 0x00000020表示是否有凹槽;
* 0x00000008表示是否有圆角。
*
* @param context Context
* @return hasNotch
*/
private static boolean hasNotchInVivo(Context context) {
boolean hasNotch = false;
try {
ClassLoader cl = context.getClassLoader();
Class ftFeature = cl.loadClass("android.util.FtFeature");
Method[] methods = ftFeature.getDeclaredMethods();
if (methods != null) {
for (int i = 0; i < methods.length; i++) {
Method method = methods;
if (method.getName().equalsIgnoreCase("isFeatureSupport")) {
hasNotch = (boolean) method.invoke(ftFeature, 0x00000020);
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
hasNotch = false;
}
return hasNotch;
}[/mw_shl_code]
作者:brucevanfdm
链接:漫谈Android手机刘海屏(附工具类) - 简书
delphi demo
本帖隐藏的内容
[mw_shl_code=delphi,true]const
VIVO_NOTCH = $00000020;//是否有刘海
VIVO_FILLET = $00000008;//是否有圆角
type
JMethodInvoke = interface;
JMethodInvokeClass = interface(JObjectClass) // or JObjectClass // SuperSignature: java/lang/reflect/AccessibleObject
['{78FDACCD-05FA-478C-AA11-4BF4794DFC7C}']
{ static Property Methods }
{ static Methods }
{ static Property }
end;
[JavaSignature('java/lang/reflect/Method')]
JMethodInvoke = interface(JObject) // or JObject // SuperSignature: java/lang/reflect/AccessibleObject
['{6190D24D-0B94-4836-92C4-7F8B23C4FC9B}']
{ Property Methods }
{ methods }
function invoke(receiver: JObject; args: TJavaObjectArray<JObject>): JObject; cdecl; //(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
{ Property }
end;
TJMethodInvoke = class(TJavaGenericImport<JMethodInvokeClass, JMethodInvoke>) end;
var
IsVivoFeatureSupportMethod: JMethodInvoke = nil;
ftFeatureClass: Jlang_Class = nil;
function IsVivoFeatureSupport(Mask: Integer): Boolean;
begin
Result := False;
try
if (IsVivoFeatureSupportMethod = nil) or (ftFeatureClass = nil) then
begin
var ClassName := 'android.util.FtFeature';
var MethodName := 'isFeatureSupport';
var Signature := '(I)Z';
if IsCanFindJavaStaticMethod(MethodName, Signature, ClassName) then
begin
//Result := TJFtFeatureUtil.JavaClass.isFeatureSupport(Mask);
//狗屁 vivo 一定要用反射调用。
var cl:JClassLoader := TAndroidHelper.Context.getClassLoader;
ftFeatureClass := cl.loadClass(StringToJString(ClassName));
// var methods:TJavaObjectArray<JMethod> := ftFeatureClass.getDeclaredMethods;
var methods:TJavaObjectArray<JMethod> := ftFeatureClass.getMethods;
if (methods <> nil) then
begin
var method: JMethod := nil;
for var I := 0 to methods.length - 1 do
begin
method := methods[I];
if (method.getName.equalsIgnoreCase(StringToJString(MethodName))) then
begin
IsVivoFeatureSupportMethod := JMethodInvoke(method);
break;
end;
end;
end;
end;
end;
if (IsVivoFeatureSupportMethod <> nil) and (ftFeatureClass <> nil) then
begin
var args := TJavaObjectArray<JObject>.Create(1); //maybe auto free
try
args.Items[0] := TJInteger.JavaClass.init(Mask);
Result := JBoolean(IsVivoFeatureSupportMethod.invoke(ftFeatureClass, args)).booleanValue;
finally
FreeAndNil(args);
end;
end;
except
Result := False;
end;
end;[/mw_shl_code]
推荐阅读
-
玩转Java底层:JMX详解 - jconsole与自定义MBean监控工具的实际应用与区别" 在日常JVM调优中,我们熟知的jconsole工具通过JMX包装的bean以图形化形式展示管理数据,而像jstat和jmap这类内建监控工具则由JVM直接支持。本文将以jconsole为例,深入讲解其实质——基于JMX的MBean功能,包括可视化界面上的bean属性查看和操作调用。 MBeans在jconsole中的体现是那些可观察的组件属性和方法,如上图所示,通过名为"Verbose"的属性能看到其值为false,同时还能直接操作该bean的方法,例如"closeJerryMBean"。 尽管jconsole给我们提供了直观的可视化界面,但请注意,这里的MBean并非固定不变,开发者可根据JMX提供的接口将自己的自定义bean展示到jconsole。以下步骤展示了如何创建并注册一个名为"StudyJavaMBean"的自定义MBean: 1. 首先定义接口`StudyJavaMBean`,接口需遵循MBean规范,即后缀为"MBean"且包含getter方法代表属性,如`getApplicationName`,和无返回值的setter方法代表操作,如`closeJerryMBean`。 ```java public interface StudyJavaMBean { String getApplicationName(); void closeJerryMBean(); } ``` 2. 编写接口的实现类`StudyJavaMBeanImpl`,实现接口中的方法: ```java public class StudyJavaMBeanImpl implements StudyJavaMBean { @Override public String getApplicationName() { return "每天学Java"; } @Override public void closeJerryMBean() { System.out.println("关闭Jerry应用"); } } ``` 3. 在代码中注册自定义MBean,涉及的关键步骤包括: - 获取平台MBeanServer - 定义ObjectName,指定唯一的MBean标识符 - 注册MBean到服务器 - 启动RMI连接器服务,以便jconsole能够访问 ```java public void registerMBean() throws Exception { // ... 具体实现省略 ... } ``` 实际运行注册后的MBean,您将在jconsole中发现并查看自定义bean的属性和调用相关方法。然而,这种方式相较于传统的属性/日志查看和HTTP接口,实用性相对有限,可能存在潜在的安全风险。但不可否认的是,JMX及其MBean机制对于获取操作系统信息、内存状态等关键性能指标仍然具有重要价值。例如: 1. **获取操作系统信息**:通过JMX MBean,可以直接获取到诸如CPU使用率、操作系统版本等系统级信息,这对于资源管理和优化工作具有显著帮助。
-
Delphi跨平台开发Android:如何使用JNI JAR和Java并注意关键事项
-
Grid++Report 锐浪报表开发常见问题解答集锦-报表设计 问:怎样在设计时打印预览报表? 答:为了及时查看报表的设计效果,Grid++Report 报表设计应用程序提供了四种查看视图:普通视图、页面视图、预览视图与查询视图。通过窗口下边的 Tab 按钮可以在四种视图中任意切换。在预览视图中查看报表的打印预览效果,在查询视图中查看报表的查询显示效果。如果在报表的记录集提供了数据源连接串与查询 SQL,在进入预览视图与查询视图时会利用数据源连接串与查询 SQL 从数据源中自动取数,否则 Grid++Report 将自动生成模拟数据进行模拟打印预览与查询显示。注意:在预览视图与查询视图中看到的报表运行结果有可能与在你程序中的最终运行结果有差异,因为在报表的生成过程中我们可以在程序中对报表的生成行为进行一定的控制。 问:怎样用 Grid++Report 设计交叉表? 答:Grid++Report 没有提供专门实现交叉表的功能,其它的报表构件提供的交叉表功能一般也比较死板和功能有限。利用 Grid++Report 的编程接口可以做出灵活多变,功能丰富的交叉表。示例程序 CrossTab 就是一个实现交叉表的例子程序,认真领会此例子程序,你就可以做出自己想要各种交叉表,并能提取一些共用代码,便于重复使用。 问:怎样设置整个报表的缺省字体? 答:设置报表主对象的字体属性,也就是设置了整个报表的缺省字体。如果改变报表主对象的字体属性,则没有专门的设置字体属性的子对象的字体属性也跟随改变。同样每个报表节与明细网格也有字体属性,他们的字体属性也就是其拥有的子对象的缺省字体。 问:怎样在打印时限制一页的输出行数? 答:设定明细网格的内容行的‘每页行数(RowsPerPage)’属性即可。另外要注意‘调节行高(AdjustRowHeight)’属性值:为真时根据页面的输出高度自动调整行的高度,使整个页面的输出区域充满。为假时按设计时的高度输出行。 问:怎样显示中文大写金额? 答:将对象的“格式(Format)”属性设为 “$$” 及可,可以设置格式的对象有:字段(IGRField)、参数(IGRParameter)、系统变量(IGRSystemVarBox)与综合文字框(IGRMemoBox),其中综合文字框是在报表式上设格式。 问:能否实现自定义纸张与票据打印? 答:Grid++Report 完全支持自定义纸张的打印,只要在报表设定时在页面设置中选定自定义纸张,并指定准确的纸张尺寸。当然要在最终输出时得道合适的打印结果,输出打印机必须支持自定义纸张打印。Windows2000/XP/2003 操作系统上可以在打印机上定义自定义纸张,也可以采用这种方式实现自定义纸张打印。 问:怎样实现 0 值不打印? 答:直接设置格式串就可以,在“数字格式”设置对话框中选定“0 不显示”,就会得到合适的格式串。也可以通过直接录入格式串来指定 0 不显示,但格式串必须符合 Grid++Report 的规定格式。另一种实现办法是在报表获取明细记录数据时,在 BeforePostRecord 事件中将值为零的字段设为空,调用字段的 Clear 方法将字段置为空。 问:怎样实现多栏报表? 答:在明细网格上设‘页栏数(PageColumnCount)’属性值大于 1 即可。通过 Grid++Report 的“页栏输出顺序”还可以指定多栏报表的输出顺序是“先从上到下”还是“先从左到右”。 问:如何实现票据套打? 答:Grid++Report 为实现票据套打做了很多专门的安排:报表设计器提供了页面设计模式,按照设定的纸张尺寸显示设计面板,如果将空白票据的扫描图设为设计背景图,在定位报表内容的输出位置会非常方便。报表部件可以设定打印类别,非套打输出的内容在套打打印模式下就不会输出。 问:Grid++Report 有没有横向分页功能? 答:回答是肯定的,在列的总宽度超过打印页面的输出宽度时,Grid++Report 可以另起新页输出剩余的列,如果左边存在锁定列,锁定列可以在后面的新页中重复输出,这样可以保证关键数据列在每一页都有输出。仔细体会 Grid++Report 提供的多种打印适应策略,选用最合适的方式。Grid++Report 的多种打印适应策略为开发动态报表提供了很好的支持。 问:怎样实现报表本页小计功能? 答:定义一个报表分组,将本分组定义为页分组,在本分组的分组头与分组尾上定义统计。页分组就是在每页产生一个分组项,在每页的上端与下端都会分别显示页分组的分组头与分组尾,页分组不用定义分组依据字段。 报表运行 问:怎样与数据库建立连接? 答:如果在设计报表时指定了数据集的数据源连接串与查询 SQL 语句,Grid++Report 采用拉模式直接从数据源取得报表数据,Grid++Report 利用 OLE DB 从数据源取数,OLE DB 提供了广泛的数据源操作能力。如果 Grid++Report 的数据来源采用推模式,即 Grid++Report 不直接与数据库建立连接,各种编程语言/平台都提供了很好的数据库连接方式,并且易于操作,应用程序在报表主对象(IGridppReport)的 FetchRecord 事件中将数据传入,例子程序提供了各种编程语言填入数据的通用方法,对C++Builder 和 Delphi 还进行了专门的包装,直接关联 TDataSet 对象也可以将 TDataSet 对象中的数据传给报表。 问:打印时能否对打印纸张进行自适应?支持表格的折行打印吗? 答:Grid++Report 在打印时采用多种适应策略,通过设置明细网格(IGRDetailGrid)的‘打印策略(PrintAdaptMethod)’属性指定打印策略。(1)丢弃:按设计时列的宽度输出,超出范围的内容不显示。(2)绕行:按设计时列的宽度输出,如果在当前行不能完整输出,则另起新行进行输出。(3)缩放适应:对所有列的输出宽度进行按比例地缩放,使总宽度等于页面的输出宽度。(4)缩小适应:如果列的总宽度小于页面的输出宽度,对所有列的输出宽度进行按比例地缩小,使总宽度等于页面的输出宽度。(5)横向分页:超范围的列在新页中输出。(6)横向分页并重复锁定列。 问:如何改变缺省打印预览窗口的窗口标题? 答:改变报表主对象的‘标题(Title)’属性即可。 问:利用集合对象的编程接口取子对象的接口引用,但不是自己期望的结果。 答:Grid++Report中所有集合对象的下标索引都是从 1 开始,另按对象的名称查找对象的接口引用时,名称字符是不区分大小写的。 问:怎样在运行时控制报表中各个对象的可见性?即怎样在运行时显示或隐藏对象? 答:在报表主对象(GridppReport)的 SectionFormat 事件中设定相应报表子对象的可见(Visible)属性即可。 问:报表主对象重新载入数据,设计器中为什么没有反映新载入的数据? 答:应调用 IGRDesigner 的 Reload 方法。 问:怎样实现不进入打印预览界面,直接将报表打印出来?