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 쪽도 살펴볼까 싶다.

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

 

Xamarin 앱 개발 테스트 #5

사실, Xamarin이 크로스 플랫폼 앱을 개발하는데 가장 뛰어난 툴이라고는 할 수 없다. 단지 크로스플랫폼 앱을 빠르게 만들고 싶다면, Codova를 이용한 하이브리드앱이나 AIR, Corona SDK 같이 완전히 동일한 코드를 실행하는 다른 크로스 플랫폼 도구를 사용하는 것이 나을 수도 있다. 다만, Xamarin의 장점은 다른 언어가 아닌 C#으로 개발할 수 있다는 점, Visual Studio를 이용할 수 있다는 점, 기능 제약이 없는 네이티브 앱을 개발할 수 있다는 점 등이 특징이다.

완전히 동일한 코드를 사용하는 것은 아니지만 최대한 코드 재사용성을 높이는 방법을 살펴보고 얼마나 코드 재사용이 가능할지, 어느 정도 할만한지를 확인하는 것이 이 글의 목적이다.

자료를 조사하면서 Xamarin 크로스 플랫폼 앱을 개발하는 데 있어서, Portable Class Library를 이용해 공용 라이브러리를 만들거나,  Shared 프로젝트를 이용해 코드를 공유하는 방법 등을 이용할 수 있다는 것을 알게 되었다.

Portable Class Library(이하 PCL)는 Windows Phone 7 시절에 서로 다른 .NET 프로젝트에서 이식 가능한 라이브러리를 만들 수 있도록 한 것이고, Shared 프로젝트는 Universal Windows App에서 Windows/Windows Phone 프로젝트간 코드 공유를 위해 나온 것이다. Universal Windows App을 위해서 나온 것이지만, 다른 C#, C++, WWA/JS 프로젝트에서도 사용할 수 있도록 하는 Shared Project Reference Manager라는 VS 확장기능도 있다. PCL과 Shared 프로젝트는 개념이 서로 다르다. 두 가지의 차이에 대해서 설명한 글도 있는데 내용을 요약하면 다음과 같다.

  • PCL은 공통 API만 사용 가능하고 플랫폼 고유의 코드를 추상화 레이어 없이는 사용할 수 없는 반면, Shared 프로젝트는 플랫폼 고유의 코드를 사용할 수 있고 XAML UI도 공유할 수 있다.
  • PCL과 Shared 프로젝트는 컴파일과 배포 시점에 완전히 다르게 동작한다. PCL은 계약과 구현에 의해 컴파일할 수 있으나, 빌드와 배포 시점에 플랫폼 고유 버전이 사용된다. Shared 프로젝트는 그 것이 속해 있는 부모 프로젝트와 부모의 레퍼런스에 따라 컴파일하고 배포된다.
  • 글쓴이(Xamarin Developer Evangelist)는 PCL은 라이브러리 개발자, Shared 프로젝트는 앱 개발자를 위한 것이라고 생각하지만, 앱 개발자의 취향에 따라서 PCL 선택할 수 있지만, PCL의 제약으로 인해서 Shared 프로젝트를 더 많이 쓸 생각이라고…

PCL과 Shared 프로젝트를 함께 쓰는 것도 괜찮은 것 같다. System.Net.Http와 같이 PCL 버전으로 만들어진 라이브러리를 사용하면서 Shared 프로젝트에 공유 코드를 담는 방식으로 구현하는 것이 좋겠다는 생각이 든다.

먼저 Xamarin 크로스 플랫폼 앱 개발 문서에도 나와 있는 PCL을 이용하는 방법에 대해서 살펴보고자 한다. 이전 글에서 3가지 코드 공유 옵션을 간단히 살펴 보았지만, 그 중에서는 PCL을 만들어서 공유 컴포넌트를 만들어 사용하는 것이 가장 재사용성이 높다고 생각된다. (File Linking 방식은 Shared 프로젝트로 대체 가능하다.)

