• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

将Expo应用程序分离到ExpoKit

在我的使用 Expo 更轻松地开发 react Native 帖子中,您了解了 Expo 如何让初学者更轻松地开始使用 React Native 创建应用程序。您还了解到,由于不再需要设置android Studio、Xcode 或其他开发工具,Expo 允许开发人员更快地开始并运行开发 React Native 应用程序。

但正如您也看到的,Expo 并不支持应用程序可能需要的所有本机功能。尽管 Expo 团队一直 在努力支持更多的原生功能,但最好学习如何将现有的 Expo 项目转换为标准的原生项目,以便在需要时轻松转换。 

分离到 ExpoKit

为了分离到 ExpoKit,您首先必须编辑 app.json 和 package.json 文件。 

在 app.json 文件中,确保 name 已设置 a。platforms 应该是您要构建的平台。 

{
  "expo": {
    "name": "ocdmom",
    "platforms": [
      "ios",
      "android"
    ],

如果要为 iOS 构建,则必须指定 ios 选项:

"ios": {
  "bundleIdentifier": "com.ocd.mom"
},

如果你想支持 Android,那么还要指定以下选项:

"android": {
  "package": "com.ocd.mom"
}

exp 创建项目时,命令行工具还预先填充了其他选项 。但唯一重要的是 bundleIdentifier iOS 和 package Android。这些将是应用程序发布到 Apple 或 Play 商店后的唯一 ID。分离需要这些细节,因为它实际上会为要在设备上运行的应用程序生成本机代码。您可以在 Expo 文档中找到有关 app.json 文件 的不同配置选项的更多信息。

您可以 在 GitHub 存储库中查看文件的完整内容。

接下来,打开 package.json 文件并添加项目名称:

"name": "ocdmom"

这应该是您使用创建项目时使用的名称 exp init。它们相同非常重要,因为  在编译应用程序时会使用您在package.jsonname中 指定的 内容。此名称不一致会导致错误。

现在我们准备好分离到 ExpoKit。在项目目录的根目录下执行以下命令:

exp detach

这将在本地下载适用于 Android 和 iOS 的原生 Expo 包。

如果成功,您应该会看到类似于以下内容的输出:

将Expo应用程序分离到ExpoKit  第1张如果要部署到 iOS,则需要安装最新版本的 Xcode。在编写本教程时,最新版本是 9。接下来,通过执行 sudo gem install cocoapods. 这允许您安装项目的本机 iOS 依赖项。完成后,导航到项目的 ios 目录并执行 pod install 以安装所有本机依赖项。 

安装自定义本机包

现在我们已经分离了,我们现在可以像在标准 React Native 项目中一样安装原生包。 

对于这个应用程序,我们需要 React Native background Timer 和 Pusher 包。

首先,安装 Pusher 包,因为它更简单:

npm install --save pusher-js

这使我们能够与您之前创建的 Pusher 应用程序进行通信。

接下来,安装 React Native后台Timer。这允许我们根据特定的时间间隔定期执行代码(即使应用程序在后台):

npm install --save react-native-background-timer

与 Pusher 包不同,这需要将本机库(iOS 或 Android)链接到应用程序。执行以下命令会为您执行此操作:

react-native link

完成后,它还应该在 android/app/src/main/host/exp/exponent/MainApplication.java上初始化模块。但只是为了确保,检查该文件中是否存在以下内容:

import com.ocetnik.timer.BackgroundTimerPackage; // check this

public List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
      new BackgroundTimerPackage() // also check this
    );
}

如果您正在为 iOS 构建,请打开 ios目录 中 的Podfile 并确保在  声明之前添加以下内容:post_install

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

完成后, pod install 在 ios 目录中执行以安装本机模块。

对于 Android,当您使用 Android Studio 运行应用程序时,这已经自动完成。

更新Android清单文件

如果您正在为 Android 构建,请打开 Android 清单文件 ( android/app/src/main/AndroidManifest.xml ) 并确保添加了以下权限:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

这允许我们请求 Pusher 访问互联网和 Expo 的权限,以获取用户在 Android 设备上的当前位置。 

运行应用程序

我们还没有完成,但最好现在运行该应用程序,这样您就可以看到它是否有效。这样,您还可以在我们开发应用程序时看到更改。

运行应用程序的第一步是 exp start 从项目的根目录执行。这将启动开发服务器,以便您对源代码所做的任何更改都会反映在应用预览中。

如果您正在为 Android 构建,请打开 Android Studio 并选择 Open an existing Android Studio project。在显示的目录选择器中,选择  Expo 项目中的android文件夹。选择文件夹后,它应该索引该文件夹中的文件。此时,您现在应该能够通过  从顶部菜单中选择Build > Rebuild Project来重建项目。完成后,通过选择 Run > Run 'app'来运行应用程序。

