[Xamarin Android] Sử dụng thư viện native trong Xamarin Android P.2
Ở phần trước mình đã nói sơ về khái niệm của thằng Java Binding Library (JBL) này. Giờ là lúc bắt tay vào thực hành. Đầu tiên cần tìm thư viện cần dùng và build ra file .jar hoặc .aar nhé. Ở đây mình dùng thằng https://github.com/Porval/InfiniteIndicator này để làm demo, đây là thư viện ...
Ở phần trước mình đã nói sơ về khái niệm của thằng Java Binding Library (JBL) này. Giờ là lúc bắt tay vào thực hành. Đầu tiên cần tìm thư viện cần dùng và build ra file .jar hoặc .aar nhé. Ở đây mình dùng thằng https://github.com/Porval/InfiniteIndicator này để làm demo, đây là thư viện InfiniteIndicator, chắc làm android thì nhiều người sẽ biết về topic này.
Ở đây mình có 2 cách:
- tạo 1 project JBL riêng biệt ở ngoài, build và lấy file DLL, từ file DLL đấy sẽ import vào nơi cần dùng.
- là add 1 project JBL nào thẳng trong solution làm việc của mình luôn, sau đó add reference của project JBL đấy vào các nơi cần dùng, cách này thì khi chúng ta build hoặc update input (tức là thay đổi file jar hoặc aar) cho JBL thì chỉ cần rebuild là xong, DLL được import thẳng vào nơi cần dùng vì có reference
Nhưng nếu với trường hợp thư viện jar/aar được build trên nền jdk khác với nên jdk mà project chính của bạn làm việc thì cách 1 chính là solution cho việc này.
1. Khởi tạo project Binding library:
Cấu tạo project sơ bộ gồm 3 thư mục như sau:
- Additions: chứa thông tin như about, hướng dẫn sử dụng :v
- Jars: nơi chứa các file .jar, .aar của chúng ta
- Transforms: gồm 3 file mặc định: EnumFields.xml, EnumMethods.xm, Metadata.xml. Dùng để config, thay đổi, sửa lỗi trong quá trình build (sẽ nói ở sau).
2. Add file .jar/.aar vào JBL
Như đã nói ở #1, folder Jars là nơi sẽ chứa file(s) .jar/.aar của chúng ta. Right click vào nó và chọn Add existing item, trỏ tới file .jar/.aar và add chúng vào.
3. Set build action cho file .jar/.aar
Khi đã add file .aar vào JBL, chúng ta phải set build action cho nó, ý nghĩa là để JBL biết cách chúng ta muốn nhúng file aar này vào JBL như thế nào, ở đây có 2 options:
- EmbeddedJar (recommended): file .aar sẽ được nhúng vào JBL, khi build JBL, Java bytecode từ file .aar sẽ được chuyển thành Dex bytecode và nhúng vào cùng với MCW (ở phần 1) và bơm vào file APK của app. Nên sử dụng cách này để tránh các lỗi không mong muốn.
- InputJar: file aar sẽ không được nhúng vào JBL khi build, tuy nhiên bạn phải bảo đảo file JAR này đã tồn tại trên device đang chạy file APK của app bạn. Nó sẽ mang rất nhiều rủi ro tiềm ẩn cho bạn, thực tế tôi nhác chết nên chưa thử option này :v
Vậy bước này chúng ta sẽ chọn Build action là EmbeddedJar.
4. Config target framework cho JBL
Trước khi build, chúng ta phải xác định được rằng là file jar/aar này được build trên môi trường nào.
- Android API level
- JDK version
Khi xác định được 2 target trên, tiến hành config: Right-click vào project JBL -> Properties
Đối với API level, chúng ta sẽ chọn API level nào nằm trong khoảng mà file jar/aar support là được. Chọn tab Application nhé, ở đây tôi chọn Lastest cho khoẻ.
Đối với JDK version, khi chúng ta chọn sai version so với version được build của thằng file jav/aar thì khi build JBL sẽ có error hoặc sẽ không đủ class tại file DLL. Lỗi này thì sai JDK level là 1 trong các lý do thôi, mình sẽ hướng dẫn sửa những lỗi khác sau nhé.
Note: Khi đổi JDK thì nó sẽ apply cho toàn bộ IDE (XamarinStudio/VisualStudio) của bạn, nên như đã nói ở phần đầu, nếu với trường hợp thư viện jar/aar được build trên nền jdk khác với nên jdk mà project chính của bạn làm việc thì hãy tách project JBL ra 1 solution riêng, mở nó lên, down/up JDK version phù hợp và build lấy file DLL, sau đó rollback lại JDK version cũ và về solution làm việc, add DLL build thành công vào và làm việc.
Để đổi JDK version: Tools -> Options -> Xamarin -> chọn Change tại đoạn Java Development Kit Location, trỏ đến location có JDK phù hợp, ở đây của mình may mắn là cả 2 đều là JDK 1.8 nên chọn 1.8 ngon lành:
5. Build
Cuối cùng thì chúng ta tiến hành build project JBL để xuất được file DLL, nếu những config của chúng ta đã đúng và không gặp vấn đề gì, thì đây là thành quả:
Chú ta thử đối chiếu code java đã được convert sang thành code C# ra sao nhé:
Code java:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.jp.inifinite; import android.content.Context; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import com.jp.inifinite.InfinitePagerAdapterWrapper; public class InfinitePagerView extends ViewPager { public InfinitePagerView(Context context) { super(context); } public InfinitePagerView(Context context, AttributeSet attrs) { super(context, attrs); } public void setAdapter(PagerAdapter adapter) { if(!(adapter instanceof InfinitePagerAdapterWrapper)) { adapter = new InfinitePagerAdapterWrapper((PagerAdapter)adapter); } super.setAdapter((PagerAdapter)adapter); this.setCurrentItem(0); } public void setCurrentItem(int item) { this.setCurrentItem(item, false); } public void setCurrentItem(int item, boolean smoothScroll) { PagerAdapter pagerAdapter = this.getAdapter(); if(pagerAdapter instanceof InfinitePagerAdapterWrapper) { int currentItem = this.getCurrentItem(); int virtualCurrentItem = super.getCurrentItem(); int realCount = ((InfinitePagerAdapterWrapper)pagerAdapter).getRealCount(); if(virtualCurrentItem < this.getOffsetAmount()) { item = this.getOffsetAmount() + item % realCount; } else if(item > currentItem) { item = virtualCurrentItem + (item - currentItem) % realCount; } else { item = virtualCurrentItem - (currentItem - item); } } super.setCurrentItem(item, smoothScroll); } public int getCurrentItem() { int position = super.getCurrentItem(); if(this.getAdapter() instanceof InfinitePagerAdapterWrapper) { InfinitePagerAdapterWrapper infAdapter = (InfinitePagerAdapterWrapper)this.getAdapter(); return position % infAdapter.getRealCount(); } else { return super.getCurrentItem(); } } private int getOffsetAmount() { if(this.getAdapter() instanceof InfinitePagerAdapterWrapper) { InfinitePagerAdapterWrapper infAdapter = (InfinitePagerAdapterWrapper)this.getAdapter(); return infAdapter.getRealCount() * 100; } else { return 0; } } }
This is Magic:
using Android.Content; using Android.Runtime; using Android.Support.V4.View; using Android.Util; using System; namespace Com.JP.Inifinite { [Register ("com/jp/inifinite/InfinitePagerView", DoNotGenerateAcw = true)] public class InfinitePagerView : ViewPager { // // Static Fields // internal static IntPtr java_class_handle; private static IntPtr id_ctor_Landroid_content_Context_; private static IntPtr id_ctor_Landroid_content_Context_Landroid_util_AttributeSet_; // // Static Properties // internal static IntPtr class_ref { get; } // // Properties // protected override IntPtr ThresholdClass { get; } protected override Type ThresholdType { get; } // // Constructors // protected InfinitePagerView (IntPtr javaReference, JniHandleOwnership transfer); [Register (".ctor", "(Landroid/content/Context;)V", "")] public InfinitePagerView (Context p0); [Register (".ctor", "(Landroid/content/Context;Landroid/util/AttributeSet;)V", "")] public InfinitePagerView (Context p0, IAttributeSet p1); } }