Xamarin 引用iOS静态库(.a)文件

我们在开发的的时候常常要引用第三方的库文件或者SDK等。在window平台下有.dll或com组件,在Android可以引用.jar包或.aar包。在Mac(iOS)平台下则可以引用.a或者.framework静态库,在Mac(iOS)原生的环境下引用相对比较简单。使用Xamarin引用iOS静态库相对就比较复杂了,需要做一些特殊的操作。目前Xamarin只能引用.a文件,下面就对Xamarin引用(.a文件)静态库进行一个简单的介绍。

以下内容参考官方文档:https://developer.xamarin.com/guides/cross-platform/macios/binding/

生成自己的静态库

我们在使用第三方的库文件时候,都是已经编译好的库文件。但有时,我们只有代码需要我们自己去生成.a文件。

1、XCode创建静态库项目

打开XCode,菜单File -> New -> Project…,进入项目创建界面。

选择 Framework & Library -> Cocoa Touch Static Library。如下图所示:

image05

之后按照向导一步一步做就可以创建项目了,先假设我创建的工程名字叫做“WosLibDemo”。将源代码(.h和.m文件)导入到项目中。

2、配置项目

添加了源代码之后,我们检查我的的代码引用了哪些系统库(framework)。你可以查看README文件获得信息,或者打开一些相同的项目查考一下。一般应用的framework有 Foundation.framework、UIKit.framework和CoreGraphics.framework。具体哪些库自己搞定。

点击项目查看项目设置界面,选择 yourprojectName target -> Build Phases 然后展开 Link Binary With Libraries 区域。如下图:

image16b

上图点击 + 按键进入添加系统库界面,如下图:

image16c

搜索到我们要添加的系统库,点击 Add 按键即可。还有一些项目的配置,我还不是很清楚,之后熟悉了再补充。

3、生成库文件

在网上查资料说编译项目生成.a 文件直接 Command + B 即可。我亲自试了是可以生成的,项目中生成的位置在Products文件夹下,但是在硬盘中找该文件时却找不到,会保存到默认路径下。如下图,点击生成的.a 文件,右侧高光的路径就是该文件的路径。可以对生成路径设置。

%e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2016-09-30-%e4%b8%8b%e5%8d%884-58-14

但是参考官方文档指出,所有版本的iOS 设备的处理器框架是不同的,在iOS设备上处理器就有 armv6 ,armv7,amrv7s,arm64指令集。iOS虚拟机是x86或x86_64框架。为了各个框架通用性,.a 文件要编译出不同框架的版本,然后在将他们合并成一个 .a文件。官方推荐使用Makefile文件来编译项目生成 .a文件。下面是创建Makefile文件的步骤。

  • 打开Mac电脑的终端,使用cd指令  到你所建项目的同级目录下
  • 输入指令  touch Makefile,此时会创建一个空的文件
  • 使用文本编辑器打开 Makefile文件,复制下面的代码,记得将 YOUR-PROJECT-NAME 替换成你项目的名称,最后保存即可
XBUILD=/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild
PROJECT_ROOT=./YOUR-PROJECT-NAME
PROJECT=$(PROJECT_ROOT)/YOUR-PROJECT-NAME.xcodeproj
TARGET=YOUR-PROJECT-NAME

all: lib$(TARGET).a

