Windows Phone 8.1

OpenFramework를 Windows Universal App에서 사용해보기

최근에 OpenFramework(이하 OF)가 Windows Universal App에서도 사용할 수 있게 되었다. 직접 한번 간단히 해본 내용을 공유한다.

아직 나온지가 얼마 안 되서 그런지 친절한 튜토리얼은 없었다. MS오픈테크의 공지를 읽어 보니까 projectGenerator라고 하는 OF의 프로젝트 생성 툴이 있는데 이를 Universal App 프로젝트를 만드는 버전으로 포팅했다는 내용이 있다.

따라서, 해당 공지에 있는 GitHub의 OF 프로젝트를 Clone해봤다.

image

GitHub for Windows를 설치한 상태에서 Clone in Desktop을 클릭하여 프로젝트를 로컬로 Clone한다.

image

openFrameworks 프로젝트를 선택하고 Branch를 universal로 변경한다.

 

image 

Clone 된 폴더를 열고, projectGenerator라는 하위 폴더에 들어가면 projectGeneratorSimple_winrt.exe라는 실행파일을 찾을 수 있다.

이 파일을 실행하면 다음과 같은 GUI 툴이 나온다.

 

image

Name을 적당히 변경하고 GENERATE PROJECT 버튼을 눌러서 프로젝트를 생성한다.

image 

솔루션 파일을 열면 다음과 같은 구조로 되어 있다.

image

이 중에 ofApp.cpp가 openFramework를 사용하는 코드가 들어가는 파일이다. 현재는 아무런 코드가 작성되어 있지 않기 때문에 실행하면 회색 화면만 보인다.

image

이제부터는 openFramework.org의 튜토리얼을 따라서 ofApp.cpp나 opApp.h를 수정하면 된다.

image

ofApp.cpp 파일은 위와 같이 미리 정해진 메서드가 있고, 해당 메서드의 내용을 작성하면 되는 구조이다. setup()은 처음에 한번만 실행되고, draw()는 프레임 단위로 화면을 갱신하고, update()는 반복 실행하는 코드를 넣으면 된다.

여러 가지 튜토리얼 중 파티클 시스템을 따라해 봤다. 마우스의 클릭에 따라 원이 따라 움직이는 예제이다.

image

간단히 코드를 살펴보면 위와 같이 of로 시작하는 기본 제공 함수들을 이용하여 마우스 위치(mouseX, mouseY)를 가져와서, 색상을 정하고(ofBackgroundGradient(…), ofSetColor(…), ofFill() 등), 원을 그려 넣는 등(ofCircle(…)) 쉽게 구현을 해볼 수 있게 되어 있다. Shared 프로젝트에 ofApp.cpp 파일이 있어서 윈도우/윈도우폰 프로젝트 설정만 바꾸면 동일하게 실행되는 것을 확인할 수 있다.

_968

_969

간단히 openFramework가 유니버설 앱에서 동작하는 것을 확인하였는데, OpenGL이나 OpenCV 등도 동작하는지는 좀 더 살펴보아야 할 것 같다.

Advertisements

Xamarin 앱 개발 테스트 #6

지난 #5에서 PCL과 Shared 프로젝트를 통해서 C# 코드를 공유하는 방법을 알아보았다.

이번에는 UI 구현을 통해서 Windows Phone과 유사하게 동작하는 Xamarin.Android 앱을 만들어 보고자 한다. Android 앱 개발을 잘 모르다 보니, Android UI 모델과 Xamarin.Android를 동시에 공부해야 해서 조금 어려운 부분은 있었지만 결과적으로 포팅은 큰 어려움 없이 성공적으로 진행되었다.

다음은 Xamarin.Android의 MainActivity.cs 파일이다.

    public class MainActivity : Activity
    {
        int count = 1;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            
            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            string searchTerm = "flowers";

            SearchFlickr(searchTerm);
        }

        async void SearchFlickr(string searchTerm)
        {
            List<FlickrPhotoResult> results = await FlickrSearcher.SearchAsync(searchTerm);
            var gridview = FindViewById<GridView>(Resource.Id.gridview);
            gridview.Adapter = new ImageAdapter(this, results);
        }
    }

Main.axml 파일에 GridView를 하나 만들어 놓고, 여기에 Adapter를 이용해서 FlickrSearcher에서 받은 List 데이터를 넘겨주고 있다. 같은 부분의 Windows Phone 코드를 살펴보면 다음과 같다.

        async void SearchFlickr(string searchTerm)
        {
            List<FlickrPhotoResult> results = await FlickrSearcher.SearchAsync(searchTerm);

            this.DataContext = results;

            ...
        }

