【问题标题】:How to translate Mapbox GL Native Android Activity example Java app into NativeScript?如何将 Mapbox GL Native Android Activity 示例 Java 应用程序翻译成 NativeScript?
【发布时间】:2019-08-22 02:07:30
【问题描述】:

我正在尝试在NativeScript Mapbox plugin 中查找一个令人讨厌的崩溃错误。在 android 上,使用该插件构建的任何应用程序都会在 onResume 上崩溃。

为了排除 Mapbox GL Native Android 库中的错误,我按照安装步骤创建了一个 very simple example app in Java that loads and displays a map

无论我暂停和恢复多少次,该示例应用都不会崩溃。

我注意到 NativeScript Mapbox 插件似乎没有调用推荐的 Mapbox 生命周期钩子,并且在 Mapbox 原生问题列表中报告了许多崩溃,答案是“遵循生命周期钩子指南”。

所以我的下一个想法是看看我是否可以按照推荐的生命周期挂钩将 Java 代码直接翻译成 NativeScript(就像在示例应用程序中所做的那样)。这样我就可以确定崩溃是因为生命周期钩子没有被正确调用还是因为一些更深奥的 NativeScript 问题。

有效的 Java 活动是:

package com.amapboxtest.mapboxtest;

import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;

import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    private MapView mapView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.d( "test","onCreate()");

        Mapbox.getInstance(this, "MAPBOX_ACCESS_TOKEN_HERE");

        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        mapView = findViewById(R.id.mapView);
        mapView.onCreate(savedInstanceState);
        mapView.getMapAsync(new OnMapReadyCallback() {
            @Override
            public void onMapReady(@NonNull MapboxMap mapboxMap) {

                Log.d( "test","onMapReady()");

                mapboxMap.setStyle(Style.MAPBOX_STREETS, new Style.OnStyleLoaded() {
                    @Override
                    public void onStyleLoaded(@NonNull Style style) {

                        Log.d( "test", "onStyleLoaded()");

                    // Map is set up and the style has loaded. Now you can add data or make other map adjustments


                    }
                });
            }
        });

    }

    @Override
    public void onStart() {

        Log.d( "test", "onStart()");

        super.onStart();
        mapView.onStart();
    }

    @Override
    public void onResume() {

        Log.d( "test", "onResume");

        super.onResume();
        mapView.onResume();
    }

    @Override
    public void onPause() {
        Log.d( "test", "onPause");

        super.onPause();
        mapView.onPause();
    }

    @Override
    public void onStop() {

        Log.d( "test", "onStop");

        super.onStop();
        mapView.onStop();
    }

    @Override
    public void onLowMemory() {

        Log.d( "test", "onLowMemory()");

        super.onLowMemory();
        mapView.onLowMemory();
    }

    @Override
    protected void onDestroy() {

        Log.d( "test","onDestroy");

        super.onDestroy();
        mapView.onDestroy();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {

        Log.d( "test", "onSaveInstanceState()");

        super.onSaveInstanceState(outState);
        mapView.onSaveInstanceState(outState);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
} 

我最初尝试进行 NativeScript 翻译:

/**
*
* @link https://github.com/NativeScript/android-runtime/issues/981
*/

import {setActivityCallbacks, AndroidActivityCallbacks} from "tns-core-modules/ui/frame";

import * as application from "tns-core-modules/application";

declare const com, java, org;

@JavaProxy("com.amapboxtest.MainActivity")
class Activity extends android.support.v7.app.AppCompatActivity {
    public isNativeScriptActivity;

    private _callbacks: AndroidActivityCallbacks;

    private mapView: any;

    public onCreate(savedInstanceState: android.os.Bundle): void {

      console.log( "Activity::onCreate()" );

      this.isNativeScriptActivity = true;

      if (!this._callbacks) {
        setActivityCallbacks(this);
      }

      this._callbacks.onCreate(this, savedInstanceState, super.onCreate );

      console.log( "Activity::onCreate(): after _callbacks.onCreate()" );

      let layout;
      let resourceId;

      console.log( "Activity::onCreate(): before getting layout" );

      // this fails.

      try {
        layout = this.getResources().getIdentifier( "activity_main", "layout", this.getPackageName() );
      } catch( e ) {
        console.error( "Unable to get layout:", e );
        throw e;
      }

      this.setContentView(layout);

      console.log( "Activity::onCreate(): before getting resourceId" );

      try {
        resourceId = this.getResources().getIdentifier( "mapView", "id", this.getPackageName() );
      } catch( e ) {
        console.error( "Unable to get resourceId:", e );
        throw e;
      }

      this.mapView = this.findViewById( resourceId );

      console.log( "Activity::onCreate(): after findViewById()" );

      this.mapView.onCreate( savedInstanceState ); 

      console.log( "Activity::onCreate(): after this.mapView.onCreate( savedInstanceState" );

      com.mapbox.mapboxsdk.Mapbox.getInstance( application.android.context, 'SET_ACCESS_TOKEN_HERE' );

      console.log( "Activity::onCreate(): after getInstance()" );

      // modelled after mapbox.android.ts in the Nativescript-Mapbox plugin.

      this.mapView.getMapAsync(
        new com.mapbox.mapboxsdk.maps.OnMapReadyCallback({
          onMapReady: mapboxMap => {

            console.log( "onMapReady()");

            this.mapView.addOnDidFinishLoadingStyleListener(
              new com.mapbox.mapboxsdk.maps.MapView.OnDidFinishLoadingStyleListener({
                onDidFinishLoadingStyle : style => {

                  console.log( "style loaded" );

                }
              })
            );

            let builder = new com.mapbox.mapboxsdk.maps.Style.Builder();

            const Style = com.mapbox.mapboxsdk.constants.Style;

            mapboxMap.setStyle( 
              builder.fromUrl( Style.LIGHT )
            );
          } 
        })
      );

    } // end of onCreate()

    // -------------------------------------------------------

    public onSaveInstanceState(outState: android.os.Bundle): void {

        console.log( "Activity::onSaveInstanceState()" );

        this._callbacks.onSaveInstanceState(this, outState, super.onSaveInstanceState);

        this.mapView.onSaveInstanceState( outState );

    }

    // -------------------------------------------------------

    public onStart(): void {

        console.log( "Activity::onStart()" );

        this._callbacks.onStart(this, super.onStart);

        this.mapView.onStart();
    }

    // -------------------------------------------------------

    public onStop(): void {

        console.log( "Activity::onStop()" );

        this._callbacks.onStop(this, super.onStop);

        this.mapView.onStop();
    }

    // -------------------------------------------------------

    public onDestroy(): void {

        console.log( "Activity::onDestroy()" );

        this._callbacks.onDestroy(this, super.onDestroy);

        this.mapView.onDestroy();
    }

    // -------------------------------------------------------

    public onBackPressed(): void {

        console.log( "Activity::onBackPressed()" );

        this._callbacks.onBackPressed(this, super.onBackPressed);
    }

    // -------------------------------------------------------

    public onRequestPermissionsResult(requestCode: number, permissions: Array<string>, grantResults: Array<number>): void {

        console.log( "Activity::onCRequestPermissionResult()" );

        this._callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined /*TODO: Enable if needed*/);
    }

    // -------------------------------------------------------

    public onActivityResult(requestCode: number, resultCode: number, data: android.content.Intent): void {

        console.log( "Activity::onActivityResult()" );

        this._callbacks.onActivityResult(this, requestCode, resultCode, data, super.onActivityResult);
    }
}

