0%

准备工作

开发环境

  • Python 3.8.1
  • Windows 10

安装依赖

1
2
pip install PyQt5
pip install PyQtWebEngine

Python端

  1. 使用QWebChannelregisterObject("JsBridge名","JsBridge")方法注册回调

    • JsBridge名:在JavaScript中调用时使用的对象名称
    • JsBridge:被JavaScript调用的Python对象
  2. JsBridge 对象

    • 入参

      1
      2
      3
      @QtCore.pyqtSlot(str)
      def log(self, message):
      print(message)
    • 出参

      1
      2
      3
      @QtCore.pyqtSlot(result=str)
      def getName(self):
      return "hello"
    • 出入参

      1
      2
      3
      4
      @QtCore.pyqtSlot(str, result=str)
      def test(self, message):
      print(message)
      return "ok"

JavaScript端

  1. 在Html的<head>中添加

    1
    <script src='qrc:///qtwebchannel/qwebchannel.js'></script>
  2. 调用

    1
    2
    3
    4
    5
    6
    new QWebChannel(qt.webChannelTransport, function(channel) {
    channel.objects.pythonBridge.test("hello",function(arg) {
    console.log("Python message: " + arg);
    alert(arg);
    });
    });
  3. 调试(Chrome DevTools)

    1. 配置环境变量:QTWEBENGINE_REMOTE_DEBUGGING = port
    2. 使用Chromium内核的浏览器打开地址:http://127.0.0.1:port
    3. 使用PyCharm中可以在运行配置(Run/Debug Configurations)中的Environment variables中添加环境变量,用;号分隔,然后可以直接运行。

Demo

Python

  1. JsBridge

    1
    2
    3
    4
    5
    6
    7
    from PyQt5 import QtCore

    class JsBridge(QtCore.QObject):
    @QtCore.pyqtSlot(str, result=str)
    def test(self, message):
    print(message)
    return "ok"
  2. Application
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    from PyQt5 import QtCore
    from PyQt5 import QtWebEngineWidgets
    from PyQt5.QtCore import QDir
    from PyQt5.QtWebChannel import QWebChannel
    from PyQt5.QtWebEngineWidgets import QWebEngineView
    from PyQt5.QtWidgets import *

    class TestWindow(QMainWindow):
    def __init__(self):
    super().__init__()
    self.webView = QWebEngineView()
    self.webView.settings().setAttribute(
    QtWebEngineWidgets.QWebEngineSettings.JavascriptEnabled, True)

    channel = QWebChannel(self.webView.page())
    self.webView.page().setWebChannel(channel)
    self.python_bridge = JsBridge(None)
    channel.registerObject("pythonBridge", self.python_bridge)
    layout = QVBoxLayout()
    layout.addWidget(self.webView)
    widget = QWidget()
    widget.setLayout(layout)
    self.setCentralWidget(widget)

    self.resize(900, 600)
    self.setWindowTitle('Test')
    qr = self.frameGeometry()
    cp = QDesktopWidget().availableGeometry().center()
    qr.moveCenter(cp)
    self.move(qr.topLeft())
    self.show()
    html_path = QtCore.QUrl.fromLocalFile(QDir.currentPath() + "/assets/index.html")
    self.webView.load(html_path)

    if __name__ == '__main__':
    app = QApplication(sys.argv)
    m = TestWindow()
    sys.exit(app.exec_())

JavaScript

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Test</title>
<script src='qrc:///qtwebchannel/qwebchannel.js'></script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
</head>
<body>
<button id="test">test</button>
</body>
<script>
$(document).ready(function() {
new QWebChannel(qt.webChannelTransport, function(channel) {
$('#test').on('click', function() {
channel.objects.pythonBridge.test("hello",function(arg) {
console.log("Python message: " + arg);
alert(arg);
});
});
});
});
</script>
</html>

本文需要了解基本Android开发知识

环境

  1. JDK
  2. Node
  3. Watchman 可选 Windows问题比较多,不推荐安装
  4. Android SDK配置环境