Windows Phone에서는 XAML 페이지의 DataContext로 지정하고, GridView의 ItemsSource에 데이터 바인딩을 통해서 XAML의 각 오브젝트에서 처리를 하게 된다.

<GridView x:Name="gridView" ItemsSource="{Binding}" Grid.Row="2" Margin="10,0,0,0">
            <GridView.ItemTemplate>
                <DataTemplate>
                    <Grid HorizontalAlignment="Left" Width="460" Height="300">
                        <Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}">
                            <Image Source="{Binding ImageUrl}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
                        </Border>
                        <StackPanel VerticalAlignment="Bottom" Background="{ThemeResource ListViewItemOverlayBackgroundThemeBrush}">
                            <TextBlock Text="{Binding Title}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" Height="60" Margin="15,0,15,0"/>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>

처음에는 이 개념이 이해하기 어려운데, 막상 데이터 바인딩 없이 Android UI 개발을 하려고 하니 머리가 더 복잡해지는 느낌이다.

어쨌든 Android에서는 데이터 바인딩 대신 Adapter를 만들어서 UI에 데이터를 뿌려 주어야 한다. Adapter는 BaseAdapter를 상속하여 클래스로 직접 구현해야 하는데, 아래와 같이 구현해 보았다.

using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Views;
using Android.Widget;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;

namespace FlickrSearch.Android
{
    class ImageAdapter : BaseAdapter
    {
        Context context;
        List<FlickrPhotoResult> list;
        public ImageAdapter(Context c, List<FlickrPhotoResult> data)
        {
            context = c;
            list = data;
        }

        public override int Count
        {
            get { return list.Count; }
        }

        public override Java.Lang.Object GetItem(int position)
        {
            return null;
        }

        public override long GetItemId(int position)
        {
            return position;
        }

        public override global::Android.Views.View GetView(int position, global::Android.Views.View convertView, global::Android.Views.ViewGroup parent)
        {
            /*
            // 직접 ImageView를 생성해서 전달하는 경우 
            ImageView imageView;
            var item = list[position];

            if (convertView == null)
            {
                imageView = new ImageView(context);
                imageView.LayoutParameters = new AbsListView.LayoutParams(1200,900);
                imageView.SetScaleType(ImageView.ScaleType.CenterCrop);
                //imageView.SetPadding(8,8,8,8);
            }
            else
            {
                imageView = (ImageView)convertView;
            }
            Bitmap bitmap = GetImageBitmapFromUrl(item.ImageUrl);
            imageView.SetImageBitmap(bitmap);
            return imageView;
             */

            // GridViewItem.axml 파일을 이용해서 값만 전달하는 경우
            View row = convertView;
            RecordHolder holder = null;

            if (row == null)
            {
                LayoutInflater inflater = ((Activity)context).LayoutInflater;
                row = inflater.Inflate(Resource.Layout.GridViewItem, parent, false);

                holder = new RecordHolder();
                holder.txtTitle = row.FindViewById<TextView>(Resource.Id.item_text);
                holder.imageItem = row.FindViewById<ImageView>(Resource.Id.item_image);
                row.Tag = holder;
            }
            else
            {
                holder = row.Tag as RecordHolder;
            }
            holder.txtTitle.Text = list[position].Title;
            Bitmap bitmap = GetImageBitmapFromUrl(list[position].ImageUrl);
            holder.imageItem.SetImageBitmap(bitmap);
            return row;
        }

        private Bitmap GetImageBitmapFromUrl(string p)
        {
            Bitmap imageBitmap = null;
            using (var wc = new WebClient())
            {
                var imageBytes = wc.DownloadData(p);
                if (imageBytes != null && imageBytes.Length > 0)
                {
                    imageBitmap = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
                }
            }
            return imageBitmap;
        }
    }

    public class RecordHolder : Java.Lang.Object
    {
        public TextView txtTitle;
        public ImageView imageItem;
    }
}

추상 클래스인 BaseAdapter를 상속하여 구현하는데, VS에서 자동으로 생성해주는 코드에 Count, GetItem, GetItemId, GetView 등 일부 메소드의 구현부만 추가하면 된다. GetView에서 2가지 방식으로 구현을 해보았는데, 주석처리된 위 쪽은 직접 ImageView를 생성하여 이미지를 뿌려주는 것이고, 아래 쪽은 별도의 axml 파일을 생성해서 이를 템플릿처럼 사용하는 것이다. 해당 코드들은 Android와 Xamarin의 문서 및 포럼 글들을 참고로 하였다. 별도로 생성하는 GridViewItem.axml의 내용은 다음과 같다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
  <ImageView 
    android:id="@+id/item_image" 
    android:layout_width="480dp" 
    android:layout_height="360dp" 
    android:layout_marginRight="0dp" > 
  </ImageView> 
  <TextView 
    android:id="@+id/item_text" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:layout_marginTop="0dp" 
    android:textSize="30sp" > 
  </TextView>
