cordova

1,618 阅读10分钟

There are several components to a Cordova application. The following diagram shows a high-level view of the Cordova application architecture.

img

create your first app

sudo npm install -g cordova
cordova create hello com.example.hello HelloWorld

add platform

cd hello
cordova platform add ios
cordova platform add android
cordova platform ls

Check requirements

cordova requirements

Add plugin

cordova plugin add cordova-plugin-camera
cordova plugin ls

merges根目录

merges/android/css/overrides.css
www/css/overrides.css

merges/ios/img/back_button.png
www/img/back_button.png

cordova及项目版本更新

sudo npm update -g cordova
sudo npm install -g cordova@3.1.0-0.2.0

cordova -v

cordova platform update android --save
cordova platform update ios --save

//事实上项目版本更新一般需要remove再add
cordova platform remove android
cordova platform add android@8

//拷贝www以及插件等资源文件(一般会拷贝www和插件www下脚本样式等资源,不拷贝原生代码,会配置config.xml)
cordova prepare

项目结构

myapp/
|-- config.xml
|-- hooks/
|-- merges/
| | |-- android/
| | |-- windows/
| | |-- ios/
|-- www/
|-- platforms/
| |-- android/
| |-- windows/
| |-- ios/
|-- plugins/
  |--cordova-plugin-camera/

Customize Icons

<platform name="android">
	<icon density="mdpi" src="res/icon/android/mdpi.png" />
  <icon density="hdpi" src="res/icon/android/hdpi.png" />
</platform>

<platform name="ios">
  <icon height="120" src="res/icon/ios/icon-40@3x.png" width="120" />
  <icon height="50" src="res/icon/ios/icon-50.png" width="50" />
  <icon height="100" src="res/icon/ios/icon-50@2x.png" width="100" />
</platform>

Splash screen and stoaryBoard

cordova-plugin-splashscreen插件

<!--splash screen duration-->
<preference name="SplashScreenDelay" value="3000" />
<platform name="android">
  <!--splash screen for ios-->
	<splash density="port-hdpi" src="res/screen/android/screen-hdpi-portrait.png" />
  <splash density="port-ldpi" src="res/screen/android/screen-ldpi-portrait.png" />
  <splash density="port-mdpi" src="res/screen/android/screen-mdpi-portrait.png" />
  <!--SplashMaintainAspectRatio: set to true, splash screen drawable is not stretched to fit screen, 
		but instead simply "covers" the screen-->
  <preference name="SplashMaintainAspectRatio" value="true" />
  <!--When set SplashShowOnlyFirstTime to true, splash screen will only appear on application launch-->
  <preference name="SplashShowOnlyFirstTime" value="true" />
</platform>
<platform name="ios">
  <!--splash screen for ios-->
	<splash height="480" src="res/screen/ios/1x.png" width="320" />
  <splash height="960" src="res/screen/ios/2x.png" width="640" />
  <splash height="2208" src="res/screen/ios/Default-736h.png" width="1242" />
  <!--story board for ios-->
  <splash src="res/screen/ios/Default@2x~iphone~anyany.png" />
  <splash src="res/screen/ios/Default@2x~iphone~comany.png" />
  <splash src="res/screen/ios/Default@2x~iphone~comcom.png" />
  <splash src="res/screen/ios/Default@3x~iphone~anyany.png" />
  <splash src="res/screen/ios/Default@3x~iphone~anycom.png" />
  <splash src="res/screen/ios/Default@3x~iphone~comany.png" />
</platform>

Plugin

plugman install --platform android --project android --plugin cordova-sqlite-storage

plugins/**/plugin.xml

<clobbers target="cordova.plugins.samplePlugin" />

插件调用名

cordova plugin add cordova-plugin-camera
cordova plugin remove cordova-plugin-camera
cordova plugin ls

Create a plugin

Plugin.xml