配置

  1. 项目结构,创建相应文件

    ├── android
    ├── index.js
    ├── node_modules
    ├── package.json

  2. 编辑package.json文件添加:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    {
    "name": "[项目名]",
    "version": "0.0.1",
    "private": true,
    "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest"
    },
    "dependencies": {
    "react": "16.3.1",
    "react-native": "0.55.1",
    "react-navigation": "1.5.2"
    },
    "devDependencies": {
    "babel-jest": "22.4.3",
    "babel-preset-react-native": "4.0.0",
    "jest": "22.4.3",
    "react-test-renderer": "16.3.1"
    },
    "jest": {
    "preset": "react-native"
    }
    }

    也可以使用命令创建官方的HelloWorld项目,然后拷贝相关配置文件,如下:

    1
    2
    npm install -g create-react-native-app
    create-react-native-app AwesomeProject
  3. 安装nodejs库,在根目录中执行命令:

    1
    2
    npm install
    npm install -g react-native-cli
  4. 编辑index.js文件添加:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    import React, { Component } from 'react';
    import {
    AppRegistry,
    StyleSheet,
    Text,
    View
    } from 'react-native';

    export default class Launch extends Component {
    render() {
    return (
    <View style={styles.container}>
    <Text style={styles.welcome}>
    Welcome to React Native!
    </Text>
    <Text style={styles.instructions}>
    To get started, edit index.js
    </Text>
    <Text style={styles.instructions}>
    Double tap R on your keyboard to reload,{'\n'}
    Shake or press menu button for dev menu
    </Text>
    </View>
    );
    }
    }

    const styles = StyleSheet.create({
    container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
    },
    welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
    },
    instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
    },
    });

    AppRegistry.registerComponent('ReactTest', () => Launch);
  5. android目录中创建app工程项目,然后在项目中集成React Native

    • [可选] 在settings.gradle文件中添加:

      1
      rootProject.name = '[项目名]'
    • gradle.properties文件中添加:

      1
      android.useDeprecatedNdk=true
    • 在根目录build.gradle文件中添加:

      1
      2
      3
      4
      5
      6
      7
      allprojects {
      repositories {
      maven {
      url "$rootDir/../node_modules/react-native/android"
      }
      }
      }
    • 在app目录build.gradle文件中添加:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      apply from: "../../node_modules/react-native/react.gradle"
      android {
      defaultConfig {
      ndk {
      abiFilters "armeabi-v7a", "x86"
      }
      }
      splits {
      abi {
      reset()
      enable false
      universalApk false // If true, also generate a universal APK
      include "armeabi-v7a", "x86"
      }
      }
      }
      dependencies {
      compile "com.facebook.react:react-native:+"
      }
    • 在app项目中创建MApplication.java

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
        public class MApplication extends Application implements ReactApplication{

      private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this){

      @Override
      public boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
      }

      @Override
      protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(new MainReactPackage());
      }

      @Override
      protected String getJSMainModuleName() {
      return "index";
      }
      };

      @Override
      public ReactNativeHost getReactNativeHost() {
      return mReactNativeHost;
      }
      }
    • 在app项目中创建MainActivity.java

      1
      2
      3
      4
      5
      6
      7
      8
      public class MainActivity extends ReactActivity {

      @Nullable
      @Override
      protected String getMainComponentName() {
      return "ReactTest";//与index.js中registerComponent对应
      }
      }
    • 修改app项目中AndroidManifest.xml文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <application
      android:name=".MApplication">
      <activity android:name=".MainActivity">
      <intent-filter>
      <action android:name="android.intent.action.MAIN" />

      <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      </activity>
      <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
      </application>

