System designHow-to

Migrate an OpenCV Android project from Manager to a Gradle SDK setup

Replace the retired OpenCV Manager in an old Android project with a Gradle-bundled SDK or AAR, port 2.x calls to 4.x, and verify the setup.

LumaSpec Vision10 min read Share on LinkedIn

On this page 9 sections
  1. 01Why the project cannot find its package
  2. 02What you need
  3. 03The migration, step by step
  4. 04Worked example: a 2.4-line barcode activity moves to a bundled 4.6.0 build
  5. 05Prove the library works before you trust it
  6. 06Common mistakes that keep the old project broken
  7. 07When the app was only ever a prototype
  8. 08Before you ship: the short checklist
  9. 09Questions

You open a legacy Android project, run the activity that does the image processing, and the screen answers with "Package not found". The code is not broken; it is waiting for the retired OpenCV Manager app to supply its native library, and that app is no longer there to find. This guide is for engineers reviving 2.x-era OpenCV Android code. You will remove the Manager dependency, add the OpenCV Android SDK to your Gradle build as a module or a prebuilt AAR, rename the handful of calls that moved between 2.x and 4.x, and prove the library works with a test that runs in seconds.

Why the project cannot find its package

A 2.4-line Android project does not carry OpenCV inside its own APK. At startup it asks, by version string, for a separate app called OpenCV Manager; the Manager supplies the native library and calls the activity back when it is ready. The Manager was retired from the supported workflow and app stores no longer carry it, so the handshake never completes: the activity stays blank and the package error repeats on every launch.

Stop looking for a Manager APK. The supported route ships the library inside your build: include the official SDK folder as a Gradle module, or depend on an AAR. Both end at the same place — a self-contained APK that loads its own native code and never contacts a Manager.

What you need

  • The legacy project, with its Manager initialization still in the activity's startup path.
  • The official OpenCV Android SDK from the 4.x line, taken from the OpenCV project itself: the published archive, or an AAR you build from the official sources. Skip third-party repackagings, so you know exactly which sources your binary came from.
  • Android Studio with the Gradle version your project already uses; neither route needs a new plugin.
  • A device or emulator for the final check; prefer a 64-bit one, for a reason that appears in step 7.
  • Roughly an hour for an activity the size of the worked example below.

The migration, step by step

  1. Delete the Manager handshake from the activity. Remove the asynchronous initialization call and its ready-callback; nothing in the new route uses them. Note where they sat, because the replacement load call goes at the same point in the startup order.
  2. Unpack the official SDK archive into the repository, in a top-level folder, and commit it. Every machine that clones the project then builds against an identical copy.
  3. Declare that folder as a Gradle module by adding one include line to settings.gradle; the module name is yours to choose:
// settings.gradle — ':opencv' is the module name chosen here for the SDK folder
include ':opencv'
  1. Make the app module depend on it in its build file:
// app module build file — the same module name you chose in step 3
implementation project(':opencv')

The alternative is an AAR: build one from the official sources, following the custom Android SDK and AAR build instructions in the OpenCV project's own wiki, then add that single file as the dependency. Use one route, not both — mixing them puts two copies of the library on the build path.

  1. Load the native library once, before the first Mat is created, at the spot you cleared in step 1. The 4.x Java binding ships a one-call initializer that loads the JNI library, which is named opencv_java4:
// startup guard — the binding calls are the documented API; the exception message is a chosen string
if (!OpenCVLoader.initLocal()) {
    throw new IllegalStateException("OpenCV native library failed to load");
}

