尝试给NativeScript添加按键监听功能失败记
本文由 小茗同学 发表于 2016-11-10 浏览(1271)
最后修改 2016-11-11 标签:nativescript 按键 监听

前言

痛苦的过程,蛋疼的框架,领导安排的任务,只能硬着头皮上。

概述

经过无数次的查找,确定NativeScript默认不支持按键监听。有人说,不支持就不支持呗,我自己写一个呗,好吧,我也是这么想的。

首先需要做的就是如何修改Android工程的源码。

修改原生代码

platform/android下确实是一个AndroidStudio的工程,但是导入AndroidStudio中却是各种报错,最坑爹的连入口Activity(也就是com.tns.NativeScriptActivity)都找不到,还有一些gradle的错误。初步估计是NativeScript在编译的过程中会自己先修改Android工程再去编译,所以我们直接导入IDE是不行的,这只是我的猜测。

其实我只要是想在入口Activity中添加onKeyDown即可,没啥其它更多需求,所以,先要找到这个文件。但是,我确定我已经把工程下的每一个文件夹一层层点开去找了,依然没有找到那个com.tns.NativeScriptActivity,包括各种aar包、jar包我都打开看过,真是奇了怪了。但是生成的apk反编译时是有这个类的。

好吧,既然直接修改你没有办法,那我自己写一个呗,把反编译出来的那个Activity代码全部复制(好在这个反编译出来的代码没有任何语法错误),然后自己写一个同包同名的Activity,运行,结果你应该猜到了,肯定是报dex重复的错误。

再然后,将这个activity改为com.test.WelcomeActivity,修改app\App_Resources\Android\AndroidManifest.xml

<activity
	android:name="com.test.WelcomeActivity"
	android:label="@string/title_activity_kimera"
	android:configChanges="keyboardHidden|orientation|screenSize">
	<!--省略其它-->
</activity>

然后编译运行,编译是没问题,但是运行报错,可能是js在哪个地方写死了入口Activity导致找不到入口。

再后来在platforms\android\build\nativescript-bindings下无意中发现了com.tns.NativeScriptActivity.dex文件,好家伙,总算让我找到了,删掉它,然后自己写一个同名的Activity,并且试着修改了一下里面的代码,好吧,这次终于没问题了,至少有办法修改它的代码了。

2016-11-11更新:貌似这里说错了,上一次不记得怎么就成功了,但是后来试了无数次都不行,因为即使我们把它临时生成的dex文件删除了,重新编译运行时仍然会重新生成,所以依旧会报dex重复错误。

增加按键监听

模仿其它代码,写了个这样的方法:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
	if(event.getAction() != KeyEvent.ACTION_DOWN) return super.onKeyDown(keyCode, event);
	Object[] arrayOfObject = { keyCode, event };
	Runtime.callJSMethod(this, "onKeydown", Void.TYPE, arrayOfObject);
	return super.onKeyDown(keyCode, event);
}

关键就是这个Runtime.callJSMethod,每个onXXX方法内部都调用了它,比如onStart、onStop、onSaveInstanceState等,表面上看是调用JS方法的意思,但是具体是调用哪里的js、怎么调用的完全不知,跟踪进去看源码也没看懂,折腾了很久最后只能放弃。

在官网API文档找了很久有关事件的东西,依然没有找到什么有价值的信息。

再后来无意中在官网看到一个可以用js重写Activity的方法,然后试了下。在app下面新建一个activity.android.js,然后像下面这样写:

var frame = require("ui/frame");

var superProto = android.app.Activity.prototype;
var Activity = android.app.Activity.extend("com.test.MainActivity", {
	onCreate: function(savedInstanceState) {
		if(!this._callbacks) {
			frame.setActivityCallbacks(this);
		}
		// Modules will take care of calling super.onCreate, do not call it here
		this._callbacks.onCreate(this, savedInstanceState, superProto.onCreate);

		// Add custom initialization logic here
	},
	onSaveInstanceState: function(outState) {
		this._callbacks.onSaveInstanceState(this, outState, superProto.onSaveInstanceState);
	},
	onStart: function() {
		this._callbacks.onStart(this, superProto.onStart);
	},
	onStop: function() {
		this._callbacks.onStop(this, superProto.onStop);
	},
	onDestroy: function() {
		this._callbacks.onDestroy(this, superProto.onDestroy);
	},
	onBackPressed: function() {
		this._callbacks.onBackPressed(this, superProto.onBackPressed);
	},
	onRequestPermissionsResult: function (requestCode, permissions, grantResults) {
		this._callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined);
	},
	onActivityResult: function (requestCode, resultCode, data) {
		this._callbacks.onActivityResult(this, requestCode, resultCode, data, _super.prototype.onActivityResult);
	}
});

再把AndroidManifest.xml中的Activity的name改成com.test.MainActivity,运行,嘿嘿,还真可以,到这里才终于恍然大悟:NativeScript的入口Activity是在编译的时候根据JS动态生成的!尼玛!怪不得我找了那么久都找不到。

直接模仿其它方法,写了一个onKeyDown:

	onKeyDown: function(keyCode, event)
	{
		console.log(keyCode);
		this._callbacks.onKeyDown(this, keyCode, event, superProto.onKeyDown);
	}

运行肯定是不行的,报什么错忘了,毕竟肯定没这个简单。

然后全局搜索onRequestPermissionsResult(因为这个方法和onKeyDown类似有参数),发现只有3个js有相关代码,然后全部模仿着增加了onKeyDown相关方法:

node_modules\tns-core-modules\ui\frame\frame.android.js

ActivityCallbacksImplementation.prototype.onSaveInstanceState = function (activity, outState, superFunc) {
	superFunc.call(activity, outState);
	var view = this._rootView;
	if (view instanceof Frame) {
		outState.putInt(INTENT_EXTRA, view.android.frameId);
	}
};
ActivityCallbacksImplementation.prototype.onKeyDown = function (activity, keyCode, event, superFunc) {
	superFunc.call(activity, keyCode, event);
};

node_modules\tns-core-modules\ui\frame\frame.d.ts

export interface AndroidActivityCallbacks {
	onCreate(activity: any, savedInstanceState: any, superFunc: Function): void;
	onSaveInstanceState(activity: any, outState: any, superFunc: Function): void;
	onStart(activity: any, superFunc: Function): void;
	onStop(activity: any, superFunc: Function): void;
	onDestroy(activity: any, superFunc: Function): void;
	onBackPressed(activity: any, superFunc: Function): void;
	onRequestPermissionsResult(activity: any, requestCode: number, permissions: Array<String>, grantResults: Array<number>, superFunc: Function): void;
	onActivityResult(activity: any, requestCode: number, resultCode: number, data: any, superFunc: Function);
	onKeyDown(activity: any, keyCode: number, event: any, superFunc: Function): void;
}

然后再试,还是报错,一个Java的空指针异常:

虽然原生报错了,但是前面我们自己写的那个console.log(keyCode)却生效了,控制台可以看到输出的键值。

最后还尝试了一些措施,反正都没有成功,尼玛真是彻底崩溃了。我只是想Java调用JS这么一个简单的需求而已,却不知如何做到。

另外发现了默认的Activity生成的jsnode_modules\tns-core-modules\ui\frame\activity.android.js,默认的入口Activity应该就是根据这个JS生成的。