</LinearLayout>

Android UI 레퍼런스와 샘플 코드를 찾아보느라 조금 시간이 걸리기는 했지만, 어렵지 않게 구성할 수 있었고 전체적으로 생각보다 매끄럽게 포팅이 진행되었다. 그러나, 에뮬레이터 자체가 느리기도 하고, 코드를 최적화하지 않아서 동작이 버벅거렸다. 특히, 이미지 스크롤 할 때 끊김이 심했는데, 데이터 로딩 코드를 최적화 해야 할 듯 싶다.(Windows Phone에서는 URL만 바인딩 시키면 지가 알아서 퍼포먼스 지장 없도록 로드해와서 뿌려 주는데… 내가 Android를 잘 몰라서 그렇겠지…)

xamarin

Windows Phone(좌), Xamarin.Android(우)

UI가 아주 예쁘게 나오진 않았지만 결과적으로 같은 기능을 가진 앱을 포팅할 수 있었다.

이번 Xamarin 앱 개발 테스트를 통해서 Xamarin으로 크로스플랫폼 앱 개발이 충분히 가능한 시나리오이며, 실제 구현하기 위해서 알아야 하는 기본적인 개념과 테크닉들을 알아볼 수 있었다. 아직 국내에 Xamarin이 잘 알려지지 않았고, 실제로 이 도구를 이용해서 앱을 개발한 사례도 극히 드문 것으로 알고 있다. 국내에서 최근에 유행하고 있는 Unity도 같은 오픈소스 프레임워크를 사용하고 있는데, 이 글을 통해서 더 많은 개발자들이 C#의 다양한 활용 가능성에 대해서 알릴 수 있으면 좋겠다.

계속해서 기회가 된다면 디바이스에 올려서 퍼포먼스 테스트를 해보거나, Xamarin.iOS 쪽도 살펴볼까 싶다.

사용한 소스 코드는 여기서 다운로드할 수 있다.

 

윈도우폰8.1의 새로운 앱 모델

이번 Build 컨퍼런스에서 소개된 새로운 윈도우폰8.1의 앱 모델을 소개한 세션을 리뷰해 보고자 한다. 이번 윈도우폰은 윈도우8.1과 앱을 공유할 수 있는 유니버설 앱이 적용되면서 앱 모델이 새롭게 바뀌었다. 기존의 실버라이트 앱에서 윈도우8.1 앱과 유사한 형태의 앱 모델로 변경되었는데 이 세션에서는 좀 더 자세한 내용을 살펴본다.

image

윈도우와 윈도우폰은 이제 개발 플랫폼과 앱 서비스에서 많은 부분을 공유하고 있다. 앱 배포 형식(appx)을 공유하고, Push 서비스(WNS)를 공유하고, 앱 아이덴터티와 권한 등도 공유한다.

image

윈도우폰에서만 되는 것은 실버라이트 앱이다. 기존에 굉장히 많은 윈도우폰 앱이 실버라이트 기술로 만들어져 있다. 기존 앱과의 호환성을 제공한다.

image

가장 많은 시간을 투자한 부분은 저사양 디바이스 지원, 멀티태스킹, 패키징 및 배포, 백업 및 로밍, 앱 데이터 공유 등이며, UX 측면에서도 액션 센터나 공유된 푸시 알림 등이 개선되었다. 저사양 폰 중에 가장 많이 팔린 디바이스인 520과 같은 윈도우폰은 512MB의 메모리를 갖고 있는데 이러한 디바이스에서도 잘 돌아가도록 최적화 되어 있다.
512MB에서 잘 동작하도록 하기 위해서 앱은 다음과 같은 메모리 사용 제한을 갖게 된다.

image

image

시스템은 앱이 얼마나 많은 메모리를 사용하는지를 모니터링하여 앱의 메모리 제한을 조정한다. 그래서 결론적으로 더 많은 앱을 실행 상태로 유지할 수 있고, 재시작 안 해도 되기 때문에 UX 측면의 이점을 얻을 수 있다. 윈도우폰8.1앱은 백그라운드에서 앱을 종료하지 않으며, 쉽고 편리한 앱 스위처를 제공해서 재생이 원활하도록 한다.

image

앱 실행은 트리거 기반으로 된다. 코드가 필요할 때만 실행이 된다는 점이 중요한 부분. 노키아에서 Sensor Core라는 것을 발표했는데, 매우 적은 배터리로 센서를 트래킹할 수 있다.

image