On 4.x builds older than the initializer, load the same library directly with System.loadLibrary("opencv_java4"). Either way, no Manager exists in the flow any more.

  1. Rename the calls that moved. In the 2.4 line, image file reading and writing lived in Highgui; 4.x keeps the same functions with the same arguments in Imgcodecs, so each call is a rename — Imgcodecs.imread(path) replaces Highgui.imread(path). Color-conversion and filter flags stay on their module classes, such as Imgproc. If the project also contains JNI C++ written against the old C-language interface, that interface was removed in the 4.0 release and only those files need a rewrite against the opencv2 C++ headers; the OpenCV project's migration guide, on its wiki, maps the moves across versions. A Java-only activity avoids that work entirely.
  2. Fix the ABI list. The 4.x SDK ships native libraries for arm64-v8a, armeabi-v7a, x86 and x86_64, while a 2.4-era project often packages 32-bit ABIs only. A 64-bit-only phone cannot load a 32-bit library, so step 5 would throw on current hardware even with a green build. Keep arm64-v8a, keep armeabi-v7a only for old 32-bit phones, and add x86_64 if you test on a desktop emulator.
  3. Build, then inspect the merged APK straight from the build output: the OpenCV native library must appear under every packaged ABI folder. If one is missing, the two places to look are the module include from step 3 and the ABI filter from step 7.

Worked example: a 2.4-line barcode activity moves to a bundled 4.6.0 build

The case: a small activity that reads a barcode from a saved image file. Its inputs, with their sources: the old code is a 2.4-line project that initializes through the Manager and fails with "Package not found" (the situation described above); the target is a 4.6.0 build from the official 4.x SDK; the call inventory below is an illustrative count for an activity this size — tally your own code against the same rows.

The rule in words: count the seams the migration touches. Each seam is a deletion, a rename or a one-line addition; the counting pipeline itself, the Mat and Imgproc calls, contributes nothing to change.

Seam 2.4-line build Bundled 4.6.0 build Change
Manager init and callback touches 4 1 4 removed, 1 load call added
File read/write calls (Highgui) 9 9 renamed to Imgcodecs, arguments unchanged
Mat and Imgproc pipeline calls 6 6 unchanged
Manager references left in code 4 0 all deleted
Packaged ABIs 1 (armeabi-v7a) 2 (added arm64-v8a) one filter line added

The arithmetic: 4 initialization touches, minus the 4 deleted, plus the 1 load call, leaves 1 startup call. The 9 file calls move as 9 renames and 0 signature changes. The 6 pipeline calls move as 0 edits. Counted as operations: 4 deletions, 9 renames, 6 no-ops, 1 addition — 20 changes in total, of which the algorithm code supplies 0.

Decision: when your tally shows init, Highgui and the ABI list and nothing else, the job is mechanical, and an hour is a fair budget. The moment the inventory shows JNI C++ files on the old C interface, those files carry the only genuine rewrite, and you budget them separately — roughly a day each is a realistic planning number.

Prove the library works before you trust it

Three checks, ordered by how early each catches a failure.

Build level: the merged APK lists the OpenCV native library under every packaged ABI folder. Read it from the build output before installing anything.

Load level: the guard from step 5 either passes silently or throws on the first line of startup. When it throws, suspect the ABI list first and a leftover Manager reference second.

Function level: a round-trip test proves the bound library computes, not merely loads. This unit test builds a 2×2 matrix of ones and sums it:

// JUnit test in the app module — org.opencv calls are the documented API; the values are chosen for the test
Mat m = new Mat(2, 2, CvType.CV_64F);     // 2 rows, 2 columns, 64-bit float
m.setTo(new Scalar(1.0));                  // every element set to 1.0
double total = Core.sumElems(m).val[0];    // documented sum over all elements
assertEquals(4.0, total, 1e-9);            // 4 elements × 1.0 = 4.0

The assertion fails if the binding, the library or the ABI is wrong, and 4 elements at 1.0 each must sum to exactly 4.0. A green run here exercises the same classes your barcode activity uses — Mat, Imgproc and the Bitmap conversion utility — so the capture-and-convert path is bound and working.

Common mistakes that keep the old project broken

  • Keeping any Manager reference at all. A single leftover can pull a second copy of the library onto the build path or fail the load step. Search the whole project for the Manager name; the only passing count is zero.
  • Packaging 32-bit ABIs only. The build stays green, and step 5 throws on any 64-bit-only phone. This is the most common reason a migrated project runs on an old test device and fails on a current one.
  • Expecting the contributed modules. The base Android SDK carries the core modules only; if old code calls a contributed module, build a custom SDK or AAR that includes it, following the wiki instructions referenced in step 4.
  • Letting the SDK copy drift between machines. Two developers building against different SDK folders produce two different binaries from one repository. Pin the folder or the AAR, the same discipline as pinning one library version in every containerized build.
  • Missing opencv2 headers in a mixed Java and C++ project. When the native part cannot find its headers after the dependency change, the module include path is misconfigured — the same diagnosis as when a camera build cannot find its OpenCV headers.
  • Treating post-swap build errors as migration failures. If the project compiled before you touched it and broke after the dependency change, work through the SDK build errors that follow a library upgrade before rewriting anything.

