This document is intended for developers integrating the Ad SDK, explaining how to capture frames of the ad screen within the display callback in two scenarios: banner/native (with container) and splash/interstitial/rewarded Video (without container). These frames can be used for material archiving, random checks, or reporting. Please refer to the Ad SDK's display API and callbacks for direct implementation.
- Click to download:secmtp
- Click to download:thinkup
| ability | illustrate |
|---|---|
| Frame capture | Advertisement View, full screen/overlay page, or native image url-only (meta.image_url) → JPEG (≤ configurable KB, url-only no local file) |
| Metadata | AdInfo + your passed AdTimingInput + device field → meta |
| Report | POST {baseUploadUrl}/v1/creative/upload(app_id、sign、meta、image) |
| Offline queue | SQLite;Retry after delay in case of no network or transport layer failure (does not count towards maxUploadAttempts); Daily quota throttling |
| Error code | End-side 6xxx (§8.1) + Gateway 0 / 1xxx ~ 5xxx (§8.4) |
Add the following to the dependencies section of the module's build.gradle file:
dependencies {
implementation files("libs/material-monitor-release")
// The advertising SDK dependency must be retained to ensure that AdInfo exists at runtime.
}
minSdkVersion:The build.gradle file for this repository is set to 21; if the host application has a higher value, the host application's value will prevail.
Java 8+。
Network permissions:Reporting requires the host to have INTERNET. The module has declared ACCESS_NETWORK_STATE (used for pre-checking whether there is an available external network before uploading; after merging the manifest, the system grants "view network connections" type permissions).
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
In the first Activity.onCreate call (which internally registers lifecycle callbacks to track the foreground Activity, see demo):
import com.github.material.monitor.MaterialMonitor;
import com.github.material.monitor.MaterialMonitorConfig;
public class FirstActivity extends Activity {
@Override
public void onCreate() {
super.onCreate();
// The developer user agreement allows them to obtain and populate device information independently.
MaterialMonitor.init(this, new MaterialMonitorConfig.Builder()
.baseUploadUrl("https://your-gateway-host") // Upload address
.appId("YOUR_APP_ID")
.appKey("YOUR_APP_KEY") // Please do not disclose.
.oaid(oaidString)
.androidId(androidIdString)
.gaid(gaidString)
// .imei(...).mac(...)
// .maxUploadAttempts(3)
// .retryDelayMs(3000L, 10000L)
// .connectTimeoutMs(15000).readTimeoutMs(30000)
// .debugPerfLogging(BuildConfig.DEBUG)
.build());
}
}
MaterialMonitorConfig.Builder Parameters at a glanceClasspath::com.github.material.monitor.MaterialMonitorConfig。After construction, it can be read via MaterialMonitor.get().getConfig(); isUploadConfigured() is true when baseUploadUrl, appId, and appKey are all non-empty (a prerequisite for automatic enqueueing and reporting).
| Builder method | default | Required (for reporting) | illustrate |
|---|---|---|---|
baseUploadUrl(String) |
"" |
Yes | Gateway root address, only host (can include port), does not include path. The client will construct POST {baseUploadUrl}/v1/creative/upload. Example: https://api.example.com. Leading and trailing whitespace will be trimmed. |
appId(String) |
"" |
Yes | TopOn assigns an Application ID in the developer backend, used for the multipart form field app_id and signature. |
appKey(String) |
"" |
Yes | The signing key (HMAC-MD5, see Gateway Documentation §3) paired with appId. Do not write it to a public repository or plaintext log; remote distribution or hardened storage is recommended. |
oaid(String) |
"" |
Optional | Write to meta.device_info.oaid. Android anonymous device identifier; must be collected and transmitted by the host after user consent to privacy. |
androidId(String) |
"" |
Optional | Write meta.device_info.android_id. |
gaid(String) |
"" |
Suggestion | Write the Google Advertising ID to meta.device_info.gaid. |
imei(String) |
"" |
Optional | Write to meta.device_info.imei; due to system version and permission restrictions, this is not necessary in most scenarios. |
mac(String) |
"" |
Optional | Writing to meta.device_info.mac; compliance-wise, this is generally not recommended. |
maxUploadAttempts(int) |
3 |
No | Maximum number of uploads per task, including the initial request (3 = 1 initial upload + a maximum of 2 retries). Counting only for gateway retryable failures (e.g., 4xxx/5xxx); no-network latency (6006) does not count towards this budget (§9.2). Exhausted → RETRY_EXHAUSTED (6007). Minimum value is 1. |
retryDelayMs(long firstMs, long secondMs) |
5000, 15000 |
No | The number of milliseconds to wait after the first and second retry failures; for the third and subsequent failures, secondMs is reused. When using 4001 rate limiting, the SDK will take max(configured delay, 1s).。 |
connectTimeoutMs(int) |
15000 |
No | HttpURLConnection connection timeout (milliseconds), internal lower limit 3000. |
readTimeoutMs(int) |
30000 |
No | HttpURLConnection read timeout (milliseconds), internal lower limit 3000. |
debugPerfLogging(boolean) |
false |
No | When set to true, outputs the time consumed in stages such as [frame], [bitmap], [storage], and [upload] (Logcat tag includes MaterialLog@ + module version), for performance testing; Release version is recommended to disable. |
Collect only, do not upload:However, MaterialMonitor.init(this) will use the default empty configuration; in this case, automatic enqueueing / reportFrameRecord will result in ErrorCodes.NOT_READY due to incomplete upload parameters (see §8).
Call this function in callbacks where the ad has been displayed, the target area has been laid out, and the ad is still attached (e.g., onAdShow, banner display callback, or after native data binding).
FrameOptions.delayMs indicates how many milliseconds to wait from the time of the call before actually taking a screenshot. This is used to avoid situations such as blank screens, the first frame of the footage or video not being ready, or slow loading on a weak network.
Considering the difference that video materials buffer slower in weak network conditions, while static or HTML materials buffer relatively faster, the following suggestions are recommended (which can be deployed in remote configuration and fine-tuned according to traffic levels):
| Ad format | Recommendation: delayMs |
illustrate |
|---|---|---|
| Rewarded Video | 3000 ~ 5000 ms | Video streams and overlays are often affected by network conditions; streams that are too short can easily result in black screens or undecoded frames. You can start observing the integrity rate online from 2 seconds, and increase it to 3-5 seconds if necessary. |
| Splash / Banner / Interstitial / Native and other non-excited videos | Approximately 2000 ms | Compared to incentive videos, which have slightly lower "first frame ready" requirements, a 2-second frame is sufficient to maintain both compliance and user experience on most devices and networks. |
The above is for the routine monitoring of "only capturing one frame per display"; it can be used in combination with "multiple frames before excitation is turned off" in §4.3.
To more accurately identify illegal screens, abnormal jumps, and end-frame inducements, you can use delayMs(0) to perform real-time frame capture at the node where the screen is stably visible before the ad is about to close or the user is about to leave, and call it multiple times at business intervals (e.g., once every 5 seconds).
Notice:
delayMs(0) means no extra waiting, but still requires that View has been laid out and still attached to the window when called; if called too early, it may still fail (VIEW_INVALID / black frame), please put the "0 delay call" in a callback that is later in the lifecycle and the content has been displayed.collectFrameFrom generates a new FrameRecord.uploadTaskId (queue idempotent key), the same ad display can be captured and reported multiple times; the gateway side display dimension is still consistent with AdInfo.getShowId() and meta.event_info.maxLongEdge) standard of 1080. If there is a high frequency requirement, the maxLongEdge standard should be reduced to 720 or 540. High-frequency frame capture will increase the risk of ANR or excessive memory usage.Example (pseudocode: sample one more frame at a short interval before the excitation is turned off, delayMs is 0):
FrameOptions urgent = new FrameOptions.Builder().delayMs(0).maxSizeKb(300).build();
MaterialMonitor.get().collectFrameFromAdContainer(rewardContainer, adInfo, timing, urgent, callback);
For native self-rendering ads, please select the API (Demo reference: NativeAdActivity for images, NativePatchVideoActivity for videos) according to the Main Creative Type:
| Material Type | Recommended API | illustrate |
|---|---|---|
| Images (large static images, etc.) | collectFrameFromNativeAdContainer |
When a valid http/https creativeAssetUrl is passed in (such as getAdMaterial().getMainImageUrl()), the local screenshot is skipped, and only meta.image_url (the gateway url-only path) is reported; invalid or non-http(s) URLs will fall back to take a screenshot of targetView. |
| video | collectFrameFromAdContainer |
The video frames reside in the SurfaceView hardware layer and require container screenshotting + PixelCopy; do not rely on static main image URLs. |
creativeAssetUrl:Image scenario: Send if available; must begin with http:// or https://. A blank space or an invalid protocol will not fail the entire call, but will instead revert to a screenshot.FrameRecord.file will be null, and multipart will not include the image part; specific required rules are subject to the gateway documentation.Package Name:com.github.material.monitor。Unless otherwise specified below, all data collection-related callbacks are performed on the main thread (see thread description in the external implementation plan).
MaterialMonitor.init / get / getConfig| method | illustrate |
|---|---|
static void init(Application application) |
Uses the default MaterialMonitorConfig (without upload parameters). Suitable only for scenarios where data is only written to disk and no gateway is connected. |
static void init(Application application, MaterialMonitorConfig config) |
Recommended. When config cannot be null, use DEFAULT, which is equivalent to an empty configuration. |
static MaterialMonitor get() |
Retrieves the singleton instance. Returns an idle placeholder instance if init is not initialized or shutdown has been initialized (for collect*, etc., it is no-op and logs a warning), without throwing an exception. |
static boolean isReady() |
Has it been initialized and not yet shut down? (A real instance can be lazily created during the first get().) |
static void shutdown() |
Release lifecycle callbacks and worker; SQLite rows to be uploaded are retained and can be resumed by init later. |
MaterialMonitorConfig getConfig() |
Read the initial configuration; you can call isUploadConfigured() to check if it has baseUploadUrl + appId + appKey. |
collectFrameFromAdContainersign
void collectFrameFromAdContainer(
View targetView,
AdInfo adInfo,
AdTimingInput timing,
FrameOptions options,
FrameCallback callback
)
| parameter | type | Is it possible to null | illustrate |
|---|---|---|---|
targetView |
View |
No | The actual rendering area (container) for the advertisement. After the delay ends, it will be checked whether it is still attached; during the delay, it is only held by a weak reference to avoid leakage. |
adInfo |
AdInfo |
No | The currently displayed TopOn ad information is used for placementId, showId, and style enumeration MaterialStyle, etc. |
timing |
AdTimingInput |
No | Request/populate/display time (milliseconds), written to the gateway event_info (converted to seconds within the database). |
options |
FrameOptions |
No | Use FrameOptions.defaults() when passing null. |
callback |
FrameCallback |
No | Sending null will result in no data collection/reporting callback, which is generally not recommended. |
Behavior: After successful verification, postDelayed(delayMs) is executed, at which point a screenshot is taken → the image is compressed and written to disk → onCollectSuccess in the main thread → if upload is configured, enqueueReport is executed automatically. If it fails, onCollectFailure is executed (the image is not enqueued).
Style Directory: MaterialStyle is resolved internally based on AdInfo and used for local subdirectory names (such as rewarded_video). It corresponds to the meaning of the gateway ad_type and does not need to be passed in by the caller.
collectFrameFromAdPagesign
void collectFrameFromAdPage(
Activity hostActivity,
AdInfo adInfo,
AdTimingInput timing,
FrameOptions options,
FrameCallback callback
)
| parameter | type | Is it possible to null | illustrate |
|---|---|---|---|
hostActivity |
Activity |
No | The caller is the current page (the Activity that displays the ad/hosts the callback, such as the Demo page). This is used to exclude the window during delayed screenshots, preventing accidental captures of the host page in transparent overlay or independent AdActivity scenarios. Invalid (null / finishing / destroyed) → PARAM_INVALID. |
adInfo / timing/ options / callback |
Same as above | Same as above | Consistent with container collection. |
Applicable to: Interstitial/incentive ads, etc. When there is no stable business-side ad container, or when it is difficult to retrieve child Views in full-screen coverage (Demo: InterstitialAdActivity, RewardVideoAdActivity, pass in this).
Common reasons for failure: No available Activity after the delay / Capture View → ACTIVITY_INVALID; Weak reference Activity being recycled, etc.
示例:
MaterialMonitor.get().collectFrameFromAdPage(
InterstitialAdActivity.this, adInfo, timing,
new FrameOptions.Builder().delayMs(3000).build(), callback);
onAdClosedsign
void onAdClosed(AdInfo adInfo)
| parameter | illustrate |
|---|---|
adInfo |
Currently closed ads; press AdInfo.getShowId() to match collection tasks that are still waiting in delayMs. |
Behavior: If the same showId's collectFrameFromAdContainer or collectFrameFromAdPage has not yet executed a screenshot, then cancel this collection and call back onCollectFailure(COLLECT_CANCELLED, ...) to avoid capturing a blank page or the host page even when the ad is turned off.
When to call: Called in the ad's close callback (Demo: onInterstitialAdClose, onRewardedVideoAdClosed). Recommended to call: For interstitials displayed on the Dialog/overlay/host Activity; can be omitted if the independent full-screen AdActivity has a sufficient lifecycle.
@Override
public void onInterstitialAdClose(AdInfo adInfo) {
MaterialMonitor.get().onAdClosed(adInfo);
}
reportFrameRecordsign
void reportFrameRecord(FrameRecord record, AdInfo adInfo)
void reportFrameRecord(FrameRecord record, AdInfo adInfo, ReportCallback callback)
| parameter | illustrate |
|---|---|
record |
The screenshot path must contain a already existing local JPEG file; when using url-only capture, file is null and is automatically enqueued by collectFrameFromNativeAdContainer, generally requiring no manual call. Enqueuing uploadTaskId when it is empty will generate a new task_id. |
adInfo |
Consistent with the original display, this is used to assemble the ad fields in the meta tag. |
callback |
Only report results: onReportSuccess / onReportSkip / onReportFailure (no onCollect). |
Typical Uses: First collect data and write it to disk, then upload it at an opportune time; or re-upload after a successful data collection but a previous upload failure (note the semantics of uploadTaskId and duplicate queuing).
flushPendingFramesvoid flushPendingFrames()
Function: Actively requesting modules to process, as quickly as possible, any remaining pending upload tasks in SQLite (internally scheduling the drain in the upload executor, which is from the same source as the processing triggered by "new task queuing"). It can be called from any thread; internally, it switches between the upload thread and the main thread's Handler, and does not perform HTTP operations on the calling thread.
Typical Use Cases (Select as needed; generally harmless when combined with "Automatic Drain for New Tasks Joining the Queue"):
| Scene | illustrate |
|---|---|
| Network from unavailable to available | Tasks in the NETWORK_ERROR delayed path are still in the library; calling NetworkCallback.onAvailable or a custom "networked" check can shorten the interval until the next approximately 30s timed probe. |
| After the user turns on Wi-Fi/cellular, they return to the App. | In onResume or a network callback, use flushPendingFrames(). |
| Daily quota / After the traffic restriction is lifted | After blocking the window, you can use flush again based on business needs. |
Generally unnecessary: Automatic drain during in-process enqueueing/init is sufficient to upload gradually after network recovery; without global network monitoring, it's enough to call at least once the network availability callback is available and the main Activity's onResume function is invoked.
Relationship with "No Network Delay": flush only wakes up the upload queue and does not re-capture; when there is no network, the pre-check will still fail and be re-scheduled within the drain, but it is more timely than waiting for the timer to run.
Note: flush does not bypass the daily quota gate; if local files have been deleted or the task has ended, no uploads can be performed. See §9 for details on network delays, callback semantics, etc.
FrameOptions and AdTimingInputFrameOptions.Builder| Method / Field | default | illustrate |
|---|---|---|
delayMs(long) |
500 |
≥ 0. It is recommended to adjust according to §4.2: Excitation 2 ~ 5s, others approximately 2s; 0 can be used for the fixed point before shutdown. |
maxSizeKb(int) |
300 |
Output JPEG limit, aligned with the gateway's "single image ≤300KB"; allowed range 80, 300 (exceeding this limit will result in clamping). |
maxLongEdge(int) |
1080 |
Maximum pixel limit for longest side; allowed 540, 1440 (exceeding this limit will result in clamping). Default is 1080, a trade-off between the 300KB gateway limit and readability for review; for higher recognition requirements, adjust to 1440 and verify JPEG size. If frame capture frequency is high, it tends towards 720 or lower. |
keepQualityFirst(boolean) |
true |
Prioritize higher JPEG quality, then reduce/downsize until maxSizeKb is met. |
Build: new FrameOptions.Builder().delayMs(2000).maxSizeKb(300).build()。
AdTimingInput| Build / factory | illustrate |
|---|---|
new AdTimingInput(requestTimeMs, fillTimeMs, showTimeMs) |
requestTimeMs:Advertising request timing、fillTimeMs:Ad fill timing、showTimeMs:Advertising display timing, Milliseconds; converted to seconds when written to meta.event_info. |
The gateway's required semantics for request_ts / fill_ts / show_ts are subject to the integration documentation; for items with values ≤0 , the database will backfill according to the equivalent seconds of show_ts (see CreativeMetaBuilder), it is still recommended to pass in the actual three-stage time in the production environment.
FrameCallback / AbstractFrameCallback)AbstractFrameCallback() {
@Override
public void onCollectSuccess(FrameRecord record) {
// Main thread: When the screenshot is successful, the file is already written; when url-only, the file is null and only the image_url is included.
}
@Override
public void onCollectFailure(int errorCode, String message, Throwable cause) {
// Main thread: Uploads not yet in queue
}
@Override
public void onReportSuccess(FrameRecord record) {
// Main thread: Upload successful; record.getFile() is usually null (the library for JPEG has been removed).
}
@Override
public void onReportSkip(FrameRecord record, String reason) {
// Main thread: repetitive tasks, quota blocking, etc.
}
@Override
public void onReportFailure(int errorCode, String message, FrameRecord record, Throwable cause) {
// Main thread: record may be null (terminal failed and has been cleaned up).
}
}
MaterialMonitor.get().collectFrameFromAdContainer(target, adInfo, AdTimingTracker.mock(), options,
MaterialMonitorHelper.demoFrameCallback(this));
If the frameCallback needs to hold the activity, two usage paths are recommended to avoid memory leaks:
Activity.this method: refer to MaterialMonitorHelper.demoFrameCallback(activity) for weak reference to the activity;Activity.this method: can be used in the activity onDestroy interface, passing in the same instance as collect frameCallback: MaterialMonitor.get().detachCallback(frameCallback);
com.github.material.monitor.ErrorCodes)In the SDK callback, the errorCode values are all 6xxx client-side codes, which are not the same set of values as the code (1xxx ~ 5xxx) in the gateway's HTTP response JSON. When troubleshooting gateway issues, please check the gateway NNNN: … prefix in the failed message and refer to the CreativeAuditGateway - SDK Integration Documentation.
| code | constant | Callback | terminal | Meaning and handling suggestions |
|---|---|---|---|---|
| 6001 | VIEW_INVALID |
onCollectFailure |
Yes | If the container is empty, not attached, or the View has been destroyed after delayMs, call this in the callback that is displayed and still attached; appropriately increase the size of delayMs. |
| 6002 | ACTIVITY_INVALID |
onCollectFailure |
Yes | No usable page after full-screen capture delay → collectFrameFromAdPage passed valid hostActivity; confirms the ad page is still in the foreground. |
| 6003 | COLLECTION_FAILED |
onCollectFailure |
Yes | Screenshot failure (including PixelCopy failure) → Use a container API for video classes; older versions may only support View.draw, you can increase the size of delayMs. |
| 6004 | COMPRESS_FAILED |
onCollectFailure |
Yes | JPEG exceeding maxSizeKb budget → Reduce maxLongEdge or maxSizeKb, or shrink the frame capture area. |
| 6005 | PARAM_INVALID |
onCollectFailure / onReportFailure |
Yes | Invalid input parameters (e.g., adInfo, timing, hostActivity) or reportFrameRecord if the local file does not exist. |
| 6006 | NETWORK_ERROR |
onReportFailure |
No | No network or transmission failed; tasks are still queued, not exceeding the retry limit → After network recovery, flushPendingFrames() (§9.2); onReportSuccess may still be available after success. |
| 6007 | RETRY_EXHAUSTED |
onReportFailure |
Yes | Upload retries exhausted → Check the gateway code in message; if re-upload is needed, use a new uploadTaskId to re-upload reportFrameRecord or re-collect data. |
| 6008 | NOT_READY |
onCollectFailure / onReportFailure |
Yes | The following errors may occur: init not configured, shutdown already configured, or baseUploadUrl/appId/appKey not configured correctly. |
| 6009 | COLLECT_CANCELLED |
onCollectFailure |
Yes | The ad closes before a screenshot is taken (onAdClosed) – expected behavior; calling onAdClosed in the close callback can prevent accidental capture. |
| Callbacks | illustrate |
|---|---|
onCollectFailure |
Acquisition/write to disk failed, or onAdClosed was canceled; automatic queuing for upload will not occur. |
onReportFailure |
Chain reporting failed; 6006 indicates a non-terminal error, while the rest are mostly terminal errors (local tasks may have been cleaned up). |
onReportSkip |
This is not a not an error code; for example, daily quota blocking or duplicate uploadTaskId, check the reason string. |
Collection vs. Reporting: 6001-6004 and 6009 only appear in onCollectFailure; 6005-6008 mainly appear in onReportFailure (6005 may also be triggered at the collection entry point).
code and endpoint errorCode| What you see | illustrate |
|---|---|
Callback errorCode (6xxx) |
SDK data collection chain and reporting errors when there is no HTTP response (such as 6006 No Network). |
Callback errorCode (1xxx ~ 5xxx) |
In some cases where the gateway fails in the final state, the errorCode may be the same as the code in the response body; however, it is still recommended to use the message as the correct value. |
message in gateway NNNN: |
The gateway's original protocol code and text; during integration testing, this field should be parsed first. |
Response body trace_id |
Both success and failure may result in a return value. Please provide this information when contacting TopOn technical support. |
Do not mix the 6xxx endpoint codes with the gateway codes 1xxx to 5xxx in the same set of tracking points. See §8.4 for a complete list of gateway codes.
The upload interface POST .../v1/creative/upload returns JSON, where code == 0 indicates success (HTTP is usually 200). On failure, code will be non-zero, and trace_id may be returned on both success and failure. Please log failures for easy troubleshooting.
The following is consistent with Section 8 of
document/CreativeAuditGateway - SDK Integration Documentation.md; if there are any additions or deletions to the gateway version, the online interface documentation shall prevail.
| code | msg(example) | illustrate |
|---|---|---|
| 0 | ok | 上Successfully transmitted; even if the same event_info.show_id is reported repeatedly, it will still return 0 (idempotent, quota will not be deducted repeatedly). |
| code | HTTP | msg(example) | illustrate | Recommended treatment |
|---|---|---|---|---|
| 1001 | 401 | unauthorized | Signature verification failed or app_id is invalid |
Check appKey, signature algorithm, and appId |
| 1002 | 403 | forbidden | Material monitoring capability not enabled | Contact TopOn operations to activate your account. |
| code | HTTP | msg(example) | illustrate | Recommended treatment |
|---|---|---|---|---|
| 2001 | 400 | invalid params | meta required fields are missing or formatted incorrectly |
Check AdInfo, AdTimingInput, and device fields. |
| 2002 | 400 | image too large | JPEG file over 300KB | Reduce maxSizeKb / maxLongEdge |
| 2003 | 400 | invalid image format | Non-JPEG | Confirm screenshot output is JPEG |
| 2005 | 400 | request body too large | The entire package exceeds 512KB. | Reduce image size or use url-only |
| code | HTTP | msg(example) | illustrate | Recommended treatment |
|---|---|---|---|---|
| 3001 | 429 | account quota exceeded | Daily quota used up | Reporting will cease for the current day; the response or onReportSkip/reason may contain daily_quota_reset_at. |
| 3002 | 429 | app quota exceeded | App daily quota used up | Same as above, for a single App |
| code | HTTP | msg(example) | illustrate | Recommended treatment |
|---|---|---|---|---|
| 4001 | 429 | rate limit exceeded | Too high a request frequency | Wait for a response. Retry after the header Retry-After (usually ≥1s). |
| code | HTTP | msg(example) | illustrate | Recommended treatment |
|---|---|---|---|---|
| 5001 | 500 | upload failed | Server-side storage error | The SDK will automatically retry; if it still fails, it will run RETRY_EXHAUSTED (6007). |
| 5002 | 500 | internal error | Internal server error | Same as above |
| 5003 | 502 | image download failed | Download failed for meta.image_url |
Check URL accessibility (native url-only) |
| 5004 | 503 | service unavailable | Service is temporarily unavailable | Try again later |
gateway code |
SDK behavior | Host Callback |
|---|---|---|
| 0 | Delete local JPEGs and queue lines | onReportSuccess |
| 1001 ~ 1002、2001 ~ 2005 | Terminal failure, clean up local tasks | onReportFailure; message contains gateway NNNN: … |
| 3001 ~ 3002 | Write to the daily quota ban window and clear the current tasks. | onReportFailure or a subsequent new task onReportSkip (reason including daily_quota_reset_at) |
| 4001、5001 ~ 5004 | Press maxUploadAttempts to automatically retry (4001, wait at least 1 second). |
During retry, there is typically no failure callback; after exhaustion, onReportFailure(6007, ...) is executed. |
| No HTTP response / No network | Try again after approximately 30 seconds; this will not count towards your retry count. | onReportFailure(6006, ...) can be used at most once; after network recovery, onReportSuccess can be used. |
uploadExecutor.onReportSkip, reason, and daily_quota_reset_at (see gateway documentation).NETWORK_ERROR = 6006)The upload thread calls NetworkReachability.hasInternet (which requires ACCESS_NETWORK_STATE, merged into the host module's manifest) before actually initiating the HTTP request:
NET_CAPABILITY_INTERNET and NET_CAPABILITY_VALIDATED (the system has determined that it can access the internet), otherwise it is considered to have no network.NetworkInfo.isConnected() is true.If the preflight checks for no network access, or if the HttpURLConnection fails before receiving the HTTP status line (timeout, UnknownHost, no route, etc., the upload result will show httpCode==0 and gatewayCode==-1), then the process will proceed to the delayed path:
| Item | Behavior |
|---|---|
| Retry count | Do not increase retry_count (do not include the "retry failure" budget of MaterialMonitorConfig.getMaxUploadAttempts()). |
| Data | Reserved DB rows and local JPEG |
| Try again at a set time | The processNextUploadTask probe queue will be rescheduled after approximately 30 seconds. |
**onReportFailure |
For the same uploadTaskId, during the period it remains in a "delayed state", NETWORK_ERROR will only be called once to avoid excessive logging/tracking activity. |
**ReportCallback life cycle |
It will not be removed due to this failure; when the same task is uploaded successfully in a subsequent session, you will still receive onReportSuccess (this does not contradict the "failure first, then success" principle; see the ReportCallback JavaDoc for details). |
The host can call MaterialMonitor.get().flushPendingFrames() in places such as NetworkCallback.onAvailable and onResume when returning to the foreground, to shorten the "wait 30 seconds" window (it is harmless to overlap with internal timers).
flushPendingFrames(summary)MaterialMonitor.get().flushPendingFrames();
Actively wake up the queue once using drain; can be called from any thread. See §5.7 table for typical scenarios.
View to avoid including sensitive personal screens.appKey in publicly accessible places is not recommended.| Phenomenon | Possible error codes | Inspection items |
|---|---|---|
The call had no effect / warning not initialized |
— | Has MaterialMonitor.init been initialized? If not initialized, get() will be an idle instance. |
| Data collection failed after delay | 6001 | Is the View still attached? Was the call initiated too early? |
| Interstitial/Incentivized Fullscreen Failure | 6002 | collectFrameFromAdPage: Whether to pass hostActivity; Whether the ad page will still be visible after a delay. |
| Black screen / Incorrect video feed | 6003 | For video, use collectFrameFromAdContainer; increase delayMs (§4.2). |
| The image is too large to be compressed. | 6004 | Reduce maxLongEdge (540 ~ 1440) or maxSizeKb |
| I tried to capture frames even after the ad was turned off, but failed. | 6009 | This is expected; data collection should be completed before closing, or onAdClosed should not be called. |
Always NOT_READY |
6008 | Are baseUploadUrl, appId, and appKey complete? |
Still pending after NETWORK_ERROR |
6006 | Delay upload (§9.2); after network recovery flushPendingFrames() |
| Final state after multiple failures | 6007 / Gateway code | Search message for gateway NNNN: and trace_id (§8.4) |
| Duplicate reporting | —(onReportSkip) |
The same uploadTaskId is only queued once. |
| I want to re-upload the same file | — | Use an empty FrameRecord with uploadTaskId to call reportFrameRecord, or re-collect the data. |
| Native images have no local storage JPEG | — | Valid URLs use url-only; video classes use collectFrameFromAdContainer. |
| Interstitial screenshot of the host page | 6002 / 6003 | Pass in hostActivity; call onAdClosed when closing. |
For the complete error code table, see §8。