그 밖에 기존 푸시 방식을 통해서 앱에서 특정한 작업을 실행시킬 수도 있다. 계속해서 푸시 토스트를 보여주지 않고 푸시 알림을 미리 처리해서 기존 토스트나 타일, 액션 센터 등에 업데이트하는 등으로 처리가 가능하다.

image

Geofencing을 통해서 앱은 1000개 까지 원형 지역 좌표를 설정하고, 사용자가 해당 지역에 들어갔을 때 이벤트를 받아서 실행시킬 수 있다. Geofencing도 위에 설명한 트리거를 통한 앱 실행이나 태스크 실행 푸시에 적용되는 기술이다.

image

다음은 데모를 진행했는데, PC로 폰 화면을 바로 프로젝션할 수 있는 기능이 기본으로 제공된다고 하여 좋은 반응을 얻었다. 미라캐스트도 지원하여 무선으로 화면을 공유할 수 있다고 한다. 윈도우 역시 미라캐스트를 지원하고 있다.

image image

위 스크린샷은 앞서 살펴본 Geofence를 설정하는 데모와, 심박수 센서에 접속해서 정보를 가져오는 데모이다.

image

윈도우폰의 푸시 서비스(MPNS)는 윈도우 앱의 푸시 서비스(WNS)와 통합되었다. 기존 푸시 서비스는 WNS에서 계속 동작하며 디바이스에 단 하나의 클라이언트가 푸시를 처리하여, 더 잘 동작하고 배터리도 효율적으로 사용한다.

image

토스트 알림은 동일한 데이터 형식을 사용하면서 위와 같이 디바이스에 맞게 시각화된다.

image

알림을 관리하고 관련 정보를 확인 및 처리할 수 있는 액션 센터가 새로 추가되었다.

image

라이브 타일은 이제 같은 템플릿 메커니즘을 사용하고, 다양한 형식으로 더 많은 사용자 반응을 이끌어 낼 수 있다.

image

윈도우 앱에서 사용하던 APPX 포맷이 폰에 적용이 되었다. 윈도우 앱과 완전히 동일한 포맷이라고 한다. 멀티 해상도와 언어를 지원하는 것이 특징. 모든 해상도와 언어를 APPX에 패키지하였다고 하더라도, 스토어에서 다운로드 받을 때는 필요한 해상도와 언어만 다운로드 한다. 또한, 앱 업데이트 시에 전체 파일을 다운로드하는 것이 아니라 실제 변경된 부분만 다운로드 하게 된다.

image

SD 카드는 윈도우폰8부터 사용할 수 있었는데, 앱을 SD에 설지할지 옮길지 등을 사용자가 결정할 수 있게 된다. 또한, SD 카드 상에서 보호 및 격리를 위해 암호화 된다.

image

100K 용량의 설정을 OneDrive를 통해 로밍할 수 있으며, 윈도우와 윈도우폰의 같은 앱에서 공유할 수 있다. 스타트스크린과 앱 데이터 백업도 OneDrive를 통해 제공한다. 설정은 사용자의 OneDrive 용량을 사용하지 않지만 앱 데이터 백업은 사용자 용량을 사용하며 대신 100K 제한이 없다.

 

image

1년에 $299 짜리 엔터프라이즈 토큰이 있는데, 이를 이용해서 앱을 서명하면 스토어를 통해서 다운로드 할 필요가 없다. Device Management Servers, 웹사이트, 이메일, SD 카드 등을 통해서 배포할 수 있다.

image

앱 간에 데이터를 공유하는 기능이 가능하다. 윈도우 앱에 있었던 Share Contract이 적용된다. 대신 윈도우폰에는 Charm Bar가 없다. 앱바에서 Share 버튼을 클릭해서 공유하는 식으로 구현이 된다. 윈도우와 마찬가지로 자주 사용하는 앱이 공유 앱 리스트 상단에 올라온다.

전체 내용을 정리하고 요약하자면 다음과 같다.

  • 저사양에서 고사양에 이르기까지 앱은 더 잘 동작한다.
  • 트리거 기반의 멀티태스킹
  • 향상된 배포 옵션
  • 백업 & 로밍
  • WNS와 액션 센터의 활용

윈도우 앱의 앱 모델이 적용이 되면서 많은 부분에서 동일한 기능을 갖게 되었고, 개념들도 많이 차용되었다. 그러면서도 기존 실버라이트 앱과의 호환성을 유지하고 윈도우폰8 디바이스에 모두 업데이트가 가능한 점은 아주 적절하지 않나 싶다. 윈도우 앱과의 데이터 로밍, 푸시 연동 등 클라우드 활용에 대해서는 시나리오에 대해서 앱을 서비스하는 입장에서 많은 고민이 필요할 듯 싶다.