【问题标题】:Include a cn1lib in a local codenameone android build?在本地代号 android 构建中包含 cn1lib?
【发布时间】:2018-08-19 20:33:26
【问题描述】:

我目前正在考虑将我开始使用 react-native 开发的应用程序移植到代号one。为此,我仍在检查可行性和它需要的工作量(因为我必须移植或开发一些从 react-native 到 codenameone 的本机库绑定,因为 codenameone 错过了我的一些需求,比如 socket.io 支持例子)。免费的代号构建云服务仅限于 1Mb 的应用程序,我必须在本地进行测试构建(只有少数测试类和使用谷歌地图 cn1lib,我的测试应用程序已经超过 1Mb 限制) 可悲的是,没有关于如何执行本地构建的代号免费文档,实际上我在互联网上找不到任何关于如何执行此操作的说明(我只在博客文章中找到了一些关于如何执行的基本和已弃用的说明一个本地 iOS 版本,但对 Android 没有)。所以我必须自己弄清楚... 在花了一些时间研究 gradle 配置参数之后,我终于成功地构建了一个基本的代号本地应用程序,该应用程序可以在我的 android 测试设备上运行。但问题是,当我添加一个外部 cn1lib (谷歌映射本机 cn1lib https://github.com/codenameone/codenameone-google-maps )时,我的应用程序在打开依赖于该库的屏幕时出现错误。 在 android 错误日志中,我可以找到以下消息:

D/MyApplication(  551): [EDT] 0:0:0,99 - Exception: java.lang.ClassCastException - com.codename1.googlemaps.InternalNativeMapsImpl cannot be cast to com.codename1.system.NativeInterface
W/System.err(  551): java.lang.ClassCastException: com.codename1.googlemaps.InternalNativeMapsImpl cannot be cast to com.codename1.system.NativeInterface
W/System.err(  551):    at com.codename1.system.NativeLookup.create(Unknown Source)
W/System.err(  551):    at com.codename1.googlemaps.MapContainer.<init>(MapContainer.java:171)
W/System.err(  551):    at com.codename1.googlemaps.MapContainer.<init>(MapContainer.java:151)
W/System.err(  551):    at com.tbdlab.testapp.MyApplication.start(MyApplication.java:207)
W/System.err(  551):    at com.tbdlab.testapp.MyApplicationStub.run(MyApplicationStub.java:183)
W/System.err(  551):    at com.codename1.ui.Display.processSerialCalls(Unknown Source)
W/System.err(  551):    at com.codename1.ui.Display.mainEDTLoop(Unknown Source)
W/System.err(  551):    at com.codename1.ui.RunnableWrapper.run(Unknown Source)
W/System.err(  551):    at com.codename1.impl.CodenameOneThread$1.run(Unknown Source)
W/System.err(  551):    at java.lang.Thread.run(Thread.java:818)

我真的不明白为什么 InternalNativeMapsImpl 无法转换为 NativeInterface,因为我查看了已编译的 apk 的 dex 文件,并且正确包含了来自 google maps cn1lib 的所有必要类(适用于 android)(所以我有 @ 987654323@、com.codenameone.googlemaps.InternalNativeMapsImplcom.codenameone.googlemaps.MapContainer) 以及它们所依赖的代号本机接口类 (com.codename1.system.NativeInterfacecom.codename1.impl.android.LifecycleListener...)。而且我对它们进行了反编译并且代码是正确的(无论如何我都不使用任何混淆方法,因此编译的代码与源代码不同并没有真正的原因)。我在这里可能缺少一些东西来使用 cn1lib 进行本地代号构建。

那么,是否有人已经成功地使用执行本地绑定的 cn1lib 进行本地构建?如果是,具体程序是什么? 我真的希望有人能够提供帮助,因为在这一点上,我正在认真考虑坚持使用 react-native(我对此非常满意,除了它不是完全原生的事实)或跳入颤动(或 kotlin 本机),即使我仍然认为 codenameone 提供了许多优于其他解决方案的优势(但在开发阶段无法执行本地构建对我来说是完全不行的)

【问题讨论】:

    标签: android build native codenameone lib


    【解决方案1】:

    如前所述,在我的一些测试中(我使用了我需要的全套 cn1libs + 一些自定义库),我已经超过了 1Mb 的限制(服务器因此拒绝了我的测试构建)。因此,在开发阶段使用免费构建的云服务器对我来说不是一个选择(无论如何,如果我不确定我是否可以在必要时完全独立,我不会使用解决方案。为了制作我的发布版本,我当然会订阅并使用cloud buid服务器,因为它比tweeking本地服务器方便得多,而且我没有Mac电脑(我只有一个测试iphone),当我想做一些iOS构建时需要借一个; ) .但我想确定的是,如果出于任何原因,您的服务消失了,我仍然可以进行构建。此外,我看不出在我的应用程序的开发阶段(这可能需要我几个月)支付订阅费的意义,尤其是因为我不确定我是否会使用 codenameone 作为最终解决方案(我仍然需要检查工作它需要将我已经拥有的一些用于 react-native 的库改编为代号one))。这就是我尝试进行本地构建的原因。

    关于 socket.io 库,我已经开始创建一个将使用本机解决方案的 cn1lib(https://github.com/socketio/socket.io-client-java 用于 android,https://github.com/socketio/socket.io-client-swift 用于 iOS,以及用于 javascript 的原始 socket.io 库)。这不是一个真正的问题,它只是举例说明如果我想从 react-native 切换,我必须在 codenameone 中创建库。

    关于 cn1lib 的工作原理,我已经弄清楚了,我在我的 android 项目中包含了 cn1 google-maps lib 的所有必要类(因此我包含了 main.zip、nativeand.zip 和存根中的内容。 zip 在我的项目中)并检查了我生成的 apk 的 .dex 文件,它们实际上已正确打包,如前所述。所以我的问题似乎不是我忘记在我的项目中包含一些 cn1lib 类,而是别的东西。错误消息是:Exception: java.lang.ClassCastException - com.codename1.googlemaps.InternalNativeMapsImpl cannot be cast to com.codename1.system.NativeInterface 所以它不是指未找到的类,而是指强制转换异常......我真的不知道是什么导致了这个问题。我从这里https://github.com/codenameone/CodenameOne/tree/master/CodenameOne/src/ 获取了代号核心类 https://github.com/codenameone/CodenameOne/tree/master/Ports/Androidhttp://github.com/codenameone/codenameone-skins

    将它们包含在我的项目中,所以我想我没有错过任何一个。当构建一个不使用 cn1lib 的项目时(比如一个简单的“hello word”应用程序),它在我的 android 测试设备上编译并运行得很好。 问题实际上只是当我的应用程序尝试创建一个 googlemap 视图时,它会返回强制转换异常(然后默认尝试创建一个 html 浏览器 mapview 并在此处失败,因为它缺少一些 html 文件)。 所以这可能是一个配置问题(可能是编译器用作本机类文件的java版本已经在cn1lib main.zip文件中编译的问题?) 这是我使用的 gradle 构建文件:

    buildscript {
            repositories {
                jcenter()
                maven { url "https://plugins.gradle.org/m2/" }
                google()
            }
            dependencies {
                classpath 'com.android.tools.build:gradle:3.0.1'
                classpath 'me.tatarka:gradle-retrolambda:3.2.0'
            }
        }
    
    
        allprojects {
            repositories {
                jcenter()
                google()
            }
        }
    
        apply plugin: 'com.android.application'
        apply plugin: 'me.tatarka.retrolambda'
    
    
        android {
            compileSdkVersion 26
            buildToolsVersion '26.0.2'
            dexOptions {
                // Prevent OutOfMemory with MultiDex during the build phase
                javaMaxHeapSize "4g"
            }
            lintOptions {
                checkReleaseBuilds false
            }
    
            defaultConfig {
                applicationId "com.tbdlab.testapp"
                minSdkVersion 15
                targetSdkVersion 23
                versionCode 1
                versionName "1.0"
                multiDexEnabled true
                testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
            }
            buildTypes {
                release {
                    minifyEnabled false
                    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' //my proguard files are actually empty so no obfuscation is performed. I checked it in the generated apk
                }
            }
            compileOptions {
                sourceCompatibility JavaVersion.VERSION_1_7//.VERSION_1_8
                targetCompatibility JavaVersion.VERSION_1_7//.VERSION_1_8
            }
        }
        dependencies {
            compile fileTree(include: ['*.jar'], dir: 'libs')
            androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
                    exclude group: 'com.android.support', module: 'support-annotations'
                })
            compile 'com.google.android.gms:play-services:9.4.0' //compile 'com.google.android.gms:play-services-maps:11.8.0'
            compile 'com.android.support:multidex:1.0.1'
        }
    

    这是我的 AndroidManifest.xml 文件,其中包含了 cn1lib 中定义的所有权限:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> 
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> 
    <!--- Permissions requiered by the google maps cn1lib -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> 
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
    <uses-feature android:glEsVersion="0x00020000" android:required="true"/>
    
    <application android:allowBackup="true"  android:icon="@drawable/icon" android:label="MyApplication" android:name="android.support.multidex.MultiDexApplication">
    <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version"/>
    <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="...masked_it_but_put_my_correct_key_here..."/>
    <activity  android:label="MyApplication" android:launchMode="singleTop" android:name="com.tbdlab.testapp.MyApplicationStub" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>
    <receiver android:name="com.codename1.impl.android.LocalNotificationPublisher"/>
    <service android:exported="false" android:name="com.codename1.impl.android.BackgroundFetchHandler"/>
    <activity android:name="com.codename1.impl.android.CodenameOneBackgroundFetchActivity" android:theme="@android:style/Theme.NoDisplay"/>
    <activity android:name="com.codename1.location.CodenameOneBackgroundLocationActivity" android:theme="@android:style/Theme.NoDisplay"/>
    <service android:exported="false" android:name="com.codename1.location.BackgroundLocationHandler"/>
    <service android:exported="false" android:name="com.codename1.location.GeofenceHandler"/>
    <service android:exported="false" android:name="com.codename1.media.AudioService"/>
    <activity android:excludeFromRecents="true" android:exported="false" android:name="com.google.android.gms.auth.api.signin.internal.SignInHubActivity" android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
    <provider android:authorities="com.tbdlab.testapp.google_measurement_service" android:exported="false" android:name="com.google.android.gms.measurement.AppMeasurementContentProvider"/>
    <receiver android:enabled="true" android:name="com.google.android.gms.measurement.AppMeasurementReceiver">
        <intent-filter>
            <action android:name="com.google.android.gms.measurement.UPLOAD"/>
        </intent-filter>
    </receiver>
    <service android:enabled="true" android:exported="false" android:name="com.google.android.gms.measurement.AppMeasurementService"/>
    <activity android:name="com.google.android.gms.ads.AdActivity" android:theme="@android:style/Theme.Translucent"/>
    

    我真的不知道是什么导致了强制转换异常,但是由于缺乏关于如何创建本地构建的基本教程,我可能在不知不觉中错过了一些东西......

    对于这个测试,我制作了一个非常简单的应用程序,而不仅仅是显示原生谷歌地图,它在模拟器中正确运行,在构建云服务器上编译,在我的 android 测试设备中运行良好。因此,问题出在我的 gradle 构建配置中(或者可能是 AndroidManifest.xml 文件,即使我认为它对 JVM 没有任何影响),也可能出在我的 android 项目中用于本地构建的 codenameone 核心和 cn1lib 中。

    【讨论】:

    • 我建议您将其移至讨论论坛,因为这更多的是讨论并且可能会被缓和。您不需要包含 stubs.zip。它只是为了在 IDE 中提供 javadoc 提示/自动完成。由于 cn1libs 大部分是开源的,您只需将它们的代码复制到您的项目中,然后将它们的本机接口实现放在您的本机目录下,这可能更容易。类转换异常是因为构建服务器生成一个“隐藏的”本机 impl 类,该类充当本机调用的桥梁。我将编辑我的答案以包含该内容
    【解决方案2】:

    1mb 很大,因为它可以容纳完整的谷歌地图应用程序等等。它映射到从 6kb 开始的 jar 的编译大小。整个cn1lib(只打包了一部分)是40kb。所以我建议使用构建服务器进行测试。

    几年前,Steve 为使用本机接口构建了一些支持here。在我们雇用他后,他停止了维护,主要是由于缺乏时间和需求(不是因为我们告诉他或类似的事情)。我不确定它的状态,但您可以将其用作原生接口如何工作的参考。

    还有this plugin (direct link here) 我个人没有尝试过。

    通常,原生接口会生成一个中间类,它会直接调用原生实现。除 Java SE 之外的所有平台的本机实现都没有实现本机接口,也不应该实现。我想我在文档的某个地方解释了它,但在谷歌地图的情况下再次解释它非常容易。

    这是来自本机接口的方法:

    public PeerComponent createNativeMap(int mapId);
    

    这与 Android 实现类中的方法相同:

    public android.view.View createNativeMap(int mapId);
    

    如您所见,返回值不同,我们需要将其包装在对等组件中以抽象该行为。通过避免继承和强制转换,我们可以灵活地制作更明智的原生 API。

    这是我们的构建服务器为地图生成的类,您可以看到它只是“粘合代码”:

    package com.codename1.googlemaps;
    
    import com.codename1.ui.PeerComponent;
    
    public class InternalNativeMapsStub implements InternalNativeMaps{
        private InternalNativeMapsImpl impl = new InternalNativeMapsImpl();
    
        public void setShowMyLocation(boolean param0) {
            impl.setShowMyLocation(param0);
        }
    
        public void setRotateGestureEnabled(boolean param0) {
            impl.setRotateGestureEnabled(param0);
        }
    
        public void setMapType(int param0) {
            impl.setMapType(param0);
        }
    
        public int getMapType() {
            return impl.getMapType();
        }
    
        public int getMaxZoom() {
            return impl.getMaxZoom();
        }
    
        public int getMinZoom() {
            return impl.getMinZoom();
        }
    
        public long addMarker(byte[] param0, double param1, double param2, String param3, String param4, boolean param5) {
            return impl.addMarker(param0, param1, param2, param3, param4, param5);
        }
    
        public void addToPath(long param0, double param1, double param2) {
            impl.addToPath(param0, param1, param2);
        }
    
        public long finishPath(long param0) {
            return impl.finishPath(param0);
        }
    
        public void removeMapElement(long param0) {
            impl.removeMapElement(param0);
        }
    
        public void removeAllMarkers() {
            impl.removeAllMarkers();
        }
    
        public PeerComponent createNativeMap(int param0) {
            return PeerComponent.create(impl.createNativeMap(param0));
        }
    
        public void setPosition(double param0, double param1) {
            impl.setPosition(param0, param1);
        }
    
        public void calcScreenPosition(double param0, double param1) {
            impl.calcScreenPosition(param0, param1);
        }
    
        public int getScreenX() {
            return impl.getScreenX();
        }
    
        public int getScreenY() {
            return impl.getScreenY();
        }
    
        public void calcLatLongPosition(int param0, int param1) {
            impl.calcLatLongPosition(param0, param1);
        }
    
        public double getScreenLat() {
            return impl.getScreenLat();
        }
    
        public double getScreenLon() {
            return impl.getScreenLon();
        }
    
        public void deinitialize() {
            impl.deinitialize();
        }
    
        public float getZoom() {
            return impl.getZoom();
        }
    
        public void setZoom(double param0, double param1, float param2) {
            impl.setZoom(param0, param1, param2);
        }
    
        public double getLatitude() {
            return impl.getLatitude();
        }
    
        public double getLongitude() {
            return impl.getLongitude();
        }
    
        public long beginPath() {
            return impl.beginPath();
        }
    
        public void initialize() {
            impl.initialize();
        }
    
        public boolean isSupported() {
            return impl.isSupported();
        }
    
    }
    

    关于 socket.io,您可能只需调用 BrowserComponent 即可封装 JavaScript 版本,以使原生 JS 代码开始工作。一个完整的本地端口可能会在以后出现。

    看来您已经找到了 cn1libs,但为了完整起见,它们应该是这样工作的:

    cn1lib 只是一个 zip 文件,其中包含每个平台的其他 zip 文件。 Refresh libs 解压缩 this 并将文件排列在 lib/impl 下的适当目录中。因此,您需要将与您尝试编译的平台匹配的 lib/impl 目录与您的发行版一起打包。

    cn1libs 还包含两个额外的属性文件codenameone_library_appended.propertiescodenameone_library_required.properties。刷新库将通过将这些值设置到构建提示中来自动为您处理。前一个值附加到现有的构建提示,而后者覆盖现有的构建提示。

    构建提示有效地告诉构建服务器如何编译一些东西,例如如果我们想向 plist、manifest 等中注入东西。如何映射到本地构建会有很大的不同。在某些情况下,例如 plistInject,理解起来很简单,但其他情况可能很奇怪。如果您对特定构建提示如何映射到本地构建有疑问,那么您可以提出这个问题。

    【讨论】:

    • 你好。请在下面查看我的回答(对于简单的评论来说太长了)。
    • 使用 impl 存根类编辑了我的答案。我建议将此讨论移至更适合此类长篇讨论的论坛
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-14
    • 1970-01-01
    • 2014-04-17
    • 1970-01-01
    • 2015-05-26
    • 2019-02-27
    • 1970-01-01
    相关资源
    最近更新 更多