运行

  1. 通过命令行直接运行开发服务和app:

    1
    react-native run-android

    如果使用Windows系统请使用方法1,方法2会在运行app时出现异常:java.io.IOException: Could not delete path

  2. 通过命令运行开发服务,然后手动运行app

    1
    2
    npm start
    #也可使用: react-native start

    开发服务正常启动后如下,使用中不能关闭(如果安装Watchman,开发服务会在后台运行可以关闭当前窗口):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    \> [email protected] start x:\xxxx\React
    \> node node_modules/react-native/local-cli/cli.js start

    Scanning 564 folders for symlinks in x:\xxxx\React\node_modules (43ms)
    ┌──────────────────────────────────────────────────────────────────────────── ┐
    │ Running packager on port 8081. │
    │ │
    │ Keep this packager running while developing on any JS projects. Feel │
    │ free to close this tab and run your own packager instance if you │
    │ prefer. │
    │ │
    │ https://github.com/facebook/react-native │
    │ │
    └────────────────────────────────────────────────────────────────────────────┘
    Looking for JS files in
    X:\xxxx\React

    React packager ready.

    Loading dependency graph, done.

提示

Demo:ReactTest

新进入公司做智能家居设备,于是查寻了一下Android BLE的问题,这是比较全面的一片文章,简单翻译一下。

原文链接:http://www.slideshare.net/yeokm1/introduction-to-bluetooth-low-energy

Android issues (the past) BLE支持之前的问题

http://stackoverflow.com/questions/14118658/devices-with-android-4-2-jelly-bean-supported-bluetooth-low-energy-ble

  • Before Android 4.3 (July 2013) Android 4.3在2013年七月之前
    • Fragmentation hell 痛苦的碎片化
    • Proprietary Libraries by OEMs, Android <= 4.2 各大OEM厂商提供的私有库
      • Samsung (quite reliable)
      • HTC – buggy, unreliable
      • Motorola (reliable but conflicts with Android 4.3)
    • Architecture issues 架构上的问题
  • Testing issues 其它问题

Android issues (today) BLE支持之后的问题

http://stackoverflow.com/questions/17870189/android-4-3-bluetooth-low-energy-unstable

  1. OS fragmentation

    • 74.2% of Android devices support BLE 只有74.2%的设备支持
    • Few support peripheral mode: 35.3% minus Nexus 4, 5, 7 (2012/2013) 只有35.3%支持手机作为外围设备,只有5.0以上才支持。
  2. All callbacks from BLE APIs are not on UI thread. 所有的回调不是在UI线程

  3. APIs considered new, some functions are buggy 考虑使用新的API的话会有一些bug

  4. Frequent connection drops (< 5.0) 5.0之前频繁连接会导致连接无法使用

    参考:https://code.google.com/p/android/issues/detail?id=180440

  5. Max BLE connections: 最大连接数

    • Software cap in Bluedroid code: BTA_GATTC_CONN_MAX, GATT_MAX_PHY_CHANNEL
    • Android 4.3: 4
    • 4.4 - 5.0+: 7
  6. No API callback to indicate scanning has stopped 没有提供startLeScan()扫描结束的回调

    • Scan supposed to be indefinite by API specification, but some phones stop scan after some time API规范与实际扫描结果并不相符,并且一些手机停止扫描需要的时间比较长。

      比如有一些手机会一直回调设备,直到设备连接成功,而有一些设备(Samsung…)只回调一次

    • Known offender: Samsung 比如著名的三星

    • Solution: Restart scan at regular intervals 解决方法:扫描定时

  7. Different scan return result behaviours (See further reading) 扫描的结果并不相同

    • Some phones filter advertisement results, some phones do not. (usually on 4.3 and 4.4)有一些设备会过滤掉广告结果
  8. Bugs on (Samsung) phones at least < 5.0 三星5.0之前bug

    • Scan using service UUID filtering does not work -> no results returned 使用startLeScan()过滤UUID无法工作,没有返回结果
    • connectGatt() must be called from UI threadconnectGatt()必须在UI线程中调用
  9. Slow LE initial discovery and connection time 初始化查找与连接设备相当缓慢

    • HTC seems to have this issueHTC好像有这个问题

      华为,三星也会,其他没有测试过

  10. A high-level view on issues collated by Anaren 一些高层view问题的整理

  11. A more comprehensive list of issues has been collated by iDevicesInc 全面问题清单

个人推荐使用基于RxJava的库:https://github.com/Polidea/RxAndroidBle