Android Studio 可以在连接到您的计算机的任何 Android 设备上、在您通过 Android Studio 安装的模拟器之一或通过 Genymotion(Android Studio 自动检测正在运行的模拟器实例)上运行该应用程序。对于这个应用程序,我建议您使用 Genymotion 模拟器,因为它有一个不错的 GPS 模拟小部件,可让您通过 Google 地图界面更改位置:

将Expo应用程序分离到ExpoKit  第2张(如果您在设备上运行应用程序时遇到问题,请务必查看  有关让 Android Studio 识别您的设备的Stack  Overflow问题。)


完成后,使用 Xcode 打开 ios/ ocdmom .xcworkspace 文件。一旦 Xcode 完成对文件的索引,您应该能够点击那个大播放按钮,它会自动在您选择的 iOS 模拟器上运行应用程序。

Xcode 还允许您模拟位置,但仅当您构建应用程序以在模拟器中运行时。更改代码并让开发服务器刷新应用程序实际上不会更改位置。要更改位置,请单击发送图标并选择您要使用的位置:

将Expo应用程序分离到ExpoKit  第3张

继续编写应用程序

现在我们已准备好继续为应用程序编写代码。这一次,我们将添加在应用程序处于后台时运行一些代码的功能。

添加后台任务

导入您之前安装的 Pusher 和 Background Timer 包:

import BackgroundTimer from 'react-native-background-timer';
import Pusher from 'pusher-js/react-native';

为您之前创建的 Google 项目的 Google api键设置值:

const GOOGLE_API_KEY = 'YOUR GOOGLE PROJECT API KEY';

使用 Expo 的 Location and Permissions API:

const { Location, Permissions } = Expo;

Expo 的 API 跨平台工作——这与标准的 React Native 项目没有什么不同,您必须安装像 React Native Permissions这样的包 才能访问跨平台工作的权限 API。

接下来,设置用于跟踪用户当前位置的代码将执行的时间间隔(以毫秒为单位)。在这种情况下,我们希望它每 30 分钟执行一次。请注意,在下面的代码中,我们使用 location_status 变量的值来检查是否授予了访问用户当前位置的权限。安装组件后,我们将在稍后设置此变量的值:

var interval_ms = 1800 * 100; // 1800 seconds = 30 minutes, times 100 to convert to milliseconds
var location_status = null; // whether accessing the user's location is allowed or not

BackgroundTimer.runBackgroundTimer(() => { // run the background task
 
  if(location_status == 'granted'){ // if permission to access the location is granted by the user

    // next: add code for getting the user's current location
  
  }
  
}, 
interval_ms);

获取当前位置

使用 Expo 的 Location API获取当前位置:

Location.getCurrentpositionasync({ // get the user's coordinates
  enableHighAccuracy: true // enable fetching of high accuracy location
})
.then((res) => {
 
  let { latitude, longitude } = res.coords; // extract the latitude and longitude values

  // next: add code for getting the address based on the coordinates
});

接下来,使用 Google Maps Geocoding API,通过提供纬度和经度值向反向地理编码端点发出请求。这将返回基于这些坐标的格式化地址:

fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${GOOGLE_API_KEY}`)
  .then((response) => response.json())
  .then((responseJson) => {
    let addr = responseJson.results[0].formatted_address;

    // next: send the location with Pusher

  })
  .catch((error) => {
    console.error(error);
  });

使用推送器发送位置

下一步是使用 Pusher 发送位置。稍后,我们将创建将作为身份验证端点的服务器,同时显示显示用户当前位置的页面。

更新构造函数以设置 Pusher 实例的默认值:

constructor() {
  /*
  the code for generating unique code from earlier
  */
  this.pusher = null;
}

当组件被挂载时,我们要初始化 Pusher。您现在可以从之前创建的 Pusher 应用程序的设置中提供 Pusher API 密钥和集群

componentWillMount() {
  this.pusher = new Pusher('YOUR PUSHER APP KEY', {
    authEndpoint: 'YOUR AUTH SERVER ENDPOINT (TO BE ADDED LATER)',
    cluster: 'YOUR PUSHER CLUSTER',
    encrypted: true // whether the connection will be encrypted or not. This requires https if set to true
  });
}

接下来,您现在可以添加用于发送当前位置的代码。在 Pusher 中,这是通过调用 trigger() 方法来完成的。第一个参数是被触发事件的名称,第二个参数是一个包含您要发送 的数据的对象。

稍后,在服务器中,我们将订阅相同的频道,一旦组件被挂载,我们将订阅该频道。然后我们将绑定到该 client-location 事件,以便每次从某个地方触发它时,服务器也会收到通知(尽管只有当它所服务的页面也订阅了同一频道时):

fetch(...)
  .then(...)
  .then((responseJson) => {
    let addr = responseJson.results[0].formatted_address;

    current_location_channel.trigger('client-location', {
      addr: addr,
      lat: latitude,
      lng: longitude
    });

  })
  .catch(...);

我们唯一一次请求访问用户当前位置的权限是在组件安装时。然后,我们将 location_status 根据用户的选择进行更新。该值可以是“授予”或“拒绝”。 

请记住,用于检查用户当前位置的代码会定期执行。这意味着 location_status 变量的新值也将在稍后执行函数时使用。之后,我们还想订阅将发送位置更新的 Pusher 频道:

componentDidMount() { 
  try {
    Permissions.askAsync(Permissions.LOCATION).then(({ status }) => {
      location_status = status;
    });
  }catch(error){
    console.log('err: ', error);
  }
  // subscribe to the Pusher channel 
  current_location_channel = this.pusher.subscribe('private-current-location-' + this.state.unique_code);
}

创建服务器

现在我们准备好创建服务器了。首先,在应用程序的项目目录之外创建您的工作目录 ( ocdmom -server )。在该目录内导航并执行 npm init. 只需按 Enter 直到它创建 package.json 文件。

接下来,安装我们需要的包:

npm install --save express body-parser pusher

以下是每个包的功能的概述:

  • express: 用于创建服务器。这负责提供跟踪页面以及响应身份验证端点。

  • body-parser: Express 中间件,它解析请求正文并将其作为javascript对象提供。 

  • pusher:用于与您之前创建的 Pusher 应用程序通信。

完成后,您的 package.json 文件现在应该如下所示:

{
  "name": "ocdmom-server",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.18.2",
    "express": "^4.16.2",
    "pusher": "^1.5.1"
  }
}

创建一个 server.js 文件并导入我们刚刚安装的包:

var express = require('express');
var bodyParser = require('body-parser');
var Pusher = require('pusher');

将服务器配置为使用该 body-parser 包并将公用文件夹设置 为 静态文件目录:

var app = express();
app.use(bodyParser.json()); // set middleware to parse request body to JavaScript object
app.use(bodyParser.urlencoded({ extended: false })); // for parsing URL encoded request body
app.use(express.static('public')); // specify the directory where the static files like css, JavaScript and image files lives

初始化推送器。此处提供的值将来自环境变量。我们稍后会在部署服务器时添加它们:

var pusher = new Pusher({ 
  appId: process.env.APP_ID, 
  key: process.env.APP_KEY, 
  secret:  process.env.APP_SECRET,
  cluster: process.env.APP_CLUSTER, 
});

访问基本 URL 时提供跟踪页面:

app.get('/', function(req, res){
  res.sendFile(__dirname + '/public/tracker.html');
});

接下来,创建用于响应对 auth 端点的请求的路由。每次应用程序初始化与 Pusher 的连接以及访问跟踪页面时都会触发此事件。这样做是对用户进行身份验证,以便他们可以直接从客户端与 Pusher 应用程序通信。 

请注意,这实际上并没有任何安全措施。这意味着任何人只要有权访问您的 Pusher App 密钥,就可以向您的身份验证端点发出请求。在生产应用程序中,您需要更强大的安全性!

app.post('/pusher/auth', function(req, res) {
  var socketId = req.body.socket_id;
  var channel = req.body.channel_name;
  var auth = pusher.authenticate(socketId, channel);  
  var app_key = req.body.app_key;
  var auth = pusher.authenticate(socketId, channel);
  res.send(auth);});

最后,让服务器监听环境变量中指定的端口。默认情况下,它是端口 80,但我们也将其设置为备用值,以防万一它不存在:

var port = process.env.PORT || 80;
app.listen(port);

追踪页面

跟踪页面显示每次 client-location 从应用程序触发事件时都会更新的地图。不要忘记提供您的 Google API 密钥:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <meta charset="utf-8">
    <title>OCDMom Tracker</title>
    <script src="https://js.pusher.com/4.2/pusher.min.js"></script> <!-- the pusher library -->
    <link rel="stylesheet" href="css/style.css">
  </head>
  <body>
    <div id="map"></div>
    
    <script src="js/tracker.js"></script> <!-- the main JavaScript file for this page -->

    <script async defer
    src="https://maps.googleapis.com/maps/api/js?key=YOUR-GOOGLE-API-KEY&callback=initMap"> 
    </script> <!-- the google maps library -->
  </body>
</html>

接下来,创建一个 public/js/tracker.js 文件并添加以下内容:

function getParameterByName(name, url) {
    if (!url) url = window.location.href;
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}

上面的函数从 URL 中提取查询参数。在浏览器上访问服务器的基本 URL 时,需要将唯一代码(应用程序中显示的代码)作为查询参数包含在内。这使我们能够跟踪用户的位置,因为它会为我们订阅与应用订阅的频道相同的频道。

接下来,初始化 Pusher。代码与之前服务器中的代码类似。唯一的区别是我们只需要指定 Pusher 应用密钥、auth 端点和集群:

var pusher = new Pusher('YOUR PUSHER APP KEY', {
  authEndpoint: 'YOUR PUSHER AUTH ENDPOINT',
  cluster: 'YOUR PUSHER CLUSTER',
  encrypted: true
});

检查是否 code 作为查询参数提供,并且仅在提供时订阅 Pusher 通道:

var channel = null;

if(getParameterByName('code') == null){
  alert('Make sure that the code is supplied as a query parameter, then refresh the page.');
}else{
  channel = pusher.subscribe('private-current-location-' + getParameterByName('code'));
}

添加初始化地图的功能。这将显示地图以及指向我们指定的默认位置的标记:

var map = null;
var marker = null;

function initMap(){
  var myLatLng = { // set the default location displayed on the map
    lat: -25.363, 
    lng: 131.044
  };

  map = new google.maps.Map(document.getElementById('map'), {
    zoom: 16,
    center: myLatLng
  });

  marker = new google.maps.Marker({
    position: myLatLng,
    map: map
  });
}

绑定到 client-location 事件。每当应用程序触发一个 client-location 与用户作为查询参数提供的具有相同唯一代码的事件时,都会执行回调函数:

if(channel){
  channel.bind('client-location', function(data) {
    console.log('message received: ', data);
    var position = new google.maps.LatLng(data.lat, data.lng); // create a new Google maps position object
    // set it as the position for the marker and the map
    marker.setPosition(position); 
    map.setCenter(position);
  });
}

接下来,添加跟踪页面的样式(public/css/style.css):

#map {
  height: 100%;
}

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
}

部署服务器

我们将使用 Now 来部署服务器。它对  开源项目是免费的。

立即全球安装:

npm install now

安装后,您现在可以将 Pusher 应用程序凭据添加为 secrets。如前所述,Now 对开源项目是免费的。这意味着一旦部署了服务器,其源代码将在 /_src 路径中可用。这不是很好,因为每个人都可以看到您的 Pusher 应用程序凭据。所以我们要做的就是将它们添加为秘密,以便可以将它们作为环境变量进行访问。 

还记得  之前服务器代码中的process.env.APP_ID or 吗?process.env.APP_KEY这些通过秘密被设置为环境变量。 pusher_app_id 是分配给密钥的名称, YOUR_PUSHER_APP_ID 是您的 Pusher 应用程序的 ID。执行以下命令将您的 Pusher 应用凭据添加为机密:

now secret add pusher_app_id YOUR_PUSHER_APP_ID
now secret add pusher_app_key YOUR_PUSHER_APP_KEY
now secret add pusher_app_secret YOUR_PUSHER_APP_SECRET
now secret add pusher_app_cluster YOUR_PUSHER_APP_CLUSTER

添加这些后,您现在可以部署服务器。 APP_ID 是环境变量 pusher_app_id 的名称,是您要访问的密钥的名称:

now -e APP_ID=@pusher_app_id -e APP_KEY=@pusher_app_key -e APP_SECRET=@pusher_app_secret APP_CLUSTER=@pusher_app_cluster

这是完成部署后的外观。它返回的 URL 是服务器的基本 URL:

将Expo应用程序分离到ExpoKit  第4张将该网址复制到 App.js 文件 并保存更改:

this.pusher = new Pusher('YOUR PUSHER APP KEY', {
  authEndpoint: 'https://BASE-URL-OF-YOUR-SERVER/pusher/auth',
  cluster: 'YOUR PUSHER APP CLUSTER',
  encrypted: true
});

此时,应用程序现在应该可以正常运行了。

结论

而已!在这个由两部分组成的系列中,您学习了如何将现有的 Expo 项目分离到 ExpoKit。ExpoKit 是在您的应用程序已转换为标准原生项目时使用 Expo 平台提供的一些工具的好方法。这允许您将现有的本地模块用于 React Native 并创建您自己的模块。 


文章目录
  • 分离到 ExpoKit
  • 安装自定义本机包
  • 更新Android清单文件
  • 运行应用程序
  • 继续编写应用程序
    • 添加后台任务
    • 获取当前位置
    • 使用推送器发送位置
  • 创建服务器
    • 追踪页面
  • 部署服务器
  • 结论