참고로, Visual Studio 없이 Xamarin Studio에서 PCL을 사용하려면 Portable Library Tools가 필요하다.

Visual Studio에서 기존 VS 솔루션에 Portble Class Library 프로젝트를 새로 추가하면 아래와 같은 화면이 나온다. Xamarin이 설치되어 있으면 Targets 리스트에 Xamarin.Android와 Xamarin.iOS가 나오게 된다.

 image

PCL에서 코드를 작성하면 같은 API라도 IntelliSense로 접근 가능한 옵션이 다르게 나온다. 아래는 Xamarin용으로 사용하는 프로파일과 기본 프로파일의 비교이다. 우측 스크롤바를 통해 비교할 수도 있지만, 각각 14개(PCL) vs 40개(일반적인 프로젝트)의 클래스가 사용 가능하다.

이러한 이유로 사용 가능한 API에 상당한 제약이 있다. 아래는 PCL을 만들 때 참고할 수 있는 지원 기능 리스트이다. (출처)

image

PCL을 사용할 때는 여타 라이브러리와 마찬가지로 아래와 같이 프로젝트 레퍼런스에 추가하여 사용한다.

아래 솔루션 구성을 보면 맨 아랫 부분에 PCL 프로젝트가 있고, Reference 폴더에 PCL이 추가된 것을 볼 수 있다.

PCL 사용 방법은 살펴보았지만 실제로 포팅이 잘 될지는 모르겠다. Universal Windows  App은 아니지만, Silverlight로 만들어진 앱을 포팅하는 가이드도 있긴 하다.

이 중 Android로 포팅하는 것을 참고로 하여 기존에 Windows Phone 8.1 앱 개발 튜토리얼에 나오는 FlickrSearch라는 Universal Store App을 한번 포팅해보고자 한다.

image image

FlickrSearch는 Flickr Open API를 이용해서 특정 키워드(flower)로 검색한 이미지 결과를 GridView에 뿌려주는 Universal Windows App 앱이다. 두 앱은 완전히 동일한 MainPage(xaml/cs) 코드로 동작한다.

먼저, Xamarin’s .NET Mobility Scanner라는 웹사이트 툴을 이용해서 dll이나 exe 파일을 검사하여, 코드를 얼마나 재사용 가능한지를 살펴보는 것을 권장하고 있다. 아래는 FlickrSearch 앱의 Phone 패키지 폴더에서 exe 파일을 넣고 돌려본 결과이다.

image

예전에 다른 Windows Phone Silverlight 앱을 넣었을 때는 61%가 나왔는데, 보통 60~70% 정도의 결과가 나오는 것 같다. 그런데 실상 결과 리스트를 보면 항목이 너무 많이 나와서 이걸로 포팅하는데 얼마나 도움이 될까 싶기도 하다.

FlickrSearch 솔루션에 Android 포팅을 위해서 FlickrSearch.Android라는 이름으로 Android Application 프로젝트를 추가하고, 여기를 참고하여 FlickrSearch.Shared 프로젝트를 레퍼런스로 추가하였다. 이 때 App.xaml.cs 파일과 TemplateUtility.cs는 Xamarin에서 실행되지 않으므로, 코드 전체를 #if NETFX_CORE ~ #endif로 처리해 주었다.(이럴꺼면 왜 Shared 프로젝트? 라고 생각할 수도 있지만, 일단 Windows/Windows Phone 프로젝트에서 사용하기도 하고, FlickrSearcher.cs 파일은 Android에서도 재사용할 수 있다.)

image

FlickrSearcher.cs 파일이 실제로 Flickr API를 호출하는 클래스인데, 이 프로젝트에서 유일하게 재사용 가능한 코드이다. 그런데 원래 예제에서는 Windows.Http를 사용하고 있다. 따라서 Xamarin.Android에서는 사용할 수 없고, 다행히(?) PCL로 제공되는 System.Net.Http 를 사용할 수 있기 때문에 이 것으로 바꾸어 주어야 한다. 여기서 PCL 버전의 System.Net.Http을 찾을 수 있었다.

