Apache Cordova / PhoneGap 치트시트
Apache Cordova / PhoneGap - Hybrid Mobile App Development
Apache Cordova (이전의 PhoneGap)는 개발자가 HTML, CSS, JavaScript를 사용하여 네이티브 모바일 애플리케이션을 빌드할 수 있게 해주는 모바일 애플리케이션 개발 프레임워크입니다. JavaScript 플러그인을 통해 네이티브 디바이스 API에 접근할 수 있습니다.
[No text to translate] ```bash # Install Node.js (version 12 or later) # Download from nodejs.orgVerify Node.js installation
node —version npm —version
Install Java Development Kit (JDK) 8 or later
Download from Oracle or use OpenJDK
Install Android Studio (for Android development)
Download from developer.android.com
Install Xcode (for iOS development, macOS only)
Download from Mac App Store
Set environment variables
export ANDROID_HOME=$HOME/Library/Android/sdk export PATH=$PATH:$ANDROID_HOME/tools export PATH=$PATH:$ANDROID_HOME/platform-tools
```bash
# Install Cordova CLI globally
npm install -g cordova
# Verify installation
cordova --version
# Check requirements for platforms
cordova requirements
# Install platform-specific tools
# For Android
npm install -g gradle
# For iOS (macOS only)
npm install -g ios-deploy
```### Cordova CLI 설치
```bash
# Install PhoneGap CLI (Adobe's distribution)
npm install -g phonegap
# Verify installation
phonegap --version
# PhoneGap Build service (deprecated)
# Use Cordova CLI for new projects
```### PhoneGap CLI (대안)
```bash
# Create new Cordova project
cordova create MyApp com.example.myapp "My App"
# Navigate to project directory
cd MyApp
# Add platforms
cordova platform add android
cordova platform add ios
# Add plugins
cordova plugin add cordova-plugin-device
cordova plugin add cordova-plugin-camera
# Build the app
cordova build
# Run on device/emulator
cordova run android
cordova run ios
```### 새 프로젝트 생성
```bash
# Create with specific template
cordova create MyApp com.example.myapp "My App" --template hello-world
# Create with custom template
cordova create MyApp com.example.myapp "My App" --template https://github.com/user/template.git
# Create blank project
cordova create MyApp com.example.myapp "My App" --template blank
# Create with TypeScript template
cordova create MyApp com.example.myapp "My App" --template cordova-template-typescript
```### 프로젝트 템플릿
MyApp/ ├── config.xml # Cordova configuration ├── package.json # Node.js dependencies ├── www/ # Web assets │ ├── index.html # Main HTML file │ ├── css/ # Stylesheets │ ├── js/ # JavaScript files │ └── img/ # Images ├── platforms/ # Platform-specific code (generated) │ ├── android/ │ └── ios/ ├── plugins/ # Installed plugins (generated) ├── hooks/ # Build hooks └── res/ # Resources (icons, splash screens) ├── icon/ └── screen/
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
<title>My App</title>
<link rel="stylesheet" type="text/css" href="css/index.css">
</head>
<body>
<div class="app">
<h1>Apache Cordova</h1>
<div id="deviceready" class="blink">
<p class="event listening">Connecting to Device</p>
<p class="event received">Device is Ready</p>
</div>
</div>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="js/index.js"></script>
</body>
</html>
```### www/index.html
```javascript
var app = {
// Application Constructor
initialize: function() {
document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
},
// deviceready Event Handler
onDeviceReady: function() {
this.receivedEvent('deviceready');
// Cordova is now initialized. Have fun!
console.log('Running cordova-' + cordova.platformId + '@' + cordova.version);
// Example: Get device information
console.log('Device Model: ' + device.model);
console.log('Device Platform: ' + device.platform);
console.log('Device Version: ' + device.version);
},
// Update DOM on a Received Event
receivedEvent: function(id) {
var parentElement = document.getElementById(id);
var listeningElement = parentElement.querySelector('.listening');
var receivedElement = parentElement.querySelector('.received');
listeningElement.setAttribute('style', 'display:none;');
receivedElement.setAttribute('style', 'display:block;');
console.log('Received Event: ' + id);
}
};
app.initialize();
```### www/js/index.js
```bash
# Create new project
cordova create <path> [id [name [config]]] [options]
# Add platform
cordova platform add <platform-name>
cordova platform add android
cordova platform add ios
cordova platform add browser
# Remove platform
cordova platform remove <platform-name>
cordova platform rm android
# List platforms
cordova platform list
cordova platform ls
# Update platform
cordova platform update <platform-name>
# Check platform version
cordova platform version
```### 프로젝트 관리
```bash
# Add plugin
cordova plugin add <plugin-name>
cordova plugin add cordova-plugin-camera
cordova plugin add cordova-plugin-device
# Add plugin with variables
cordova plugin add cordova-plugin-facebook4 --variable APP_ID="123456789" --variable APP_NAME="myApplication"
# Remove plugin
cordova plugin remove <plugin-name>
cordova plugin rm cordova-plugin-camera
# List plugins
cordova plugin list
cordova plugin ls
# Search plugins
cordova plugin search camera
# Update plugin
cordova plugin update <plugin-name>
```### 플러그인 관리
```bash
# Build for all platforms
cordova build
# Build for specific platform
cordova build android
cordova build ios
# Build with options
cordova build android --release
cordova build ios --device
# Run on emulator
cordova emulate android
cordova emulate ios
# Run on device
cordova run android
cordova run ios
# Run with options
cordova run android --device
cordova run ios --target="iPhone-12"
# Serve for browser testing
cordova serve
cordova serve --port=8080
```### 빌드 및 실행
```bash
# Check requirements
cordova requirements
# Get help
cordova help
cordova help platform
# Check version
cordova --version
cordova -v
# Get info about project
cordova info
# List available templates
cordova template list
```### 정보 명령어
```bash
# Add Android platform
cordova platform add android
# Build for Android
cordova build android
# Run on Android emulator
cordova emulate android
# Run on Android device
cordova run android --device
# Build release APK
cordova build android --release
# Build with specific API level
cordova build android --gradleArg=-PcdvBuildToolsVersion=28.0.3
# Clean Android build
cordova clean android
```### Android 플랫폼
```bash
# Add iOS platform (macOS only)
cordova platform add ios
# Build for iOS
cordova build ios
# Run on iOS simulator
cordova emulate ios
# Run on iOS device
cordova run ios --device
# Build for specific device
cordova build ios --device
# Build with provisioning profile
cordova build ios --codeSignIdentity="iPhone Developer" --provisioningProfile="UUID"
# Clean iOS build
cordova clean ios
```### iOS 플랫폼
```bash
# Add browser platform
cordova platform add browser
# Run in browser
cordova run browser
# Serve for browser testing
cordova serve
# Build for browser
cordova build browser
```### 브라우저 플랫폼
```bash
# Device Information
cordova plugin add cordova-plugin-device
# Camera
cordova plugin add cordova-plugin-camera
# File System
cordova plugin add cordova-plugin-file
# Network Information
cordova plugin add cordova-plugin-network-information
# Geolocation
cordova plugin add cordova-plugin-geolocation
# Contacts
cordova plugin add cordova-plugin-contacts
# Media
cordova plugin add cordova-plugin-media
# File Transfer
cordova plugin add cordova-plugin-file-transfer
# InAppBrowser
cordova plugin add cordova-plugin-inappbrowser
# Dialogs
cordova plugin add cordova-plugin-dialogs
# Vibration
cordova plugin add cordova-plugin-vibration
# Battery Status
cordova plugin add cordova-plugin-battery-status
# Splash Screen
cordova plugin add cordova-plugin-splashscreen
# Status Bar
cordova plugin add cordova-plugin-statusbar
# Whitelist
cordova plugin add cordova-plugin-whitelist
```### 코어 플러그인
```javascript
// Device Plugin
document.addEventListener("deviceready", function() {
console.log("Device Model: " + device.model);
console.log("Device Platform: " + device.platform);
console.log("Device Version: " + device.version);
console.log("Device UUID: " + device.uuid);
console.log("Device Cordova: " + device.cordova);
}, false);
// Camera Plugin
function takePicture() {
var options = {
quality: 75,
destinationType: Camera.DestinationType.FILE_URI,
sourceType: Camera.PictureSourceType.CAMERA,
encodingType: Camera.EncodingType.JPEG,
targetWidth: 300,
targetHeight: 300
};
navigator.camera.getPicture(onSuccess, onFail, options);
function onSuccess(imageURI) {
var image = document.getElementById('myImage');
image.src = imageURI;
}
function onFail(message) {
alert('Failed because: ' + message);
}
}
// Geolocation Plugin
function getCurrentPosition() {
var options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
navigator.geolocation.getCurrentPosition(onSuccess, onError, options);
function onSuccess(position) {
console.log('Latitude: ' + position.coords.latitude);
console.log('Longitude: ' + position.coords.longitude);
console.log('Altitude: ' + position.coords.altitude);
console.log('Accuracy: ' + position.coords.accuracy);
console.log('Timestamp: ' + position.timestamp);
}
function onError(error) {
alert('code: ' + error.code + '\n' + 'message: ' + error.message);
}
}
// File Plugin
function writeFile() {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs) {
fs.root.getFile("test.txt", {create: true, exclusive: false}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.onwriteend = function() {
console.log("Successful file write...");
};
fileWriter.onerror = function(e) {
console.log("Failed file write: " + e.toString());
};
var dataObj = new Blob(['some file data'], { type: 'text/plain' });
fileWriter.write(dataObj);
});
}, onErrorCreateFile);
}, onErrorLoadFs);
}
// Network Information Plugin
function checkConnection() {
var networkState = navigator.connection.type;
var states = {};
states[Connection.UNKNOWN] = 'Unknown connection';
states[Connection.ETHERNET] = 'Ethernet connection';
states[Connection.WIFI] = 'WiFi connection';
states[Connection.CELL_2G] = 'Cell 2G connection';
states[Connection.CELL_3G] = 'Cell 3G connection';
states[Connection.CELL_4G] = 'Cell 4G connection';
states[Connection.CELL] = 'Cell generic connection';
states[Connection.NONE] = 'No network connection';
alert('Connection type: ' + states[networkState]);
}
```### 플러그인 사용 예시
Would you like me to continue with the rest of the translation?```xml
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.example.myapp" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>My App</name>
<description>
A sample Apache Cordova application.
</description>
<author email="dev@cordova.apache.org" href="http://cordova.io">
Apache Cordova Team
</author>
<!-- Content source -->
<content src="index.html" />
<!-- Access control -->
<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:*" />
<!-- Platform-specific configurations -->
<platform name="android">
<allow-intent href="market:*" />
<icon density="ldpi" src="res/icon/android/ldpi.png" />
<icon density="mdpi" src="res/icon/android/mdpi.png" />
<icon density="hdpi" src="res/icon/android/hdpi.png" />
<icon density="xhdpi" src="res/icon/android/xhdpi.png" />
<icon density="xxhdpi" src="res/icon/android/xxhdpi.png" />
<icon density="xxxhdpi" src="res/icon/android/xxxhdpi.png" />
<splash density="land-ldpi" src="res/screen/android/splash-land-ldpi.png" />
<splash density="land-mdpi" src="res/screen/android/splash-land-mdpi.png" />
<splash density="land-hdpi" src="res/screen/android/splash-land-hdpi.png" />
<splash density="land-xhdpi" src="res/screen/android/splash-land-xhdpi.png" />
<splash density="land-xxhdpi" src="res/screen/android/splash-land-xxhdpi.png" />
<splash density="land-xxxhdpi" src="res/screen/android/splash-land-xxxhdpi.png" />
<splash density="port-ldpi" src="res/screen/android/splash-port-ldpi.png" />
<splash density="port-mdpi" src="res/screen/android/splash-port-mdpi.png" />
<splash density="port-hdpi" src="res/screen/android/splash-port-hdpi.png" />
<splash density="port-xhdpi" src="res/screen/android/splash-port-xhdpi.png" />
<splash density="port-xxhdpi" src="res/screen/android/splash-port-xxhdpi.png" />
<splash density="port-xxxhdpi" src="res/screen/android/splash-port-xxxhdpi.png" />
</platform>
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
<icon height="57" src="res/icon/ios/icon.png" width="57" />
<icon height="114" src="res/icon/ios/icon@2x.png" width="114" />
<icon height="40" src="res/icon/ios/icon-40.png" width="40" />
<icon height="80" src="res/icon/ios/icon-40@2x.png" width="80" />
<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" />
<icon height="60" src="res/icon/ios/icon-60.png" width="60" />
<icon height="120" src="res/icon/ios/icon-60@2x.png" width="120" />
<icon height="180" src="res/icon/ios/icon-60@3x.png" width="180" />
<icon height="72" src="res/icon/ios/icon-72.png" width="72" />
<icon height="144" src="res/icon/ios/icon-72@2x.png" width="144" />
<icon height="76" src="res/icon/ios/icon-76.png" width="76" />
<icon height="152" src="res/icon/ios/icon-76@2x.png" width="152" />
<icon height="167" src="res/icon/ios/icon-83.5@2x.png" width="167" />
<icon height="29" src="res/icon/ios/icon-small.png" width="29" />
<icon height="58" src="res/icon/ios/icon-small@2x.png" width="58" />
<icon height="87" src="res/icon/ios/icon-small@3x.png" width="87" />
<splash height="1136" src="res/screen/ios/Default-568h@2x~iphone.png" width="640" />
<splash height="1334" src="res/screen/ios/Default-667h.png" width="750" />
<splash height="2208" src="res/screen/ios/Default-736h.png" width="1242" />
<splash height="1242" src="res/screen/ios/Default-Landscape-736h.png" width="2208" />
<splash height="1536" src="res/screen/ios/Default-Landscape@2x~ipad.png" width="2048" />
<splash height="2048" src="res/screen/ios/Default-Landscape@~ipadpro.png" width="2732" />
<splash height="768" src="res/screen/ios/Default-Landscape~ipad.png" width="1024" />
<splash height="2048" src="res/screen/ios/Default-Portrait@2x~ipad.png" width="1536" />
<splash height="2732" src="res/screen/ios/Default-Portrait@~ipadpro.png" width="2048" />
<splash height="1024" src="res/screen/ios/Default-Portrait~ipad.png" width="768" />
<splash height="960" src="res/screen/ios/Default@2x~iphone.png" width="640" />
<splash height="480" src="res/screen/ios/Default~iphone.png" width="320" />
<splash height="2732" src="res/screen/ios/Default@2x~universal~anyany.png" width="2732" />
</platform>
<!-- Preferences -->
<preference name="DisallowOverscroll" value="true" />
<preference name="android-minSdkVersion" value="19" />
<preference name="BackupWebStorage" value="none" />
<preference name="SplashMaintainAspectRatio" value="true" />
<preference name="FadeSplashScreenDuration" value="300" />
<preference name="SplashShowOnlyFirstTime" value="false" />
<preference name="SplashScreen" value="screen" />
<preference name="SplashScreenDelay" value="3000" />
<!-- Plugin configurations -->
<plugin name="cordova-plugin-whitelist" spec="1" />
<plugin name="cordova-plugin-statusbar" spec="2" />
<plugin name="cordova-plugin-device" spec="2" />
<plugin name="cordova-plugin-splashscreen" spec="5" />
<plugin name="cordova-plugin-ionic-webview" spec="^4.0.0" />
<plugin name="cordova-plugin-ionic-keyboard" spec="^2.0.5" />
</widget>
```### 플랫폼별 환경 설정
```xml
<!-- Android-specific preferences -->
<platform name="android">
<preference name="android-minSdkVersion" value="19" />
<preference name="android-targetSdkVersion" value="28" />
<preference name="android-installLocation" value="auto" />
<preference name="Orientation" value="portrait" />
<preference name="Fullscreen" value="false" />
<preference name="KeepRunning" value="true" />
<preference name="LoadUrlTimeoutValue" value="20000" />
<preference name="SplashScreen" value="splash" />
<preference name="SplashScreenDelay" value="3000" />
<preference name="InAppBrowserStorageEnabled" value="true" />
<preference name="LoadingDialog" value="My Title,My Message" />
<preference name="ErrorUrl" value="myErrorPage.html" />
<preference name="ShowTitle" value="true" />
<preference name="LogLevel" value="VERBOSE" />
</platform>
<!-- iOS-specific preferences -->
<platform name="ios">
<preference name="CordovaWebViewEngine" value="CDVUIWebViewEngine" />
<preference name="MinimumOSVersion" value="10.0" />
<preference name="AllowInlineMediaPlayback" value="false" />
<preference name="BackupWebStorage" value="none" />
<preference name="TopActivityIndicator" value="gray" />
<preference name="EnableViewportScale" value="false" />
<preference name="KeyboardDisplayRequiresUserAction" value="true" />
<preference name="SuppressesIncrementalRendering" value="false" />
<preference name="SuppressesLongPressGesture" value="false" />
<preference name="Suppresses3DTouchGesture" value="false" />
<preference name="GapBetweenPages" value="0" />
<preference name="PageLength" value="0" />
<preference name="PaginationBreakingMode" value="page" />
<preference name="PaginationMode" value="unpaginated" />
</platform>
```## 디바이스 API
```javascript
// Take picture from camera
function capturePhoto() {
var options = {
quality: 50,
destinationType: Camera.DestinationType.FILE_URI,
sourceType: Camera.PictureSourceType.CAMERA,
encodingType: Camera.EncodingType.JPEG,
targetWidth: 300,
targetHeight: 300,
mediaType: Camera.MediaType.PICTURE,
allowEdit: true,
correctOrientation: true
};
navigator.camera.getPicture(onPhotoSuccess, onPhotoError, options);
}
// Select picture from gallery
function selectPhoto() {
var options = {
quality: 50,
destinationType: Camera.DestinationType.FILE_URI,
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
mediaType: Camera.MediaType.PICTURE,
allowEdit: true,
encodingType: Camera.EncodingType.JPEG,
targetWidth: 300,
targetHeight: 300
};
navigator.camera.getPicture(onPhotoSuccess, onPhotoError, options);
}
function onPhotoSuccess(imageURI) {
var image = document.getElementById('myImage');
image.src = imageURI;
}
function onPhotoError(message) {
alert('Failed because: ' + message);
}
// Cleanup camera resources
function cleanup() {
navigator.camera.cleanup(onSuccess, onFail);
function onSuccess() {
console.log("Camera cleanup success.")
}
function onFail(message) {
alert('Failed because: ' + message);
}
}
```### 카메라 API
```javascript
// Get current position
function getCurrentLocation() {
var options = {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 60000
};
navigator.geolocation.getCurrentPosition(onLocationSuccess, onLocationError, options);
}
// Watch position changes
var watchID = null;
function watchLocation() {
var options = {
enableHighAccuracy: true,
timeout: 30000,
maximumAge: 60000
};
watchID = navigator.geolocation.watchPosition(onLocationSuccess, onLocationError, options);
}
function stopWatching() {
if (watchID != null) {
navigator.geolocation.clearWatch(watchID);
watchID = null;
}
}
function onLocationSuccess(position) {
var element = document.getElementById('geolocation');
element.innerHTML = 'Latitude: ' + position.coords.latitude + '<br />' +
'Longitude: ' + position.coords.longitude + '<br />' +
'Altitude: ' + position.coords.altitude + '<br />' +
'Accuracy: ' + position.coords.accuracy + '<br />' +
'Altitude Accuracy: ' + position.coords.altitudeAccuracy + '<br />' +
'Heading: ' + position.coords.heading + '<br />' +
'Speed: ' + position.coords.speed + '<br />' +
'Timestamp: ' + position.timestamp + '<br />';
}
function onLocationError(error) {
alert('code: ' + error.code + '\n' + 'message: ' + error.message + '\n');
}
```### 지리 위치 API
```javascript
// Request file system
function requestFileSystem() {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onFileSystemSuccess, onFileSystemError);
}
function onFileSystemSuccess(fileSystem) {
console.log(fileSystem.name);
console.log(fileSystem.root.name);
}
function onFileSystemError(error) {
console.log(error.code);
}
// Create file
function createFile() {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs) {
fs.root.getFile("newFile.txt", {create: true, exclusive: false}, function(fileEntry) {
console.log("File created: " + fileEntry.fullPath);
}, onErrorCreateFile);
}, onErrorLoadFs);
}
// Write to file
function writeToFile() {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs) {
fs.root.getFile("newFile.txt", {create: true, exclusive: false}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.onwriteend = function() {
console.log("Successful file write...");
readFile();
};
fileWriter.onerror = function(e) {
console.log("Failed file write: " + e.toString());
};
var dataObj = new Blob(['some file data'], { type: 'text/plain' });
fileWriter.write(dataObj);
});
}, onErrorCreateFile);
}, onErrorLoadFs);
}
// Read file
function readFile() {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs) {
fs.root.getFile("newFile.txt", {}, function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function() {
console.log("Successful file read: " + this.result);
displayFileData(fileEntry.fullPath + ": " + this.result);
};
reader.readAsText(file);
}, onErrorReadFile);
}, onErrorCreateFile);
}, onErrorLoadFs);
}
// Delete file
function deleteFile() {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs) {
fs.root.getFile("newFile.txt", {create: false}, function(fileEntry) {
fileEntry.remove(function() {
console.log("File deleted");
}, onErrorDeleteFile);
}, onErrorCreateFile);
}, onErrorLoadFs);
}
function onErrorLoadFs(error) {
console.log(error.code);
}
function onErrorCreateFile(error) {
console.log(error.code);
}
function onErrorReadFile(error) {
console.log(error.code);
}
function onErrorDeleteFile(error) {
console.log(error.code);
}
function displayFileData(data) {
document.getElementById("fileData").innerHTML = data;
}
```### 파일 시스템 API
```javascript
// Create contact
function createContact() {
var contact = navigator.contacts.create();
contact.displayName = "Plumber";
contact.nickname = "Plumber";
var name = new ContactName();
name.givenName = "Jane";
name.familyName = "Doe";
contact.name = name;
var phoneNumbers = [];
phoneNumbers[0] = new ContactField('work', '212-555-1234', false);
phoneNumbers[1] = new ContactField('mobile', '917-555-5432', true);
contact.phoneNumbers = phoneNumbers;
var emails = [];
emails[0] = new ContactField('work', 'jane_doe@company.com', false);
contact.emails = emails;
contact.save(onContactSaveSuccess, onContactSaveError);
}
function onContactSaveSuccess(contact) {
alert("Save Success");
}
function onContactSaveError(contactError) {
alert("Error = " + contactError.code);
}
// Find contacts
function findContacts() {
var options = new ContactFindOptions();
options.filter = "Bob";
options.multiple = true;
var fields = ["displayName", "name"];
navigator.contacts.find(fields, onContactFindSuccess, onContactFindError, options);
}
function onContactFindSuccess(contacts) {
for (var i = 0; i < contacts.length; i++) {
console.log("Display Name = " + contacts[i].displayName);
}
}
function onContactFindError(contactError) {
alert('onError!');
}
// Clone contact
function cloneContact() {
var clone = contact.clone();
clone.name.givenName = "John";
clone.save(onContactSaveSuccess, onContactSaveError);
}
// Remove contact
function removeContact() {
contact.remove(onContactRemoveSuccess, onContactRemoveError);
}
function onContactRemoveSuccess() {
alert("Removal Success");
}
function onContactRemoveError(contactError) {
alert("Error = " + contactError.code);
}
```### 연락처 API
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery Mobile App</title>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css">
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
</head>
<body>
<div data-role="page" id="home">
<div data-role="header">
<h1>My App</h1>
</div>
<div data-role="content">
<ul data-role="listview" data-inset="true">
<li><a href="#page2">Page 2</a></li>
<li><a href="#page3">Page 3</a></li>
</ul>
<button onclick="takePicture()">Take Picture</button>
</div>
<div data-role="footer">
<h4>Footer</h4>
</div>
</div>
<div data-role="page" id="page2">
<div data-role="header">
<a href="#home" data-icon="arrow-l">Back</a>
<h1>Page 2</h1>
</div>
<div data-role="content">
<p>This is page 2</p>
</div>
</div>
<script src="cordova.js"></script>
<script src="js/index.js"></script>
</body>
</html>
```## UI 프레임워크
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>Framework7 App</title>
<link rel="stylesheet" href="lib/framework7/css/framework7.bundle.min.css">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<div id="app">
<div class="view view-main view-init" data-url="/">
<div class="page" data-name="home">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="title">My App</div>
</div>
</div>
<div class="page-content">
<div class="block">
<p>Welcome to my Cordova app with Framework7!</p>
<a class="button button-fill" onclick="takePicture()">Take Picture</a>
</div>
</div>
</div>
</div>
</div>
<script src="lib/framework7/js/framework7.bundle.min.js"></script>
<script src="cordova.js"></script>
<script src="js/app.js"></script>
</body>
</html>
```### jQuery Mobile 통합
```bash
# Install Ionic CLI
npm install -g @ionic/cli
# Create Ionic Cordova project
ionic start myApp tabs --type=angular --cordova
# Add platforms
ionic cordova platform add android
ionic cordova platform add ios
# Add plugins
ionic cordova plugin add cordova-plugin-camera
# Build and run
ionic cordova build android
ionic cordova run android
```### Framework7 통합
```bash
# Install live reload plugin
cordova plugin add cordova-plugin-browsersync
# Run with live reload
cordova run android --live-reload
cordova run ios --live-reload
# Serve for browser development
cordova serve
cordova serve --port=8080
# Use browser platform for quick testing
cordova platform add browser
cordova run browser
```### Ionic 프레임워크 통합
```bash
# Install CodePush plugin
cordova plugin add cordova-plugin-code-push
# Configure CodePush
# Add to config.xml:
# <preference name="CodePushDeploymentKey" value="YOUR_DEPLOYMENT_KEY" />
# Release update
code-push release-cordova myApp-Android www 1.0.0
code-push release-cordova myApp-iOS www 1.0.0
```## 개발 워크플로우
```javascript
// hooks/before_build/010_add_platform_class.js
module.exports = function(context) {
var fs = require('fs');
var path = require('path');
var platformsMap = {
'android': 'android',
'ios': 'ios',
'browser': 'browser'
};
var platforms = context.opts.cordova.platforms;
platforms.forEach(function(platform) {
var indexPath = path.join(context.opts.projectRoot, 'www', 'index.html');
var indexContent = fs.readFileSync(indexPath, 'utf8');
// Add platform-specific class to body
var platformClass = platformsMap[platform];
if (platformClass) {
indexContent = indexContent.replace(
/<body([^>]*)>/,
'<body$1 class="platform-' + platformClass + '">'
);
fs.writeFileSync(indexPath, indexContent);
}
});
};
```### 라이브 리로드
```javascript
// js/config.js
var Config = {
development: {
apiUrl: 'http://localhost:3000/api',
debug: true
},
production: {
apiUrl: 'https://api.myapp.com',
debug: false
}
};
// Detect environment
var environment = 'development';
if (window.location.protocol === 'file:') {
environment = 'production';
}
var config = Config[environment];
// Usage
console.log('API URL:', config.apiUrl);
console.log('Debug mode:', config.debug);
```### 핫 코드 푸시
```bash
# Build debug version
cordova build android
cordova build ios
# Run debug on device
cordova run android --device --debug
cordova run ios --device --debug
# Enable debugging
cordova build android --debug
cordova build ios --debug
```### 빌드 훅
```bash
# Build release version
cordova build android --release
cordova build ios --release
# Sign Android APK
# Generate keystore
keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
# Sign APK
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk alias_name
# Align APK
zipalign -v 4 platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk MyApp.apk
```### 환경 설정
```javascript
// test/spec/test.js
describe('App Tests', function() {
beforeEach(function(done) {
document.addEventListener('deviceready', done, false);
});
it('should have device plugin', function() {
expect(window.device).toBeDefined();
expect(device.platform).toBeDefined();
});
it('should have camera plugin', function() {
expect(navigator.camera).toBeDefined();
expect(navigator.camera.getPicture).toBeDefined();
});
it('should take picture', function(done) {
var options = {
quality: 50,
destinationType: Camera.DestinationType.FILE_URI,
sourceType: Camera.PictureSourceType.CAMERA
};
navigator.camera.getPicture(
function(imageURI) {
expect(imageURI).toBeDefined();
done();
},
function(error) {
fail('Camera error: ' + error);
done();
},
options
);
});
});
// Run tests with Jasmine
// Include jasmine.js and jasmine-html.js in test.html
```## 빌드 및 테스트
```bash
# Chrome DevTools for Android
# 1. Enable USB debugging on Android device
# 2. Connect device to computer
# 3. Open Chrome and go to chrome://inspect
# 4. Select your app from the list
# Safari Web Inspector for iOS
# 1. Enable Web Inspector on iOS device (Settings > Safari > Advanced)
# 2. Connect device to Mac
# 3. Open Safari > Develop > [Device Name] > [App Name]
# Weinre (Web Inspector Remote)
npm install -g weinre
weinre --boundHost 0.0.0.0 --httpPort 8080
# Add to index.html:
# <script src="http://YOUR_IP:8080/target/target-script-min.js#anonymous"></script>
```### 디버그 빌드
```javascript
// Enhanced console logging
var Logger = {
log: function(message, data) {
if (window.console) {
console.log('[LOG] ' + message, data || '');
}
this.writeToFile('LOG', message, data);
},
error: function(message, error) {
if (window.console) {
console.error('[ERROR] ' + message, error || '');
}
this.writeToFile('ERROR', message, error);
},
warn: function(message, data) {
if (window.console) {
console.warn('[WARN] ' + message, data || '');
}
this.writeToFile('WARN', message, data);
},
writeToFile: function(level, message, data) {
// Write logs to file for later analysis
var logEntry = new Date().toISOString() + ' [' + level + '] ' + message;
if (data) {
logEntry += ' ' + JSON.stringify(data);
}
// Implementation depends on file plugin
// this.appendToLogFile(logEntry);
}
};
// Usage
Logger.log('App started');
Logger.error('Network error', error);
Logger.warn('Low battery', batteryLevel);
```### 릴리즈 빌드
```javascript
// Global error handler
window.onerror = function(message, source, lineno, colno, error) {
Logger.error('Global error: ' + message, {
source: source,
line: lineno,
column: colno,
error: error
});
// Send error to analytics service
// Analytics.trackError(message, source, lineno);
return true; // Prevent default browser error handling
};
// Unhandled promise rejection handler
window.addEventListener('unhandledrejection', function(event) {
Logger.error('Unhandled promise rejection', event.reason);
event.preventDefault();
});
// Cordova-specific error handling
document.addEventListener('deviceready', function() {
// Handle plugin errors
if (window.plugins && window.plugins.toast) {
window.plugins.toast.showShortTop = function(message) {
try {
window.plugins.toast.show(message, 'short', 'top');
} catch (error) {
Logger.error('Toast plugin error', error);
alert(message); // Fallback
}
};
}
}, false);
```### 자동화된 테스트
```javascript
// Efficient DOM manipulation
var DOMUtils = {
createElement: function(tag, attributes, content) {
var element = document.createElement(tag);
if (attributes) {
for (var attr in attributes) {
element.setAttribute(attr, attributes[attr]);
}
}
if (content) {
element.innerHTML = content;
}
return element;
},
removeElement: function(element) {
if (element && element.parentNode) {
element.parentNode.removeChild(element);
}
},
clearElement: function(element) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
};
// Memory-efficient list rendering
var ListView = {
render: function(container, items, itemRenderer) {
// Clear existing content
DOMUtils.clearElement(container);
// Use document fragment for efficient DOM updates
var fragment = document.createDocumentFragment();
items.forEach(function(item, index) {
var itemElement = itemRenderer(item, index);
fragment.appendChild(itemElement);
});
container.appendChild(fragment);
},
renderVirtual: function(container, items, itemRenderer, visibleCount) {
// Virtual scrolling for large lists
var startIndex = 0;
var endIndex = Math.min(visibleCount, items.length);
DOMUtils.clearElement(container);
for (var i = startIndex; i < endIndex; i++) {
var itemElement = itemRenderer(items[i], i);
container.appendChild(itemElement);
}
}
};
```## 디버깅
```javascript
// Lazy loading images
var ImageLoader = {
loadImage: function(src, callback) {
var img = new Image();
img.onload = function() {
callback(null, img);
};
img.onerror = function() {
callback(new Error('Failed to load image: ' + src));
};
img.src = src;
},
resizeImage: function(file, maxWidth, maxHeight, quality, callback) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = function() {
var ratio = Math.min(maxWidth / img.width, maxHeight / img.height);
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
canvas.toBlob(callback, 'image/jpeg', quality);
};
img.src = URL.createObjectURL(file);
},
preloadImages: function(urls, callback) {
var loaded = 0;
var total = urls.length;
var images = [];
if (total === 0) {
callback(null, images);
return;
}
urls.forEach(function(url, index) {
this.loadImage(url, function(error, img) {
if (error) {
callback(error);
return;
}
images[index] = img;
loaded++;
if (loaded === total) {
callback(null, images);
}
});
}.bind(this));
}
};
```### 네트워크 최적화
```javascript
// Network request manager
var NetworkManager = {
cache: {},
request: function(url, options, callback) {
options = options || {};
// Check cache first
if (options.cache && this.cache[url]) {
callback(null, this.cache[url]);
return;
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
var response = xhr.responseText;
try {
response = JSON.parse(response);
} catch (e) {
// Response is not JSON
}
// Cache successful responses
if (options.cache) {
this.cache[url] = response;
}
callback(null, response);
} else {
callback(new Error('HTTP ' + xhr.status + ': ' + xhr.statusText));
}
}
}.bind(this);
xhr.open(options.method || 'GET', url);
// Set headers
if (options.headers) {
for (var header in options.headers) {
xhr.setRequestHeader(header, options.headers[header]);
}
}
xhr.send(options.body || null);
},
clearCache: function() {
this.cache = {};
},
// Batch requests
batch: function(requests, callback) {
var completed = 0;
var results = [];
var hasError = false;
requests.forEach(function(request, index) {
this.request(request.url, request.options, function(error, response) {
if (hasError) return;
if (error) {
hasError = true;
callback(error);
return;
}
results[index] = response;
completed++;
if (completed === requests.length) {
callback(null, results);
}
});
}.bind(this));
}
};
```## 보안
### 콘텐츠 보안 정책
```html
<!-- Add to index.html -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval';
style-src 'self' 'unsafe-inline';
media-src *;
img-src 'self' data: content:;
connect-src 'self' https://api.myapp.com;
script-src 'self' 'unsafe-inline' 'unsafe-eval';
">
```### 안전한 저장소
```javascript
// Secure data storage
var SecureStorage = {
encrypt: function(data, key) {
// Use a proper encryption library like CryptoJS
return CryptoJS.AES.encrypt(JSON.stringify(data), key).toString();
},
decrypt: function(encryptedData, key) {
try {
var bytes = CryptoJS.AES.decrypt(encryptedData, key);
var decryptedData = bytes.toString(CryptoJS.enc.Utf8);
return JSON.parse(decryptedData);
} catch (error) {
throw new Error('Failed to decrypt data');
}
},
store: function(key, data, password) {
var encryptedData = this.encrypt(data, password);
localStorage.setItem(key, encryptedData);
},
retrieve: function(key, password) {
var encryptedData = localStorage.getItem(key);
if (!encryptedData) {
return null;
}
return this.decrypt(encryptedData, password);
},
remove: function(key) {
localStorage.removeItem(key);
}
};
// Usage
SecureStorage.store('userToken', { token: 'abc123' }, 'myPassword');
var userData = SecureStorage.retrieve('userToken', 'myPassword');
```### 입력 검증
```javascript
// Input validation utilities
var Validator = {
email: function(email) {
var regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
},
phone: function(phone) {
var regex = /^\+?[\d\s\-\(\)]+$/;
return regex.test(phone) && phone.replace(/\D/g, '').length >= 10;
},
url: function(url) {
try {
new URL(url);
return true;
} catch (error) {
return false;
}
},
sanitize: function(input) {
return input.replace(/[<>'"&]/g, function(match) {
var entities = {
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'&': '&'
};
return entities[match];
});
},
length: function(input, min, max) {
return input.length >= min && input.length <= max;
},
required: function(input) {
return input !== null && input !== undefined && input.toString().trim() !== '';
}
};
// Form validation
var FormValidator = {
validate: function(form, rules) {
var errors = {};
for (var field in rules) {
var value = form[field];
var fieldRules = rules[field];
for (var rule in fieldRules) {
var ruleValue = fieldRules[rule];
var isValid = false;
switch (rule) {
case 'required':
isValid = ruleValue ? Validator.required(value) : true;
break;
case 'email':
isValid = ruleValue ? Validator.email(value) : true;
break;
case 'minLength':
isValid = value ? value.length >= ruleValue : true;
break;
case 'maxLength':
isValid = value ? value.length <= ruleValue : true;
break;
}
if (!isValid) {
errors[field] = errors[field] || [];
errors[field].push(rule);
}
}
}
return {
isValid: Object.keys(errors).length === 0,
errors: errors
};
}
};
// Usage
var formData = {
email: 'user@example.com',
password: '123456',
name: 'John Doe'
};
var rules = {
email: { required: true, email: true },
password: { required: true, minLength: 6 },
name: { required: true, maxLength: 50 }
};
var validation = FormValidator.validate(formData, rules);
if (!validation.isValid) {
console.log('Validation errors:', validation.errors);
}
```## 배포
### 앱스토어 배포
```bash
# iOS App Store
# 1. Build release version
cordova build ios --release
# 2. Open Xcode project
open platforms/ios/MyApp.xcworkspace
# 3. Configure signing and provisioning
# 4. Archive and upload to App Store Connect
# Google Play Store
# 1. Generate signed APK
cordova build android --release
# 2. Sign APK with keystore
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk alias_name
# 3. Align APK
zipalign -v 4 platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk MyApp.apk
# 4. Upload to Google Play Console
```### 기업 배포
```bash
# iOS Enterprise Distribution
# 1. Build with enterprise certificate
cordova build ios --release
# 2. Create IPA file
# 3. Host on internal server with manifest.plist
# Android Enterprise Distribution
# 1. Build signed APK
cordova build android --release
# 2. Distribute through MDM or direct download
```### 지속적 통합
```yaml
# .github/workflows/build.yml
name: Build App
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm install
- name: Install Cordova
run: npm install -g cordova
- name: Add platforms
run: |
cordova platform add android
cordova platform add ios
- name: Build Android
run: cordova build android
- name: Build iOS
run: cordova build ios
- name: Run tests
run: npm test
- name: Upload artifacts
uses: actions/upload-artifact@v2
with:
name: builds
path: platforms/*/build/
```## 모범 사례
### 프로젝트 구성
src/ ├── js/ │ ├── app.js # Main application logic │ ├── config.js # Configuration │ ├── utils.js # Utility functions │ ├── services/ # API services │ ├── models/ # Data models │ └── views/ # View controllers ├── css/ │ ├── app.css # Main styles │ ├── components/ # Component styles │ └── themes/ # Theme styles ├── templates/ # HTML templates ├── assets/ │ ├── images/ │ ├── fonts/ │ └── sounds/ └── tests/ # Test files
```javascript
// Use strict mode
'use strict';
// Namespace your code
var MyApp = MyApp || {};
MyApp.Utils = {
// Utility functions
};
MyApp.Services = {
// Service functions
};
MyApp.Views = {
// View controllers
};
// Use consistent error handling
MyApp.ErrorHandler = {
handle: function(error, context) {
console.error('Error in ' + context + ':', error);
// Log to analytics service
if (MyApp.Analytics) {
MyApp.Analytics.trackError(error, context);
}
// Show user-friendly message
if (navigator.notification) {
navigator.notification.alert(
'An error occurred. Please try again.',
null,
'Error',
'OK'
);
} else {
alert('An error occurred. Please try again.');
}
}
};
// Use consistent coding style
var MyApp = {
init: function() {
this.bindEvents();
this.setupUI();
},
bindEvents: function() {
document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
document.addEventListener('pause', this.onPause.bind(this), false);
document.addEventListener('resume', this.onResume.bind(this), false);
},
onDeviceReady: function() {
console.log('Device ready');
this.setupPlugins();
},
onPause: function() {
console.log('App paused');
this.saveState();
},
onResume: function() {
console.log('App resumed');
this.restoreState();
},
setupPlugins: function() {
// Initialize plugins
},
setupUI: function() {
// Setup user interface
},
saveState: function() {
// Save application state
},
restoreState: function() {
// Restore application state
}
};
```### 성능 지침
```javascript
// Optimize for mobile performance
var PerformanceOptimizer = {
// Debounce function calls
debounce: function(func, wait) {
var timeout;
return function() {
var context = this;
var args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
func.apply(context, args);
}, wait);
};
},
// Throttle function calls
throttle: function(func, limit) {
var inThrottle;
return function() {
var args = arguments;
var context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(function() {
inThrottle = false;
}, limit);
}
};
},
// Optimize scroll events
optimizeScroll: function(element, callback) {
var ticking = false;
function update() {
callback();
ticking = false;
}
function requestTick() {
if (!ticking) {
requestAnimationFrame(update);
ticking = true;
}
}
element.addEventListener('scroll', requestTick);
},
// Lazy load content
lazyLoad: function(elements, callback) {
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
callback(entry.target);
observer.unobserve(entry.target);
}
});
});
elements.forEach(function(element) {
observer.observe(element);
});
}
};
요약
Apache Cordova/PhoneGap은 하이브리드 모바일 앱 개발을 위한 강력한 플랫폼을 제공합니다:
장점:
- 크로스 플랫폼: 한 번 작성하면 여러 플랫폼에서 실행
- 웹 기술: 익숙한 HTML, CSS, JavaScript 사용
- 플러그인 생태계: 플러그인을 통해 네이티브 기기 기능 접근
- 빠른 개발: 네이티브 앱보다 빠른 개발 주기
- 비용 효율적: 여러 플랫폼을 위한 단일 코드베이스
최적의 사용 사례:
- 콘텐츠 중심 앱
- 비즈니스 애플리케이션
- 프로토타입 및 MVP
- 간단한 UI 요구사항이 있는 앱
- 제한된 네이티브 기능이 필요한 크로스 플랫폼 앱
고려사항:
- 네이티브 앱보다 성능이 느릴 수 있음
- 최신 플랫폼 기능에 대한 제한된 접근
- UI가 완전히 네이티브하지 않을 수 있음
- 원활한 성능을 위해 신중한 최적화 필요
Cordova는 개발 속도, 비용, 크로스 플랫폼 호환성이 최대 성능과 네이티브 플랫폼 통합보다 중요한 많은 모바일 앱 프로젝트에 여전히 유효한 옵션입니다.