More build-level diagnostics of this kind are collected on the systems topic page.

When the app was only ever a prototype

An Android build earns its keep when the inspection travels: a handheld check, a service tool, a demo on a phone. When the job is a fixed station watching the same part, the packaging layer buys you nothing. The same 4.x pipeline you just migrated runs as a desktop build on x86 vision hardware, with no Manager, no AAR and no APK to size.

For a single-camera check that must inspect on the device itself, use a smart camera on the x86 platform. The LS-SMC-0017 pairs an 8.8 MP sensor with a C-mount and global shutter on that x86 platform.

Smart cameras & vision sensorsSC / SC smart camera (X86 platform), 8.8 MP, USB3, Color/Mono, global shutter (LS-SMC-0017)LS-SMC-0017
Interface
USB 2.0
Resolution
6-10 MP
Sensor format
1"
Shutter
Global
$1,359.00View part

When the station needs several cameras, strobed lighting and a PLC handshake, a vision controller takes the cameras directly and drives lights from its own channels. The MV-VC2140-128G40-1T pairs an Intel N-series processor with 8 GB of memory and drives lights from four built-in channels.

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

Either way the port is small: the Mat and Imgproc logic you migrated runs through the same 4.x API in a desktop build. To narrow hardware choices, list the cameras, light and handshake in the system configurator at the controller step, or start from the smart camera finder.

Before you ship: the short checklist

In the code

  • Every Manager reference deleted; the project-wide count is zero.
  • One include line in settings.gradle and one module dependency in the app build file — or one AAR, never both.
  • One native-library load call before the first Mat.
  • Every Highgui file call renamed to Imgcodecs; pipeline calls untouched.

In the build

  • arm64-v8a in the packaged ABI set.
  • The merged APK shows the OpenCV native library under every packaged ABI.

On the device

  • The app opens with no Manager prompt, and the step 5 guard passes.
  • The sum test reads 4.0.
  • One real capture converts to a Bitmap and renders on screen.

Questions

Can I still get the OpenCV Manager app onto a device?

For practical purposes, no: app stores no longer carry it, and sideloading a stray copy would keep you on a retired initialization path the 4.x workflow does not support. The supported route is to bundle the library — include the official SDK folder as a Gradle module or depend on an AAR, so the APK carries its own native code and never asks for a Manager.

Does the whole 2.4 Java activity need a rewrite for OpenCV 4?

No. The org.opencv Java API carried Mat, Imgproc, Core and the Bitmap conversion utility across the version change, so the processing pipeline compiles as it stands. What breaks is narrow: the Manager initialization, the Highgui file functions (renamed to Imgcodecs with the same arguments), the JNI library name, and any JNI C++ written against the removed C interface.

Module include or prebuilt AAR — which route should I pick?

Pick the module include when you expect to rebuild OpenCV, add contributed modules or step into native code while debugging; the sources stay inside your repository. Pick the AAR when the team only needs one pinned artifact — a single file you can version, share and audit. Commit to one route per project, because mixing them puts two copies of the library on the build path.

Which ABIs should the migrated build package?

arm64-v8a is the one you cannot skip: current phones run 64-bit only, so a 32-bit-only library fails to load at startup even though the build itself is green. Keep armeabi-v7a only if you must run on old 32-bit handsets, and add x86_64 for desktop emulators. The 4.x SDK ships native libraries for all four ABIs.

How do I confirm which OpenCV version the app actually bundled?

Log the version constant from the core class (Core.VERSION) at startup, right beside the library-load call. The constant reports the version the native code was built with, so a 4.6.0 build logs 4.6.0; any other value means an older SDK copy is still on the build path. It also catches the two-copies mistake, because the logged value names the copy that really loaded.