앞서 PCL과 Shared 프로젝트를 함께 쓰는 것이 괜찮다고 언급하였는데, 이처럼 Shared 프로젝트에 Application/UI 로직을 제외한 코드를 넣고, 타 프로젝트와 함께 사용하거나 이미 만들어져 있는 PCL만 참조로 추가하여 개발하는 방식이 포팅하기에는 편리해 보인다.

Shared 프로젝트의 흥미로운 점은 현재 실행되는 부모 프로젝트의 레퍼런스를 따라가기 때문에 FlickrSearch.Android 프로젝트의 레퍼런스에만 PCL을 추가해 주면 된다는 점이다. Universal Windows App에서는 PCL 추가 없이 System.Net.Http를 사용할 수 있다.

FlickrSearch.Android 프로젝트에서 PCL을 이용한 FlickrSearch 클래스로 Flickr 데이터를 잘 가져오는지 테스트해봤다.
image

일단 앱이 단순해서 필요한 코드는 쉽게 이식할 수 있었는데, 사실 좀 더 복잡한 프로젝트라고 하면 상당히 많은 코드에서 대체 라이브러리를 찾거나 직접 구현하는데 시간을 들여야 할 듯 싶다. Application/UI Layer는 완전히 새로 만들어야 하기 때문에 앱에 따라서는 이 부분도 상당한 시간이 걸릴 수 있다.

다음 글에서 Android의 GridView를 이용해서 이 데이터를 사용하여 UI를 구성하는 부분을 좀 더 살펴보도록 하겠다.

Xamarin 앱 개발 테스트 #4

앞서 3편의 포스트를 통해 기본 Xamarin.Android 튜토리얼을 살펴보았다. 이번 글에서는 Xamarin의 본래 기능인 크로스 플랫폼 앱 개발과 관련한 내용을 좀 더 살펴보고자 한다.

Xamarin 문서에 보면 Android, iOS, Windows Phone 등 서로 다른 플랫폼 앱을 어떻게 구현하는지에 대한 기본적인 이해를 돕는 내용들이 있다. 내용이 많아서 몇 가지 흥미로운 부분만 발췌해본다.

먼저 Xamarin 플랫폼의 구성요소를 살펴보면 다음과 같다.

  • C# language – Allows you to use a familiar syntax and sophisticated features like Generics, Linq and the Parallel Task Library.
  • Mono .NET framework – Provides a cross-platform implementation of the extensive features in Microsoft’s .NET framework.
  • Compiler – Depending on the platform, produces a native app (eg. iOS) or an integrated .NET application and runtime (eg. Android). The compiler also performs many optimizations for mobile deployment such as linking away un-used code.
  • IDE tools – The Xamarin Studio IDE and the Xamarin plug-in for Visual Studio allow you to create, build and deploy Xamarin projects.

개발툴을 Xamarin Studio(이하 XS)나 Visual Studio(이하 VS)를 사용하는데, Xamarin Studio는 무료 버전에서도 쓸 수 있고 Visual Studio 플러그인은 유료 버전 라이센스가 있어야 한다.

다음은 컴파일이 어떻게 이루어지는지에 대한 설명인데 그냥 참고만 하자.

  • iOS – C# is ahead-of-time (AOT) compiled to ARM assembly language. The .NET framework is included, with unused classes being stripped out during linking to reduce the application size. Apple does not allow runtime code generation on iOS, so some language features are not available (see Xamarin.iOS Limitations).
  • Android – C# is compiled to IL and packaged with MonoVM + JIT’ing. Unused classes in the framework are stripped out during linking. The application runs side-by-side with Java/Dalvik and interacts with the native types via JNI (see Xamarin.Android Limitations).
  • Windows Phone – C# is compiled to IL and executed by the built-in runtime, and does not require Xamarin tools. Designing Windows Phone applications following Xamarin’s guidance makes it simpler to re-use the code on iOS and Android.

