Mobile Development 25 min read

How We Halved Mobile Hardware Integration Time with a Modular Device SDK

The article details how the Youzan Retail mobile team re‑architected their hardware SDK—splitting devices into modular AAR packages, introducing layered OEM/Core/Base/Library components, unifying printer protocols with JsCore, and adding IoT monitoring—reducing new device onboarding from days to hours while cutting online issues by up to 55%.

Youzan Coder
Youzan Coder
Youzan Coder
How We Halved Mobile Hardware Integration Time with a Modular Device SDK

Background

The retail app integrates more than 20 categories of smart hardware (printers, scales, POS terminals, facial‑recognition cameras, NFC/RFID readers, etc.). The original monolithic SDK caused high development cost, frequent releases, and unstable device interactions due to unclear device classification, diverse communication protocols, and duplicated platform adaptations.

Hardware Matrix

All‑in‑One Terminal : Cash register used for order entry.

Printers : Receipt printing (e.g., 365, Yimei Cloud, Jiabo, Supreet, Yilianyun).

Secondary Screens : Real‑time cart, member and payment display (e.g., Sunmi T2, T1, D2).

Customer‑Facing Displays : Low‑cost external screens for data projection.

Facial Recognition : Fast member identification (e.g., Frog Pro, Dragonfly).

NFC & RFID : Card payment and RFID‑tagged item support.

Electronic Scales : Fresh produce weighing (e.g., Kaishi, Dahua, Oulu, S2).

POS Terminals : Card‑payment terminals with swipe capability (e.g., WANGPOS, SUNMI P series).

SDK Architecture Refactoring

The SDK was split into four independent layers to improve modularity and scalability.

OEM Layer

Provides manager APIs such as PrinterManager, PosManager, and WeightManager. Each device type is packaged as an independent AAR, allowing business modules to depend only on the required AAR.

Core Layer

Implements common capabilities: device model abstraction, connection handling, caching, heartbeat monitoring, exception handling, and thread management. Core contracts include:

interface IPrinter : IDevice {
    fun getPagerType(): PagerType
    fun getProtocol(): Protocol
    fun print(content: ByteArray): PrinterResponse
    fun jsDeviceName(): String
    fun isSupportJSPrinter(): Boolean
}

interface IPos : IDevice {
    fun payByPos(entity: PhoinexPosPayResult): Observable<PhoinexPosPayResult>
    fun revoke(orderNo: String, voucherNo: String): Observable<Any>
    fun refund(orderNo: String): Observable<Any>
}

interface IWeight : IDevice {
    fun doTare(): Pair<Boolean, String>
    fun doZero(): Pair<Boolean, String>
}

Base Layer

Provides foundational utilities such as network request handling and logging.

Library Layer

Encapsulates third‑party vendor SDKs (e.g., woyou.aidlservice for Sunmi printers, sprtprintersdk for Supreet, paymentService for Sunmi P1, cloudpossdk / wangpossdk for WANGPOS) to avoid duplicate dependencies across device modules.

Implementation Details

Printer Integration

Each printer implementation extends AbsPrinter (which implements IPrinter) and may also implement IMoneyBox for cash‑drawer control. The manager delegates shared services such as caching and threading to DeviceCoreManager.

POS Integration

Early POS code directly handled the 8583 protocol, causing instability. The refactor switched to vendor‑provided SDKs while preserving a compatibility layer for legacy code. PosManager exposes payment APIs defined by IPos.

Scale Integration

Scales implement IWeight, providing tare and zero functions. WeightManager offers CRUD operations and broadcasts weight updates via a CallableData (LiveData‑like) mechanism.

Heartbeat & State Monitoring

A background thread polls device states every two seconds and notifies registered listeners only on state change.

fun registerCheckState(listener: IDeviceStateListener) {
    if (!deviceStateListeners.contains(listener)) {
        deviceStateListeners.add(listener)
    }
    checkHeart()
}

Cache Management

Device information is persisted to local files so that reconnections (e.g., after app upgrade) can restore previous state quickly.

class DeviceCacheManager {
    fun addDevice(deviceInfo: DeviceInfo?) { /* add to memory and flush to file */ }
    fun removeDevice(deviceInfo: DeviceInfo?) { /* remove from memory and flush */ }
    fun getCacheDevices(tag: String): List<IDevice>? { /* load from file if needed */ }
}

Unified Printer Protocol via JsCore

Printing content is described with an HTML‑like template. JsCore parses the template, replaces {{key}} placeholders with runtime data, and generates the appropriate ESC/POS or vendor‑specific byte stream. The template can be updated remotely from the backend, allowing instant bug fixes without redeploying the app. Example template:

<html><head></head>
<body>
<p style="font-size:24px;text-align:center;">{{shopName}}</p>
<p>订单号:{{orderNo}}</p>
<qrcode>{{invoiceUrl}}</qrcode>
</body>
</html>

Secondary‑Screen Plugin Architecture

The secondary screen displays cart, member and payment information in real time. Plugins are defined by a class name; the framework reflects the class, inflates its layout, and injects data via SubPlugin callbacks. Core data structures:

@Keep
public class SubEntity {
    @SerializedName("action") public int action;
    @SerializedName("title") public String title;
    @SerializedName("jsonData") public String jsonData;
    @SerializedName("leftPlugin") public String leftPlugin;
    @SerializedName("rightPlugin") public String rightPlugin;
}
public abstract class SubPlugin {
    private Context context;
    public SubPlugin(Context ctx) { context = ctx; }
    public abstract int getLayout();
    public abstract void createView(View view, Bundle bundle);
    public abstract void updateView(Bundle bundle);
    public abstract View getView();
    @Nullable
    public abstract View getView(LayoutInflater inflater, @Nullable ViewGroup container);
}

Plugin instantiation via reflection:

public static SubPlugin getPlugin(Context ctx, String className) {
    if (TextUtils.isEmpty(className)) return null;
    try {
        return (SubPlugin) Class.forName(className)
            .getConstructor(Context.class).newInstance(ctx);
    } catch (Exception e) { e.printStackTrace(); }
    return null;
}

Layout inflation and view injection:

if (leftPlugin != null) {
    View left = LayoutInflater.from(mContext).inflate(leftPlugin.getLayout(), leftView, false);
    leftPlugin.createView(left, bundle);
    leftView.removeAllViews();
    leftView.addView(leftPlugin.getView());
}

IoT Monitoring

All external devices report status to a cloud backend, enabling real‑time health checks, remote unbinding, and centralized diagnostics. The SDK pushes state changes to the IoT service; the backend can issue commands to reset or remove devices, dramatically reducing troubleshooting time.

Impact Metrics

New hardware onboarding time reduced by ~50% (from 1‑2 days to a few hours).

Online hardware issue tickets dropped 55% for developers and 33% for support staff.

Fewer SDK releases and more stable device interactions.

Future Directions

Expand the IoT platform for company‑wide hardware solutions.

Open hardware integration APIs for third‑party devices.

Develop a self‑diagnosis assistant to let merchants troubleshoot locally.

Introduce finer‑grained device classification for selective dependency injection.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

MobileSDKarchitectureAndroidHardwareIoT
Youzan Coder
Written by

Youzan Coder

Official Youzan tech channel, delivering technical insights and occasional daily updates from the Youzan tech team.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.