<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
        id="cordova-plugin-samplePlugin" version="0.2.3">
    <name>samplePlugin</name>
    <description>samplePlugin Plugin</description>
    <license>Apache 2.0</license>
    <keywords>samplePlugin</keywords>
    <js-module src="www/samplePlugin.js" name="samplePlugin">
        <clobbers target="cordova.plugins.samplePlugin" />
    </js-module>
    <platform name="ios">
        <config-file parent="/*" target="config.xml">
            <feature name="samplePlugin">
                <param name="ios-package" value="samplePlugin" />
            </feature>
        </config-file>
      	<config-file parent="NSBluetoothAlwaysUsageDescription" target="*-Info.plist">
            <string>need bluetooth to access device data such as pulse rate</string>
        </config-file>
        <header-file src="src/ios/Devices/BP550BTModule.h" />
        <source-file src="src/ios/Devices/BP550BTModule.m" />
    </platform>
  	<platform name="android">
        <config-file parent="/*" target="res/xml/config.xml">
            <feature name="samplePlugin">
                <param name="android-package" value="com.ccm.samplePlugin.samplePlugin" />
            </feature>
        </config-file>
        <config-file parent="/*" target="AndroidManifest.xml">
          <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
        </config-file> 
      	<source-file src="src/android/samplePlugin.java" target-dir="src/com/ccm/samplePlugin" />
        <source-file src="src/android/Devices/AM3SModule.java" target-dir="src/com/ccm/samplePlugin/Devices" />
    </platform>
</plugin>

inteface

cordova.exec(function(param) {},
             function(error) {},
             "service",
             "action",
             ["firstArgument", "secondArgument", 42, false]);
  • function(winParam) {}: A success callback function. Assuming yourexeccall completes successfully, this function executes along with any parameters you pass to it.
  • function(error) {}: An error callback function. If the operation does not complete successfully, this function executes with an optional error parameter.
  • "service": The service name to call on the native side. This corresponds to a native class, for which more information is available in the native guides listed below.
  • "action": The action name to call on the native side. This generally corresponds to the native class method. See the native guides listed below.
  • [/* arguments */]: An array of arguments to pass into the native environment.

Sample:

// samplePlugin.js
var exec = require("cordova/exec");
exports.coolMethod = function(arg0, success, error) {
   exec(success, error, "samplePlugin", "coolMethod", [arg0]);
};