iOS는 C#이 ARM assembly language로 컴파일 되는 대신, runtime 코드 생성을 허용하지 않으므로 Generic과 같은 기능 사용에 제약이 있다고 한다. Android는 MonoVM과 JIT’ing이 포함되며, Java/Dalvik과 병행 실행되며, JNI를 통해 네이티브 형식과 상호작용한다. 윈폰은 중간 언어(IL)로 컴파일 후 내장 런타임에 의해 실행되며 별도의 Xamarin 툴이 필요 없다.

다음 도표는 개발 환경에 따른 플랫폼 지원이다.

image

Windows Phone 앱은 Windows-VS(이하 VS)에서만 지원하고, iOS 앱은 Windows-VS에서 개발/배포만 가능하다. 즉, 3가지 플랫폼을 모두 개발하고 테스트하려면 Mac OS X, Windows가 모두 필요하다.

UI 개발의 경우는 Windows에서 3가지 모두 작업할 수 있다. Windows Phone은 VS와 Blend에서 작업 가능하고, 나머지 2가지 플랫폼은 VS와 XS에서 모두 작업 가능하다고 한다.

크로스 플랫폼 앱에서 코드를 공유하는 방식은 3가지 옵션이 있다.

  • 각 앱 프로젝트에 파일 링크 – C# 파일을 하나 만들어서 각 프로젝트에 링크로 추가하는 방법
  • 플랫폼 고유의 라이브러리 프로젝트 – PCL로 구현하기 어려운 경우에, 같은 기능을 하는 라이브러리를 각 플랫폼 별로 구현
  • 포터블 클래스 라이브러리(PCL) – 서로 다른 CLI 플랫폼에서 공용으로 사용할 수 있는 PCL의 사용

솔루션의 구성은 다음과 같이 하는 것을 권장한다.

Shared Code 영역은 Core Project로 만들어서 Business Layer 이하의 공유 코드들을 포함하고, 각 플랫폼 앱 별로 Application Layer/UI Layer를 포함하는 별도의 프로젝트를 구성한다.

Core 프로젝트의 구성 예

Core 프로젝트의 구성 예(Data Layer, Data Access Layer, Business Layer 등을 확인할 수 있다)

Android와 iOS 프로젝트의 구성 예(Application Layer나 UI, Assets 등을 포함하고 있다.)

여기까지 Xamarin의 크로스 플랫폼 앱 개발 기본 및 코드 공유 전략 등을 살펴보았는데, 구체적인 샘플이나 사례를 가지고 살펴봐야 좀 더 실제적인 사용 가능 여부를 파악할 수 있을 듯 하다.

Xamarin 앱 개발 테스트 #3

이번에는 튜토리얼을 따라서 2개 이상의 스크린을 갖는 Android 앱을 만들어 본다. Android의 앱 구성 요소들을 나타낸 그림인데, static main이라는 함수가 앱의 시작점이 되고, Activity와 Service가 스크린을 구성한다고 한다.

_995

Intent는 메시지를 보냄으로써 어떤 일이 수행되도록 한다. 주로 Activity를 실행하는데 사용된다. 이 글을 Xamarin에 초점을 맞춰서 살펴보려고 하므로, 좀 더 자세한 내용은 튜토리얼을 참고하자.

Android의 설정은 AndroidManifest.xml에 포함되어 있으며 모든 앱은 이 파일을 포함해야 한다. 컴포넌트 등록, 권한 요구, 버전 호환성 등의 정보를 포함하여 자세한 내용은 여기서 참고 가능하다.