// END

我从 Java 应用复制了 gradle 依赖项并将它们添加到 App_Resources/Android/app.gradle。

我还复制了 app/src/main/res/layout/activity_main.xml 和 content_main.xml 文件。

最初,它会在 content_main.xml 上出错,因为 Nativescript 显然不支持 android.support.constraint.ConstraintLayout。因此,根据我在 NativeScript Mapbox 插件中看到的内容,我将其更改为 android.widget.FrameLayout。我在 java 应用程序中对此进行了测试,它似乎可以工作。

但是,现在我遇到了一个我不理解的运行时异常:

System.err: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.amapboxtest.nsmapboxtest/com.amapboxtest.MainActivity}: com.tns.NativeScriptException: 
System.err: Calling js method onCreate failed
System.err: 
System.err: Error: android.view.InflateException: Binary XML file line #9: Binary XML file line #12: Error inflating class com.mapbox.mapboxsdk.maps.MapView
System.err: Caused by: android.view.InflateException: Binary XML file line #12: Error inflating class com.mapbox.mapboxsdk.maps.MapView
System.err: Caused by: java.lang.reflect.InvocationTargetException
System.err:     java.lang.reflect.Constructor.newInstance0(Native Method)
System.err:     java.lang.reflect.Constructor.newInstance(Constructor.java:334)
System.err:     android.view.LayoutInflater.createView(LayoutInflater.java:647)

它在 Activity 的这一行死了:

layout = this.getResources().getIdentifier( "activity_main", "layout", this.getPackageName() );

NativeScript 显然不会公开 R.id,因此会调用 getResources()。

我对 NativeScript 和原生 Android 开发非常陌生,我不得不比我想要的更深入地研究杂草,但我需要解决这个崩溃问题,让这个简单的翻译工作是下一个关键步骤。

任何关于我在将这个示例翻译成 NativeScript 时做错了什么的指导将不胜感激。我不得不想象我缺少一些简单的东西。

我已将有效的 Java 示例和损坏的 NativeScript 示例都放在 Github 上。

Java MapboxTest Repository

NativeScript NsMapboxTest Repository

【问题讨论】:

标签: java android nativescript mapbox


【解决方案1】:

我们在 NativeScript 中没有 activity_main.xml。您的 UI 是使用使用 XML / JavaScript 的 NativeScript 组件构建的。要初始化 UI,您必须像在原始实现中一样执行this,否则您将失去框架内提供的所有核心导航功能。

 appModule.android.init(this.getApplication());

然后,您可以在放置 mapbox 视图的根组件中监听 onCreate 活动事件,使用 getViewById 获取引用,然后访问 nativeView 属性以获取实际的原生 mapbox 视图。

【讨论】:

  • 谢谢。我误解了一些基本原理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-10
  • 2011-12-24
相关资源
最近更新 更多