// app调用
samplePlugin.coolMethod('argument', msg=>{console.log('success '+msg)}, ()=>{console.log('error')})
// navtive code
// java
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
    if ("coolMethod".equals(action)) {
        this.coolMethod(args.getLong(0));
        callbackContext.success('coolMethod triggered');
      	//callbackContext.error('coolMethod not triggered');
      
      	//PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
        //pluginResult.setKeepCallback(true);
        //callbackContext.sendPluginResult(pluginResult);
        return true;
    }
    return false;  // Returning false results in a "MethodNotFound" error.
}
// objective-C
- (void)coolMethod:(CDVInvokedUrlCommand*)command
{
    CDVPluginResult* pluginResult = nil;
    NSString* myarg = [command.arguments objectAtIndex:0];

    if (myarg != nil) {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
    } else {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Arg was null"];
    }
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

thread

// If you need to interact with the user interface,
cordova.getActivity().runOnUiThread(new Runnable() {
            public void run() {
                ...
                callbackContext.success(); // Thread-safe.
            }
});
// android, run in background
cordova.getThreadPool().execute(new Runnable() {
            public void run() {
                ...
                callbackContext.success(); // Thread-safe.
            }
});
// ios run in backround
[self.commandDelegate runInBackground:^{
        NSString* payload = nil;
        // Some blocking logic...
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload];
        // The sendPluginResult method is thread-safe.
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];

note

//package.json
"dependencies": {
    "com.ccm.samplePlugin": "./samplePlugin",
    //...
  }
//samplePlugin.js不需要cordova.define包裹, prepare自动包裹
cordova.define("com.ccm.samplePlugin.samplePlugin", function(require, exports, module) {
	var exec = require("cordova/exec");

  exports.coolMethod = function(arg0, success, error) {
    exec(success, error, "samplePlugin", "coolMethod", [arg0]);
  };
}

               

Config.xml

<?xml version='1.0' encoding='utf-8'?>
<widget android-versionCode="20051414" id="com.aaa.bbb" ios-CFBundleVersion="20051414" version="2.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>SampleProject</name>
    <description>
        samplePlugin bbb App.
    </description>
    <author email="aaa@bbb.com" href="https://www.aaa.com/">
        aaa Omega Team
    </author>
    <content src="dist/index.html" />>
    <plugin name="cordova-plugin-android-permissions" spec="^1.0.0" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <preference name="orientation" value="portrait" />
    <platform name="android">
        <allow-intent href="market:*" />
        <icon density="mdpi" src="res/icon/android/mdpi.png" />
        <resource-file src="res/java/AndroidBug5497Workaround.java" target="app/src/main/java/com/aaa/bbb/AndroidBug5497Workaround.java" />
        <splash density="port-hdpi" src="res/screen/android/screen-hdpi-portrait.png" />
        <preference name="AndroidPersistentFileLocation" value="Compatibility" />
    </platform>
    <platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
        <icon height="20" src="res/icon/ios/icon-20-ipad.png" width="20" />
        <resource-file src="res/oc/GoogleService-Info.plist" />
        <splash height="480" src="res/screen/ios/1x.png" width="320" />
        <preference name="BackupWebStorage" value="none" />
    </platform>
</widget>

widget

Root element of the config.xml document.

<widget android-versionCode="91008" id="com.aaa.bbb" ios-CFBundleVersion="91008" version="1.0.2" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">

content

Defines the app's starting page in the top-level web assets directory. The default value is index.html, which customarily appears in a project's top-level www directory.

<content src="startPage.html"></content>

plugin

Specifies details about what plugin to restore during a prepare. This element is automatically added to a project's config.xmlwhen a plugin is added using the --saveflag.

<plugin name="cordova-plugin-device" spec="^1.1.0" />
<plugin name="cordova-plugin-device" spec="https://github.com/apache/cordova-plugin-device.git#1.0.0" />

preference

Sets various options as pairs of name/value attributes. Each preference's name is case-insensitive. Many preferences are unique to specific platforms

example:

<preference name="DisallowOverscroll" value="true"/>
<preference name="Fullscreen" value="true" />
<preference name="BackgroundColor" value="0xff0000ff"/>
<preference name="Orientation" value="portrait" />
<!-- iOS only preferences -->
<preference name="target-device" value="universal" />
<preference name="deployment-target" value="7.0" />
<preference name="Suppresses3DTouchGesture" value="true" />
<preference name="SuppressesLongPressGesture" value="true" />
<preference name="TopActivityIndicator" value="white"/>
<preference name="BackupWebStorage" value="local"/>
<preference name="KeyboardDisplayRequiresUserAction" value="false" />

<!-- Android only preferences -->
<preference name="KeepRunning" value="false"/>
<preference name="android-targetSdkVersion" value="27"/>
<preference name="android-maxSdkVersion" value="28"/>
<preference name="android-minSdkVersion" value="19"/>

deployment-target: MinimumOSVersion in the ipa;

KeyboardDisplayRequiresUserAction: Set to false to allow the keyboard to appear when calling focus() on form inputs.

<preference name="KeyboardDisplayRequiresUserAction" value="false" />

大意就是这个API默认为true,这种情况下需要用户主动去点击元素,这样才能唤起键盘,通过focus去模拟的话是不行的。如果API设为false.这个时候是可以通过focus去模拟,并唤起键盘的。

Android-sdkVersion参考:developer.android.com/guide/topic…

access

Defines the set of external domains the app is allowed to communicate with. The default value shown above allows it to access any server. See the Domain Whitelist Guidefor details.

<access origin="*"></access>
<access origin="http://google.com"></access>

allow-navigation

Controls which URLs the WebView itself can be navigated to. Applies to top-level navigations only.

allow-intent

Controls which URLs the app is allowed to ask the system to open. By default, no external URLs are allowed.

<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
<allow-intent href="sms:*" />
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />

engine

Specifies details about what platform to restore during a prepare.

优先级低于package.json里的dependencies

<engine name="android" spec="https://github.com/apache/cordova-android.git#5.1.1" />
<engine name="ios" spec="^4.0.0" />

Plugin.xml

js-module

Most plugins include one or more JavaScript files. Each <js-module>tag corresponds to a JavaScript file, and prevents the plugin's users from having to add a <script>tag for each file. Do not wrap the file with cordova.define, as it is added automatically.

clobbers

Allowed within <js-module>element. Used to specify the namespace under windowobject where module.exports gets inserted.

<js-module name="samplePlugin" src="www/samplePlugin.js">
  <clobbers target="cordova.plugins.samplePlugin" />
</js-module>

source-file

Identifies executable source code that should be installed into a project.

<!-- android -->
<source-file src="src/android/Foo.java" target-dir="src/com/alunny/foo" />
<!-- ios -->
<source-file src="src/ios/CDVFoo.m" />
<source-file src="src/ios/someLib.a" framework="true" />
<source-file src="src/ios/someLib.a" compiler-flags="-fno-objc-arc" />

header-file

This is like <source-file>element but specifically for platforms such as iOS and Android that distinguish between source files, headers, and resources.

<header-file src="CDVFoo.h" />
<header-file src="CDVSomeHeader.h" type="BridgingHeader" />

resource-file

This is like <source-file>element, but specifically for platforms such as iOS and Android that distinguish between source files, headers, and resources.

<resource-file src="FooPluginStrings.xml" target="res/values/FooPluginStrings.xml" />
<resource-file src="src/ios/assets/io_cordova_ccm_ios.pem" />

config-file

Identifies an XML-based configuration file to be modified, where in that document the modification should take place, and what should be modified. Two file types that have been tested for modification with this element are xmland plistfiles.

<config-file parent="/*" target="res/xml/config.xml">
    <feature name="samplePlugin">
        <param name="android-package" value="com.ccm.samplePlugin.samplePlugin" />
    </feature>
</config-file>
<config-file parent="/*" target="AndroidManifest.xml">
    <!-- Internet communication and detect / manage Wi-Fi state -->
    <uses-permission android:name="android.permission.INTERNET" />
</config-file>

<config-file parent="/*" target="config.xml">
    <feature name="samplePlugin">
        <param name="ios-package" value="samplePlugin" />
    </feature>
</config-file>
<config-file parent="NSBluetoothPeripheralUsageDescription" target="*-Info.plist">
    <string>need bluetooth to access device data such as pulse rate</string>
</config-file>

framework

Identifies a framework (usually part of the OS/platform) on which the plugin depends.

<framework src="io.reactivex.rxjava2:rxjava:2.1.9" />
<framework src="src/android/cordova.gradle" custom="true" type="gradleReference"/>

uses-permission

In certain cases, a plugin may need to make configuration changes dependent on the target application.

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

Runtime Permissions

(Cordova-Android 5.0.0+, Android 6.0+)

const permissions =
  window.cordova && window.cordova.plugins && window.cordova.plugins.permissions
    ? window.cordova.plugins.permissions
    : {};

export const PERMISSIONS = {
  ACCESS_FINE_LOCATION: permissions.ACCESS_FINE_LOCATION,
  ACCESS_COARSE_LOCATION: permissions.ACCESS_COARSE_LOCATION,
  WRITE_EXTERNAL_STORAGE: permissions.WRITE_EXTERNAL_STORAGE,
  RECORD_AUDIO: permissions.RECORD_AUDIO,
};

const defaultPermissionList = [
  PERMISSIONS.ACCESS_COARSE_LOCATION,
  PERMISSIONS.ACCESS_FINE_LOCATION,
  PERMISSIONS.WRITE_EXTERNAL_STORAGE,
  PERMISSIONS.RECORD_AUDIO,
];
const success = status => {
  if (status.hasPermission && permissionReady) permissionReady();
}
permissions.requestPermissions(permissionList, success, error);
permissions.checkPermission(permissionList, success, error);

cordova-plugin-file

PersistentFileLocation

一般都配置Compatibility即可,iOS可以不用配置默认Compatibility。

Android

<preference name="AndroidPersistentFileLocation" value="Internal" />

<preference name="AndroidPersistentFileLocation" value="Compatibility" />

默认是Internal,即存储在/data/data/(不可见),要想保存到SD卡或等效的存储分区则配置Compatibility

iOS

<preference name="iosPersistentFileLocation" value="Library" />

<preference name="iosPersistentFileLocation" value="Compatibility" />

默认是Compatibility,会保存在程序的 Documents 文件目录下,要想保存到应用的 Library 文件夹下则配置Library

ExtraFilesystems

android一般无需配置,ios配置"library,library-nosync,documents,documents-nosync,cache,bundle,root"

Android

  • files: The application's internal file storage directory
  • files-external: The application's external file storage directory
  • sdcard: The global external file storage directory (this is the root of the SD card, if one is installed). You must have the android.permission.WRITE_EXTERNAL_STORAGEpermission to use this.
  • cache: The application's internal cache directory
  • cache-external: The application's external cache directory
  • assets: The application's bundle (read-only)
  • root: The entire device filesystem
  • applicationDirectory: ReadOnly with restricted access. Copying files in this directory is possible, but reading it directly results in 'file not found'. Android also supports a special filesystem named "documents", which represents a "/Documents/" subdirectory within the "files" filesystem.

android需要额外配置权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

iOS

  • library: The application's Library directory
  • documents: The application's Documents directory
  • cache: The application's Cache directory
  • bundle: The application's bundle; the location of the app itself on disk (read-only)
  • root: The entire device filesystem

By default, the library and documents directories can be synced to iCloud. You can also request two additional filesystems, library-nosyncand documents-nosync, which represent a special non-synced directory within the /Libraryor /Documentsfilesystem.

读取写入文件参考https://www.html5rocks.com/en/tutorials/file/filesystem/

cordova-plugin-media

获取duration的bug

解决方案:获取duration之前,(调低音量)播放一次。

setDuration = src => {
    const { audioDurations } = this.state;
    if (audioDurations[src]) return;
    if (!this.audios[src]) {
      this.audios[src] = audioUtil.initRecord(src);
    }
    audioUtil.setVolume(this.audios[src], '0.0');
    audioUtil.play(this.audios[src]);
    let duration = audioUtil.getDuration(this.audios[src]);
    let counter = 0;
    const timerDur = setInterval(() => {
      counter += 100;
      if (counter > 2000) {
        clearInterval(timerDur);
      }
      duration = audioUtil.getDuration(this.audios[src]);
      console.debug(`${duration}sec`);
      if (duration > 0) {
        audioUtil.stop(this.audios[src]);
        audioUtil.setVolume(this.audios[src], '1.0');
        audioDurations[src] = duration;
        this.setState({ audioDurations: { ...audioDurations } });
        console.debug(`${duration}sec...`);
        clearInterval(timerDur);
      }
    }, 100);
  };

src直接以文件根目录开始

this.mediaSrc = `aaa/audios/myRecording${moment().format('YYYYMMDDHHmmss')}.wav`;

cordova-plugin-backbutton

navigator.Backbutton.goHome(function() {
  console.log('success')
}, function() {
  console.log('fail')
});

navigator.Backbutton.goBack(function() {
  console.log('success')
}, function() {
  console.log('fail')
});

//配合 document.addEventListener('backbutton', () => {})

indexedDB

支持ios和安卓

Events

ios、android兼容

deviceready

The deviceready event fires when Cordova is fully loaded. This event is essential to any application. It signals that Cordova's device APIs have loaded and are ready to access.

pause

The pause event fires when the native platform puts the application into the background, typically when the user switches to a different application.

iOS Quirks

In the pause handler, any calls to the Cordova API or to native plugins that go through Objective-C do not work, along with any interactive calls, such as alerts or console.log(). They are only processed when the app resumes, on the next run loop.

android兼容

backbutton

The event fires when the user presses the back button.

volumedownbutton

The event fires when the user presses the volume down button.

volumeupbutton

The event fires when the user presses the volume up button.

menubutton

searchbutton

h5应用注意点

配置

{
  publicPath: './',
  hd: false,
}

token保证持久登录

在浏览 器上能正常工作的cookie,就不能在app里使用了,因为打包后的项目实际上在File协议下运行,所以通过sessionID来保证登录的方案 不可行(当然没打包前是可以的,这才是最迷惑的),建议使用token放在请求头部使用,来保证登录,然后使用本地缓存token来保证持久登录。

刘海屏

  1. 设置storyboard(若需要app全屏覆盖)

  2. 使用安全区域

.fixed_bottom, body{
	padding-bottom: constant(safe-area-inset-bottom);
	padding-bottom: env(safe-area-inset-bottom);
}

Android 9.0/P http 网络请求的问题

Google表示,为保证用户数据和设备的安全,针对下一代 Android 系统(Android P) 的应用程序,将要求默认使用加密连接,这意味着 Android P 将禁止 App 使用所有未加密的连接,因此运行 Android P 系统的安卓设备无论是接收或者发送流量,未来都不能明码传输,需要使用下一代(Transport Layer Security)传输层安全协议,而 Android Nougat 和 Oreo 则不受影响。因此在Android P 使用HttpUrlConnection进行http请求会出现以下异常:W/System.err: java.io.IOException: Cleartext HTTP traffic to not permitted 使用OKHttp请求则出现:java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security policy。

在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,同样地,如果应用嵌套了webview,webview也只能使用https请求。

有以下三种解决方案

1、APP改用https请求

2、targetSdkVersion 降到27以下

3、在 res 下新增一个 xml 目录,然后创建一个名为:network_security_config.xml 文件(名字自定) ,内容如下,大概意思就是允许开启http请求

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

然后在APP的AndroidManifest.xml文件下的application标签增加以下属性

<application
...
 android:networkSecurityConfig="@xml/network_security_config"
...
/>

cordoval config.xml配置

<edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application">
            <application android:networkSecurityConfig="@xml/network_security_config" />
        </edit-config>

WKWebview

issues.apache.org/jira/issues…

UIWebview->WKWebview localStorage数据迁移:cordova-plugin-migrate-localstorage

Appcenter

#全局依赖及登陆
cnpm install -g appcenter-cli
appcenter login

#code push plugin
cordova plugin add cordova-plugin-code-push@latest

#display codepush key secret
appcenter codepush deployment list -a sinnkirou/bbb-android --displayKeys
#code push with prepare
appcenter codepush release-cordova -a  sinnkirou/bbb-android
#code push without prepare
appcenter codepush release -a sinnkirou/bbb-android -c "platforms/android/app/src/main/assets/www" -t "*" -m

#analytics and crash plugin
cordova plugin add cordova-plugin-appcenter-analytics
cordova plugin add cordova-plugin-appcenter-crashes
<!--config.xml配置-->
<!--按平台分别配置-->
<preference name="APP_SECRET" value="0062b700-sample-code" />
<preference name="CodePushDeploymentKey" value="4vsaRc-sampl-code" />
<!--需要手动监听crash事件则配置-->
<preference name="APPCENTER_CRASHES_ALWAYS_SEND" value="false" />
//track an event
var success = function() {
    console.log("Event tracked");
}
var error = function(error) {
    console.error(error);
}
AppCenter.Analytics.trackEvent('Video clicked', { Category: 'Music', FileName: 'favorite.avi' }, success, error);

CocoaPods

CocoaPods 是一个 Cocoa 和 Cocoa Touch 框架的依赖管理器,具体原理和 Homebrew 有点类似,都是从 GitHub 下载索引,然后根据索引下载依赖的源代码。

对于旧版的 CocoaPods 可以使用如下方法使用 tuna 的镜像:

$ pod repo remove master
$ pod repo add master https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git
$ pod repo update

新版的 CocoaPods 不允许用pod repo add直接添加master库了,但是依然可以:

$ cd ~/.cocoapods/repos 
$ pod repo remove master
$ git clone https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git master

最后进入自己的工程,在自己工程的podFile第一行加上:

source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'

pod install --verbose --no-repo-update pod update --verbose --no-repo-update

新的cdn源

source 'cdn.cocoapods.org/'

Gradle

国内镜像

 repositories {
        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public'}
        // ...
 }

Program type already present: android.support.v4.os.ResultReceiver$MyResultReceiver

android studio> (Refactor>Migrate to AndroidX...)

Issue

  1. Current working directory is not a Cordova based project

    根目录添加www文件夹

  2. conflict edit.config,删除plugins文件夹重新add 平台

  3. xcode framework not found Pods_MDQ_SampleProject.framework问题?(转移目录或者更改app名后)

    1. Pods文件夹拖入到xcode下
    2. added folders: create groups勾选,add to targets不勾选
  4. 插件更新后,删除platforms和plugins,重新运行prepare

  5. 安卓apk安装提示App uninstalled

    platform/android/app/build,删除build文件夹重新打包

  6. Failed to install 'cordova-plugin-xxx': undefined

    查看pod --version,检查pod版本,重新安装sudo gem install cocoapods

  7. Program type already present: org.apache.cordova.PermissionHelper

    android studio> (Refactor>Migrate to AndroidX...),不行的话删除重新安装

    sudo cordova plugin remove cordova-plugin-compat --force
    sudo cordova plugin add cordova-plugin-compat
    

上架发布更新

官方文档

support.google.com/googleplay/…

help.apple.com/app-store-c…