샘플로 만들 앱은 버튼을 클릭했을 때 두 번째 Activity를 불러오는 앱이다.

	[Activity (Label = "HelloMultiScreen", MainLauncher = true)]
	public class FirstActivity : Activity
	{

먼저 MainActivity(튜토리얼 상에는 Activity1으로 나와 있음) 로 되어 있는 클래스 명을 FirstActivity로 변경하였는데, 바로 위에 있는 ActivityAttribute에 대해서 설명하고 있다. 이 것은 앱이 컴파일 될 때, apk 파일에 들어가는 AndroidManifest.xml 파일을 수정하여 실행할 Activity를 설정한다고 한다. 수정된 AndroidManifest.xml 파일은 다음과 같다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="hellomultiscreen.hellomultiscreen" >
    <uses-sdk android:minSdkVersion="11" />
    <application android:icon="@drawable/icon"
        android:label="HelloMultiScreen">
        <activity android:name="FirstActivity"
            android:label="HelloMultiScreen">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

<intent-filter>가 있고, <action>, <category> 등이 설정되어 있는 것을 확인할 수 있는데 이 부분이 앱의 시작 지점을 나타낸다.

Activity를 변경하는 구문은 다음과 같다. StartActivity 메소드를 이용하는데 이 것은 원래 StartActivity를 오버라이드하여 Activity 타입을 받도록 한 것이라고 한다.

showSecond.Click += delegate {
    StartActivity(typeof(SecondActivity));
};

원래 방식은 다음처럼 호출한다고 한다.

var second = new Intent(this, typeof(SecondActivity));
StartActivity(second);

다음으로는 이동할 새로운 Activity를 만들어야 한다.

새로운 C# 파일로 SecondActivity.cs 라는 이름의 Android Activity 파일을 만들고, Resources/layout에는 Second.axml 파일을 추가한다. Second.axml의 파일에는 다음과 같이 TextView 하나를 추가하였다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
    	android:id="@+id/screen2Label"
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
    	android:text="Hello second activity" />
</LinearLayout>

그리고, SecondActivity.cs의 OnCreate 메소드 안에 SetContentView를 추가해야 한다.

SetContentView (Resource.Layout.Second);

이렇게 하면 Activity의 이동은 간단히 구현이 가능하다.

image

추가로 아래와 같이 Intent 인스턴스의 PutExtra 메소드를 이용하면 Activity 간 데이터 전달을 할 수 있다.

showSecond.Click += delegate {
    var second = new Intent(this, typeof(SecondActivity));
    second.PutExtra("FirstData", "Data from FirstActivity");
    StartActivity(second);
};

PutExtra()는 문자열 뿐 아니라 다른 타입들도 전달할 수 있는 오버로드들을 포함하고 있다. 전달 받은 데이터는 SecondActivity에서 다음과 같이 사용할 수 있다.

var label = FindViewById<TextView> (Resource.Id.screen2Label);
label.Text = Intent.GetStringExtra ("FirstData") ?? "Data not available";

지금까지 멀티 스크린 앱에 대해서 살펴보았다. Activity의 Intent, 그리고 Layout을 통해서 UI에서 화면 전환을 어떻게 처리하는지를 살펴보았는데, Xamarin과 C#을 이용해서 쉽게 구현할 수 있음을 확인할 수 있었다.

Xamarin 앱 개발 테스트 #2

다음은 기본 Xamarin 튜토리얼을 통해서 기초적인 내용을 살펴보고자 한다. 먼저 Hello World 찍는 것을 살펴보자. 기본 Android Application 템플릿을 생성하고 MainActivity.cs라는 파일을 열어보았다.
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace Hello_Xamarin
{
    [Activity (Label = "Hello_Xamarin", MainLauncher = true)]
    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);

            // Get our button from the layout resource,
            // and attach an event to it
            Button button = FindViewById<Button> (Resource.Id.myButton);
            
            button.Click += delegate {
                button.Text = string.Format ("{0} clicks!", count++);
            };
        }
    }
}

코드를 살펴보면 Activity 클래스가 있는데, 이게 안드로이드 앱에서 UI를 구성하는 기본 클래스라고 한다.(Windows 앱의 Page같은 개념) 그리고 OnCreate라는 메소드를 오버라이드해서 Activity가 생성되었을 때 버튼을 찾아 클릭 핸들러를 추가하는 코드를 볼 수 있다. 아래 그림은 Activity의 Lifecycle 메소드들을 나타낸다.(참고)

_989