lib$(TARGET)-i386.a:
$(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphonesimulator -configuration Release clean build
-mv $(PROJECT_ROOT)/build/Release-iphonesimulator/lib$(TARGET).a $@

lib$(TARGET)-armv7.a:
$(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphoneos -arch armv7 -configuration Release clean build
-mv $(PROJECT_ROOT)/build/Release-iphoneos/lib$(TARGET).a $@

lib$(TARGET)-arm64.a:
$(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphoneos -arch arm64 -configuration Release clean build
-mv $(PROJECT_ROOT)/build/Release-iphoneos/lib$(TARGET).a $@

lib$(TARGET).a: lib$(TARGET)-i386.a lib$(TARGET)-armv7.a lib$(TARGET)-arm64.a
xcrun -sdk iphoneos lipo -create -output $@ $^

clean:
-rm -f *.a *.dll

最后目录是这样的,Makefile文件和项目文件夹在同级目录,如下图:

%e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2016-09-30-%e4%b8%8b%e5%8d%885-21-19

创建好了Makefile之后我们就开始编译了库了。同样使用Mac终端,cd到Makefile文件的路径下。然后输入指令 make ,然后就等自动编译了。如果出现什么错误,可能要自己查下原因,比如代码缩进问题。生成成功之后文件夹的这样的,如下图:

%e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2016-09-30-%e4%b8%8b%e5%8d%885-27-30

总共生成了四个a文件,前面三个是单个框架下的a文件。第四个 libWoslibDemo.a是前三个文件合并生成的。该文件也是供我们使用的。

封装a文件成Xamarin所用 .dll文件

我们已经生成了iOS的静态库(a文件),下面就要将它封装成Window平台下的.dll文件。请参照以下步骤:

1、在Visual Studio中创建一个绑定iOS类库项目

打开新建项目,选择 Visual C# -> iOS -> Bindings Library(iOS),输入项目名称点击确定即可。如下图:

1

创建了绑定了iOS类库项目之后,项目自动生成两个文件ApiDefinition.cs和 Structs.cs。如下图:

2

ApiDefinition中的内容是必须填写的,不然生成的.Dll是不能调用静态库的方法的。同时代码转换过程比较复杂,需要开发人员认真处理。

2、添加.a文件到项目中

直接将需要封装的a文件添加的项目中即可,添加之后,项目会自动生成一个匹配的 linkwith.cs文件。如图所示:

3

linkwith.cs文件中定义了a文件的所适用的平台,可以根据实际情况进行修改。

3、生成项目

添加了a文件之后,并且完成了ApiDefinition文件的头文件代码转换之后,就可以生成。生成的dll文件可给Xamarin.iOS项目使用。

注意:iOS静态库项目的成功,主要取决于ApiDefinition文件代码转换是否正确,已经类库使用平台是否正确。

使用Objective Sharpie 插件生成 ApiDefinition.cs 文件

如果我们嫌手动写ApiDefinition文件很麻烦。我们可以使用Xamarin提供的Objective Sharpie插件自动生成文件。该插件只能安装在Mac设备上。请参考官方文档:https://developer.xamarin.com/guides/cross-platform/macios/binding/objective-sharpie/

安装了该插件之后,打开Mac终端输入下面代码

sharpie bind --output YourProjectName --namespace YourProjectName --sdk [iphone-os] [full-path-to-project]/WosLibDemo/WosLibDemo/*.h

[iphone-os] 是你手机系统,[full-path-to-project]是你项目的完整路径。

最后*.h表示该路径下的全部头文件,也可以写单个文件的名称。我使用的时候输入指令如下:

sharpie bind --output WosLibDemo -namespace WosLibDemo -sdk iphoneos8.1 /Users/woscms/Documents/TestProject/WosLibDemo/WosLibDemo/*.h

最后生成结果如下图:

%e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2016-09-30-%e4%b8%8b%e5%8d%885-51-05

但是自动生成的ApiDefinition文件不一定完全正确,需要开发人员检查,错误的地方要手动修改。

Xamarin引用.jar包

使用Xamarin开发Android App可以引用原生的Jar包。但是并不是Jar包直接引用,而是要把Jar包封装成dll文件再给Xamarin.Android项目使用。在Xamarin的官网中有使用方法有详细说明,地址如下:https://developer.xamarin.com/guides/android/advanced_topics/binding-a-java-library/binding-a-jar/

下面内容对绑定Jar包进行一个简单的说明。

创建Binding Library(Android)项目

该项目是用于封装Android的jar包的,生成C#能使用的dll文件。

1、打开Visual Studio,进入新建项目对话框。选择”Android”->”Binding Library(Android)”,点击确认创建。如下图,

1

2、向项目中的”Jars”文件夹中添加想要引用的Jar包,该文件夹可以添加多个Jar包。注意:如果不添加Jar文件直接生成项目会报错。

2

如上图所示,添加了两个包”testjar.jar”,”picasso-2.5.2.jar”。

3、修改Jar包的属性。右击添加的Jar文件,选择“属性”选项,将属性“生成操作”的值改为“EmbeddedJar”。

3

4、执行完上述操作之后,生成项目即可。

注意事项:在封装Jar包的时候,可能会出现很多警告或者错误。有时编译成功了,但是jar包中的方法属性丢失了。这时可以参考官方的文档:https://developer.xamarin.com/guides/android/advanced_topics/binding-a-java-library/troubleshooting-bindings/

引用Jar包封装的dll

1、Xamarin.Android项目可以直接引用生成的dll即可。

2、Xamarin.Forms项目中的 .Droid项目可引用该dll。

另外,Xamarin.Android 引用.AAR文件方法类似。请参考 https://developer.xamarin.com/guides/android/advanced_topics/binding-a-java-library/binding-an-aar/

常见错误

1、Jdk版本错误。当Jar包的Jdk版本和编译环境的版本不一致时,能编译成功。但是Jar包中的方法和属性会丢失,不能封装到dll中。所以要确保引用的jar包的版本和自身环境是一致的。

有关Xamarin Forms ListView 的Header属性问题

最近在学习Xamarin.Forms的过程中一直遇到很多莫名其妙的问题,特别是在UWP平台上。不知是否该平台刚刚发布,很多不完善。

Xamarin.Forms的ListView控件有个Header属性,在iOS和Android平台下直接赋值就行了,显示也很正常。但是在UWP平台下,Header属性(构造函数中设置正常)修改后界面显示总是要慢一拍,页面中显示的总是本次赋值前的值。后来想了很久写了个方法暂且解决这个问题。

void SetListViewHeader(ListView listView, object header)
{
    if(Device.OS!=TargetPlatform.Windows) listView.Header=header;//非UWP平台正常赋值

    listView.Header=header==null?String.Empty:header;
    listView.Header=null;
}

这样写好变扭,希望Xamarin.Forms 能够进一步完善。

Xamarin MasterDetailPage和 NavigationPage

学习Xamarin的Pages,想使用MasterDetailPage作为主页,随手写了一段代码。

public class MainPage:MasterDetailPage
{
    public MainPage()
    {
        this.Master=new ContentPage
        {
            Title="Master Page",
            Content = new StackLayout{ ... }
        };

        this.Detail=new ContentPage{ ... };
    }
}

//App.cs中设置主页
MianPage=new NavigationPage(new MainPage());

在Xamarin Forms UWP平台上运行,结果发现Master/Detail之间不能切换,设置MasterDetailPage属性 IsPresented=false之后,就回不到MasterPage页面了,后来去看了一下官方的Demo发现对MasterDetailPage使用导航页面(NavigationPage)的方法不对。正确代码如下:

public class MainPage:MasterDetailPage
{
    public MainPage()
    {
        this.Master=new ContentPage
        {
            Title="Master Page",
            Content = new StackLayout{}
        };

       this.Detail=new NavigationPage(new ContentPage{ ... });
    }
}

//App.cs中设置主页
MianPage=new MainPage();

不要直接对MasterDetailPage使用 NavigationPage,而是对DetailPage使用。

Xamarin开发,使用MessageCenter在ViewModel和View之间传递值

最近在学习Xamarin.Forms的开发,并且使用MVVM的结构。在学习的过程中发现页面的点击命令都在ViewModel中的Command实现,如果想要点击按键之后实现页面的跳转,必须在UI层调用 Navigation.Push方法才能实现。后来想想可以用MessageCenter实现。下面是实现方法:

首先定义一个传递的数据类,

public class NavigationMessage  
{  
    public object Paremeter { get; set; }  
}  

在ViewModel中输入发现送消息代码,可放在某个点击命令中,

MessagingCenter.Send<NavigationMessage>(new NavigationMessage() { Paremeter = "hello" },  "Test");  

在View中订阅该消息通知,

protected override void OnAppearing()  
{  
    SubscribeToMessages();  
    base.OnAppearing();  
}  
  
protected override void OnDisappearing()  
{  
    UnsubscribeToMessages();  
    base.OnDisappearing();  
}  
  
private void SubscribeToMessages()  
{  
    MessagingCenter.Subscribe<NavigationMessage>(this, "Test", (args) => {  
        Navigation.PushAsync(new TestPage());//跳转到新的页面  
    });  
}  
  
private void UnsubscribeToMessages()  
{  
    MessagingCenter.Unsubscribe<NavigationMessage>(this, "Test");  
}