이 앱을 실행해보면 아래와 같이 버튼이 하나 있고, 버튼을 클릭하면 버튼 텍스트에 클릭 횟수가 나타나며 하나씩 증가한다.

image

다음은, 기본 템플릿 UI 대신 MainActivity.cs의 onCreate 부분을 수정해서 코드로 UI를 생성하는 부분이다.

base.OnCreate (bundle);

var layout = new LinearLayout (this);
layout.Orientation = Orientation.Vertical;

var aLabel = new TextView (this);
aLabel.Text = "Hello, Xamarin.Android";

var aButton = new Button (this);
aButton.Text = "Say Hello";
aButton.Click += (sendor, e) => {
    aLabel.Text = "Hello from the button";
};
layout.AddView(aLabel);
layout.AddView(aButton);
SetContentView(layout);
 
Layout은 UI 컨트롤들을 묶어주는 역할을 하고 SetContentView를 통해서 Layout을 Activity에 보여준다. 앞선 코드에서도 SetContentView(Resource.Layout.Main)이 있었는데, 프로젝트의 Resource/layout 폴더에 보면 Main.axml 파일이 있고 여기에 처음에 본 UI가 구성되어 있는 것을 확인할 수 있었다.
image
 
다음은 앱 아이콘을 변경하는 것인데, Resources\drawable에 icon.png를 변경한다. 솔루션에 파일을 추가하는 것이 조금 불편했다. Android 디바이스가 해상도가 제각각이다 보니 해상도 별로 아이콘을 제공하는 것도 조금 귀찮은 부분이다.
_988
 
설정 파일은 프로젝트를 더블클릭하여 접근할 수 있는데 UI에서 직접 수정할 수 있는 부분은 편리하다.
_987 

약간 번거로운 것은 문자열 리소스를 등록하고 사용하는 방법인데, Resource/values/Strings.xml 파일에 다음과 같이 문자열 리소스를 등록하면,

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, Click Me!</string>
    <string name="helloButtonText">Say Hello</string>
    <string name="helloLabelText">Hello Xamarin.Android</string>
    <string name="app_name">Hello_Xamarin</string>
</resources>
 

다음과 같이 Resource.designer.cs 파일이 자동으로 생성되어(수동 수정 불가) 리소스에 대한 int 값을 할당하고,

public partial class String
{
    // aapt resource value: 0x7f040000
    public const int helloButtonText = 2130968576;

    // aapt resource value: 0x7f040001
    public const int helloLabelText = 2130968577;

    private String()
    {
    }
}
이를 MainActivity.cs와 같은 앱 코드에서 다음과 같이 사용한다.
aButton.SetText(Resource.String.helloButtonText);
원래는 SetText() 메소드 대신, Text 속성에 직접 문자열을 할당하였는데, Resource.String.helloButtonText에는 int 값이 들어 있기 때문에 SetText()로 지정해 주어야 한다.
 

문자열 리소스를 등록하는 것과 유사하게 UI도 axml 파일로 생성하여 Resource로 사용할 수 있다. 앞서 Resource.Layout.Main을 호출해서 사용하였듯이, Main.axml에 작성된 UI가 Resource.designer.cs 파일에 자동으로 등록이 되고, 이를 Resource.Layout.Main으로 호출하는 것이다.

_986

좌측의 axml 코드가 우측과 같이 표현된다. @string/helloButtonText와 같이 디자인 타임에 문자열 리소스를 사용할 수 있다. axml에 사용된 LinearLayout, TextView, Button 등은 런타임 중에 인스턴스가 생성되서 코드로 접근 가능하다.

@+id/helloLabel이라는 코드도 보이는데, 이 것은 Resource에 Id 클래스를 생성하고 helloLabel이라는 고유 int 상수를 생성하여 이를 코드에서 접근할 수 있도록 해준다. 즉 Resource.designer.cs에 다음과 같이 Id클래스를 자동으로 생성한다.

public partial class Id
{
       // aapt resource value: 0x7f050001
       public const int aButton = 2131034113;

       // aapt resource value: 0x7f050000
       public const int helloLabel = 2131034112;

       private Id()
       {
       }
}

그래서 결과적으로 이렇게 작성한 Strings.xml, Main.axml을 이용해서 OnCreate 메소드를 다음과 같이 간단하게 수정할 수 있다.

protected override void OnCreate (Bundle bundle)
{
    base.OnCreate (bundle);

    SetContentView(Resource.Layout.Main);

    var aButton = FindViewById<Button> (Resource.Id.aButton);
    var aLabel = FindViewById<TextView> (Resource.Id.helloLabel);

    aButton.Click += (sender, e) => {
        aLabel.Text = "Hello from the button";
    };
}
이걸로 간단히 Hello World 앱을 만들어 보고, 코드 구조 및 리소스 사용법, UI 파일이 어떻게 동작하는지를 살펴보았다. 아직까지는 특별히 까다롭거나 어려운 부분 없이 쉽게 진행이 되었다.
다음에는 2개 이상의 화면을 갖는 앱을 만드는 멀티 스크린 튜토리얼을 살펴보도록 하겠다.
 

Xamarin 앱 개발 테스트 #1

마이크로소프트에서 본격적으로 Xamarin으로 크로스플랫폼 앱 개발을 할 수 있다고 홍보하고 있어서, Xamarin과 C#으로 크로스플랫폼 앱을 개발하는 것이 얼마나 할만한 것인지 테스트를 해보고자 한다.

우선, Xamarin의 기초적인 내용을 파악할 겸, 현재 Xamarin 사이트에서 진행 중인 공짜 C# 티셔츠 프로그램에 참여해보았다!

먼저 Xamarin for Windows를 설치한 후에, GitHub의 Xamarin Store app의 소스를 가져오고, 솔루션 파일을 열어서 실행을 하면 된다. PC에서는 iOS 쪽 앱은 실행해 볼 수 없으므로, Android 앱만 실행해 보았다. 처음 실행하면 에러가 나는데, 앱 용량 제한에 걸려서 무료 버전으로는 실행할 수가 없다. 따라서, 체험판 버전을 활성화 한 후에 실행해야 하는데, Xamarin Studio에서는 체험판을 활성화 하는 방법을 찾지 못해서 결국 Visual Studio에서 한번 열면 나오는 창에서 Trial 라이센스로 변경하였다.

image

앱을 실행하면 티셔츠를 공짜로 구매할 수 있는 앱이 나온다. 한번 실행해서 장바구니에 티셔츠를 넣고 구매하려고 하면, 소스코드에서 Xamarin 계정 이메일을 수정 후에 다시 오라고 한다. 위 스크린샷과 같이 LoginFragment.cs 에서 XamarinAcountEmail의 값을 본인의 Xamarin 계정으로 변경하고 다시 실행하면 주문을 계속 할 수 있으며, 주문할 때 주소를 물어보는데 여기에 주소를 넣고 주문을 마무리할 수 있다.(그런데 한국 주소를 넣어도 배송이 될런지 모르겠다. 티셔츠를 받으면 업데이트 하겠다.)

  • Xamarin을 실행하기 위해서는 Android 개발도구가 필요하다.
  • 왠만한 프로젝트를 하려면 유료 라이센스가 필요한데, 이 가격이 상당한데다 연단위의 구독을 하게 되어 있다. (참고)  MSDN 구독자는 90일 체험판과 할인이 있는 듯 하다.
  • Visual Studio에서 개발을 하려면 Business 이상의 라이센스가 필요하다.Trial로 해보고 꼭 필요할 때 구매하거나, 무료 버전의 Xamarin Studio를 가지고 하되 용량 제한을 피할 궁리를 해봐야 할 듯 싶다.

윈도우폰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 디바이스에 모두 업데이트가 가능한 점은 아주 적절하지 않나 싶다. 윈도우 앱과의 데이터 로밍, 푸시 연동 등 클라우드 활용에 대해서는 시나리오에 대해서 앱을 서비스하는 입장에서 많은 고민이 필요할 듯 싶다.