InShot(com.camerasideas.instashot)变现与分析技术实现深度分析报告
分析对象:反编译源码
/Users/ccc/Downloads/app/out/jadx_src/sources/,解码清单/Users/ccc/Downloads/app/out/jadx_src/resources/AndroidManifest.xml包名:com.camerasideas.instashot(杭州影笑科技 / InShot Inc.)
0. 总体架构:商业化代码如何组织
核心前提:这家公司的商业化代码是"自研 SDK(代号 Shantanu)+ R8 强混淆"组织起来的。
com.shantanu.*是真实包名(未被完全混淆的部分),但大部分商业化代码被 R8 混淆成De、Ge、Ee、Fe、He、Ie、Je、Le、T、V5、N、p321qa等单字母/乱序包。- 证据 1:
MobileAdsInitializer.java里线程名是"\u200bcom.shantanu.mobileads.utils.InitializeExecutor"(零宽空格前缀,防字符串搜索)。 - 证据 2:
com.shantanu.mobileads.exception.*一整包异常类(9 个)因-keep规则保留了原包名,是反混淆的"锚点"。 - 证据 3:
Le/n.a(int)是 R8 字符串加密的字符串池(运行期按索引返回原始字符串,如"x-custom-event-class-data")。
三层抽象:
- 基础设施层(自研 Shantanu SDK,强混淆)
com.shantanu.mobileads→ 混淆为De(MobileAds/SdkConfiguration/SdkSettings/CmpService)、Ge(日志/常量)、Ee(Waterfall)、Fe(插屏)、He(收益适配器)、Ie(原生/MREC/横幅)、Je(激励/AdView)、Le(字符串池/全局上下文)com.shantanu.iap(服务器端 IAP 验证 + 账户绑定,1484 特性)com.shantanu.mmkv/com.shantanu.storage/com.shantanu.network(MMKV、存储、网络)
- 配置/投放层(未混淆,com.camerasideas)
V5.c(AdDeploy:设备定向、placement 开关)、N.a(最终广告位 ID)、X3.C1246g(AppCapabilities)+com.camerasideas.instashot.remote.d(AppRemoteConfig)+ Firebase RC- 一切可远程下发,本地 assets 有完整回退(
local_ad_waterfall.json/local_ad_deploy_list.json/popup_config.json)
- 业务接入层
- 各 Activity/Fragment 只调
V5.g(Banner)/V5.n(Interstitial)/V5.p(MREC)/V5.r(Reward)单例 - 展示前统一过
S.d(ctx).k()/w()(订阅判定)+V5.c.h(placement)(投放开关)+ 频控 JSON
- 各 Activity/Fragment 只调
关键点速览:
| 主题 | 结论 |
|---|---|
| 广告聚合 | AppLovin MAX + 自研 Shantanu MobileAds;自定义 Waterfall 全部直连 AdMob(GoogleMediationAdapter),非标准 mediation |
| 订阅 | Google Play Billing + 4 渠道 PurchaseManager(含华为);服务器端 Shantanu IAP 账户绑定;Play/Huawei Integrity |
| 分析 | Firebase Analytics + Crashlytics(经 p348ri.M 门面 + 协程 Channel 队列);无 AppsFlyer/Adjust |
| 远程配置 | FirebaseRemoteConfigWrapper → remote.d → X3.C1246g 静态门面 + V5.c 投放 |
| APM | 自定义 CrashHandler(吞第三方 SDK 崩溃)+ 微信 xlog + Crashlytics 日志镜像 |
| 隐私 | Google UMP(混淆在 De 包)+ 自研隐私页 PolicyFragment;AppLovin setDoNotSell(true),TCF 禁用 |
1. 广告聚合:AppLovin MAX + 自定义 Waterfall(AdMob 直连)
1.1 初始化点
类:com/camerasideas/startup/MobileAdsInitializer.java(实现 Application.ActivityLifecycleCallbacks)
初始化模型 = "延迟到主线程空闲 + 单线程执行器 + 订阅用户跳过":
// MobileAdsInitializer.a(Context)
if (S.d(cVarC.f12059a).k()) { // k() = 未购买 remove.ads → 才初始化广告
Iterator it = Arrays.asList("I_VIDEO_AFTER_SAVE","I_PHOTO_AFTER_SAVE",
"R_REWARDED_UNLOCK_","M_VIDEO_RESULT","M_PHOTO_RESULT",
"B_VIDEO_EDITING","B_PHOTO_EDITING").iterator(); // 7 个广告位,任一开启才初始化
...
strC = C1246g.f14024b.h("ad_waterfall_list_v_max"); // 远程 waterfall JSON
if (TextUtils.isEmpty(strC)) strC = B.c(openRawResource(R.raw.local_ad_waterfall)); // 本地回退
... // 硬编码 AppLovin SDK Key: kBiFuolh9J-eNFCitXmimSOs_Gt4IuV04CGy_0e8Kdoht_PkW9QGcp6c4zgMrANvNdsMdUVy6HezG2gnC62pv8
AppLovinSdk.getInstance(context2).initialize(
AppLovinSdkInitializationConfiguration.builder(sdkKey)
.setMediationProvider("max").setAdUnitIds(adUnitIds).build(), new f());
}
onActivityCreated中用Looper.getMainLooper().getQueue().addIdleHandler(...)把初始化延迟到首帧之后(f35677b防重复,f35676a完成标记)。- 远程开关:
prefer_custom_waterfall_mediation(是否用自定义 waterfall)、allow_redirect_custom_waterfall_mediation、mobile_ads_mute(静音)、mobile_ads_log。 - 隐私:
AppLovinPrivacySettings.setDoNotSell(true)、setExtraParameter("user_agent_collection_enabled","false")、禁用 MAX 自带 TCF(settings.getTermsAndPrivacyPolicyFlowSettings().setEnabled(false)),同意流程走自研 UMP(见第 6 节)。
类:De/g.java(MobileAds,混淆后的 Shantanu 广告 SDK 单例)
public final class g {
public static final a f2363a = new a(); // 初始化完成回调持有者
public static boolean f2364b; // isInitialized
public static boolean f2365c; // isInitializing
public static AppLovinSdkConfiguration f2366d;
}
类:De/o.java(SdkConfiguration)+ De/p.java(SdkSettings)
// De/p: 解析 waterfall JSON → map[ad_unit_id] → Ee.e(Waterfall 迭代器)
public static Ee.e a(String str) {
if (map.isEmpty() && f2391b != null) {
JSONArray jSONArray = new JSONArray(f2391b); // f2391b = waterfall JSON 字符串
for (...) map.put(jSONObject.optString("ad_unit_id"), jSONObject);
}
...
}
public static boolean b(String str) { // 该 ad unit 是否走自定义 waterfall
if (f2392c) return true; // preferCustomWaterfall
return f2394e != null && f2394e.contains(str); // customWaterfallAdUnitIds
}
1.2 Waterfall 结构(关键证据)
资源:resources/res/raw/local_ad_waterfall.json(201 行)
12 个 MAX ad unit,共 32 步 waterfall,全部指向 AdMob(com.applovin.mediation.adapters.GoogleMediationAdapter custom event):
{ "ad_unit_id": "820327943890bc87", "ad_unit_name": "b_photo_editing",
"waterfall": [
{ "x-custom-event-class-name": "com.applovin.mediation.adapters.GoogleMediationAdapter",
"x-custom-event-class-data": "{\"placementId\":\"ca-app-pub-4033211073363733/8382207922\",\"format\":\"BANNER\"}" },
{ "x-custom-event-class-name": "com.applovin.mediation.adapters.GoogleMediationAdapter",
"x-custom-event-class-data": "{\"placementId\":\"ca-app-pub-4033211073363733/7069126255\",\"format\":\"BANNER\"}" },
...
广告位 ↔ 格式映射:
| MAX ad_unit_id(示例) | 广告位名 | 格式 |
|---|---|---|
820327943890bc87 |
b_photo_editing | BANNER(3 个 AdMob 位级联) |
7279f670086be16e / d9b7838b717e63f3 |
b_video_editing | BANNER |
2d17c059ef2e19ba |
m_photo_result | MREC 320×250 |
40f525f9e584b412 / c9a8747dce6a96bd |
m_video_result | MREC |
c3d648d209900d60 |
i_photo_after_save | INTER |
ad9961595904b039 / 4b00eed1bfa9f67d |
i_video_after_save | INTER |
57f5a64e43ded401 |
i_material_unlock | INTER |
c415933b89159953 / ef9b460767d45f7e |
reward_ad | REWARDED |
结论:自定义 waterfall 只填 AdMob(同一 AdMob 位 id 可出现在多个 MAX 位下,同一位下 2-3 个 AdMob 位级联做兜底)。远程配置
ad_waterfall_list_v_max可整体替换此 JSON。
解析器:
De/p.java(SdkSettings):按ad_unit_id建索引。Ee/e.java(Waterfall,实现Iterator<a>):每步解析x-ad-timeout-ms(默认 30000ms)、x-custom-event-class-name、x-custom-event-class-data;字段名经Le/k.a(int)、Le/n.a(int)字符串池解密。
其他打包的广告 SDK(未进自定义 waterfall,仅随 MAX 适配器/独立库存在):
- Vungle(
com.vungle.warren)、Unity Ads(com.unity3d.services)、InMobi(com.inmobi.media)、Fyber(com.fyber.inneractive)、Yandex(com.yandex.mobile)、TradPlus(com.tradplus,manifest 里有InterNativeActivity)、PubMatic OpenWrap(com.pubmatic,assets 有 PubMatic_Logo.svg)、com.tp.ads(VAST 视频广告 SDK)。 - 崩溃白名单
C2871v(见第 5 节)的过滤列表即为这些 SDK 存在的佐证。
1.3 广告位 ID 解析:设备定向投放
类:V5/c.java(AdDeploy)+ com/camerasideas/instashot/remote/a.java(AdConfig)
// V5/c.d(): 远程 JSON → 解析,回退本地 raw/local_ad_deploy_list.json
strH = dVar.h("ad_supported_info_android"); // AdConfig: banner 支持、默认 ad unit id
strH2 = dVar.h("ad_deploy_list_v_1530"); // deployNodes: 按 OS/RAM/机型 换 ad unit
this.f12064f = (b) new Gson().fromJson(strH2, ...);
N.a.f7641a = a("I_VIDEO_AFTER_SAVE", this.f12063e.f30980d); // 最终 ad unit id 落盘到 N.a
N.a.f7642b = a("I_PHOTO_AFTER_SAVE", ...);
N.a.f7643c = a("R_REWARDED_UNLOCK_", ...);
N.a.f7644d = a("M_VIDEO_RESULT", ...);
N.a.f7645e = a("M_PHOTO_RESULT", ...);
N.a.f7646f = a("B_VIDEO_EDITING", ...);
N.a.f7647g = a("B_PHOTO_EDITING", ...);
- 数据模型:
b{deployNodes[], selectiveNode{f,adUnitNodes[]}, customWaterfallAdUnitIds[]};d{enable, newAdUnitId, os[], ram[{lower,upper}], device[]};支持*通配与@MODEL前缀、Build.DEVICE.startsWith、内存区间匹配。 h(placement)= 总开关:S.d(ctx).k()(未去广告)&& deployNode 未禁用。
类:N/a.java(AdUnitIdManager,静态最终广告位 ID)
public static String f7641a = "ad9961595904b039"; // I_VIDEO_AFTER_SAVE
public static String f7642b = "ad9961595904b039"; // I_PHOTO_AFTER_SAVE
public static String f7643c = "c415933b89159953"; // R_REWARDED_UNLOCK_
public static String f7644d = "40f525f9e584b412"; // M_VIDEO_RESULT
public static String f7645e = "2d17c059ef2e19ba"; // M_PHOTO_RESULT
public static String f7646f = "7279f670086be16e"; // B_VIDEO_EDITING
public static String f7647g = "820327943890bc87"; // B_PHOTO_EDITING
1.4 展示策略(插屏/激励/横幅/MREC)
插屏(After-Save Interstitial)
- 类:
V5/n.java(InterstitialAds)+V5/l.java(InterstitialAdControl)+Fe/g.java(ShantanuInterstitialAd)+Fe/e.java(MaxInterstitialAd 子类)+Fe/f.java/Fe/q.java(MAX 与自定义 waterfall 两种加载器) - 展示时机:
VideoResultActivity.onResume()/ImageResultActivity的showInterstitialAdIfNecessary:- 条件:
PopupControl类型为 AD、未弹过插屏、无弹窗、非"重试保存"。 - 频控:
V5.l.c(activity)读远程popup_interstitial_adsJSON(enableVideoInterstitial、videoInterstitialInterval、firstVideoInterstitialInterval等),按"距上次插屏的存盘次数"计数(VideoSaveTimesSinceLastInterstitial/PhotoSaveTimesSinceLastInterstitial),首次插屏前用 first 间隔,跨天重置(LastVideoSavedDate)。
- 条件:
// VideoResultActivity.onResume() 片段
if (this.f13770I.getPopupType() == com.camerasideas.instashot.udpate.h.AD && !this.f13771J && !this.f13772K) {
this.f13771J = true;
if (!getIntent().getBooleanExtra("Key.Retry.Save.Video", false)) {
if (V5.l.c(this) && V5.n.f12115b.b(this, N.a.f7641a, "I_VIDEO_AFTER_SAVE")) {
p202k4.o.p0(this, "isFirstVideoInterstitialFinished", true);
p202k4.o.r0(this, 0, "VideoSaveTimesSinceLastInterstitial");
} else {
// 计数 +1
}
}
}
// V5/n.java: 加载/展示,统一过 AdDeploy 开关
public final void a(Context context, String str, String str2) { // 预加载
if (!c.c(contextD).h(str2)) { ... return; } // AdDeploy 不支持 → 放弃
... // Fe.g(ShantanuInterstitialAd) 组装,按 ad unit id 去重缓存
}
public final boolean b(B b10, String str, String str2) { // 展示
if (!c.c(b10).h(str2)) return false;
Fe.g gVar = (Fe.g) this.f12116a.get(str);
... return gVar.f3570f.c(b10, str2); // Fe.a.isReady() + showAd()
}
// Fe/g.java: 加载失败指数退避重试
public static void b(g gVar, Ee.a aVar) {
int i10 = gVar.f3568d + 1; gVar.f3568d = i10;
long millis = TimeUnit.SECONDS.toMillis((long) Math.pow(2.0d, Math.min(5, i10)));
... gVar.f3567c.postDelayed(new i(gVar), millis);
}
// 双路径: De.p.b(adUnitId) ? 自定义 waterfall 加载(Fe.q) : MAX 加载(Fe.f)
if (De.p.b(str)) { e(); return; } // "Use custom waterfall mediation directly"
... fVar.eVar.setListener(dVar); eVar.setRevenueListener(new He.d(...)); eVar.loadAd();
激励视频(Rewarded)
- 类:
V5/r.java(RewardAds)+V5/s.java(加载状态/10s 窗口)+V5/t.java(待发奖队列)+Je/a.java(MaxRewardedAdImpl)+Je/c.java(基类)+Je/q.java - 场景:解锁滤镜/贴纸/素材(placement
R_REWARDED_UNLOCK_),埋点事件unlock_ad_source。 - 策略:预加载 + 10 秒观看窗口;奖励在
onRewardedAdCompleted时发放;is_ad_load_failure_can_rewarded控制加载失败是否仍发奖(宽容策略);reward_ad_load_position控制启动预热。
// V5/r.java: 请求展示入口
public final void g(String str, q qVar, Runnable runnable) {
M.g(InstashotApplication.f24926a, "unlock_ad_source", g0.j(g0.f("R_REWARDED_UNLOCK_", str)), ...);
this.f12123a = str;
f fVar = new f(); fVar.f12092a = runnable; this.f12124b = fVar; // 奖励回调
...
boolean zA = s.f12133d.a(this.f12130h); // 是否已 loaded
if (zA && !s.f12133d.b(str)) {
this.f12129g.sh();
b bVar = new b(); this.f12125c = bVar;
this.f12126d.postDelayed(bVar, this.f12127e); // f12127e = waiting_reward_timeout_millis(默认10000)
}
}
// 完成 → 发放奖励并加载下一条
@Override public final void g(String str, Ee.d dVar) { ... r.a(r.this); } // onRewardedAdCompleted
// Je/a.java: MAX 激励封装
public final class a extends c {
public a(Context context, String str) {
super(context, str);
AppLovinContextFix.ensureContext(context);
this.f6297f = MaxRewardedAd.getInstance(str);
}
public final boolean b() { return this.f6297f.isReady(); }
public final boolean c(Activity activity, String str) {
if (!this.f6297f.isReady()) return false;
this.f6297f.showAd(str, activity); return true;
}
}
横幅(Banner)
- 类:
V5/g.java(BannerAds)+com/camerasideas/mobileads/BannerContainer.java+Ie/d.java(原生广告加载器)+Je/o.java(MaxAdView) - 场景:编辑页(placement
B_VIDEO_EDITING/B_PHOTO_EDITING,经F6.java:3446V5.g.f12093e.b(viewGroup, N.a.f7646f, true)注入BannerContainer)。 - 策略:混合"自推 house ad + MAX 原生横幅":
- 先读远程
app_ad_info_list(自推广告 JSON:icon/标题/文案/CTA/跳转 Play 链接,ad_click_cache缓存最近 10 个点击去重); - 无自推配置则加载 MAX 原生 banner(
Ie.qview_binder +Ie.h配置:banner_refresh_time_millis=60s 刷新、ad_request_time_millis=30s 超时、ad_expiration_time_millis=2h 过期,均可远程覆盖); BannerContainer监听子视图移除(含华为com.huawei.hms.ads.banner.BannerView),支持远程remove_banner_ad关闭按钮。
- 先读远程
MREC(Medium Rectangle)
- 类:
V5/p.java(MediumAds)+Ie/d.java - 场景:图片/视频结果页(placement
M_PHOTO_RESULT/M_VIDEO_RESULT,N.a.f7645e/f7644d),布局native_medium_ad_layout,mrec_refresh_time_millis=60s;支持remove_card_ad关闭按钮。
设备/订阅联动门控(S.java 的 s(boolean)):
public final boolean s(boolean z10) { // z10=true 视频横幅 / false 图片横幅
V5.c cVarC = V5.c.c(context);
boolean z11 = z10 ? cVarC.f12063e.f30979c : cVarC.f12063e.f30978b; // video/photo_banner_supported
if (!cVarC.f12063e.f30977a && !Rf.e.g(context)) z11 = false; // 平板专属开关
if (iA == 5 || iA == 4) z11 = false; // 设备分级
return z11 && cVarC.h(z10 ? "B_VIDEO_EDITING" : "B_PHOTO_EDITING");
}
2. 订阅/内购
2.1 启动流程
类:com/camerasideas/startup/InitializeBillingTask.java(@Keep,启动链 Env → Billing → State → Lib → Resource,见 InstashotApplication.onCreate 中 c0010a.a(InitializeBillingTask.class.getName()))
// updateProInfo()
p321qa.o oVarA = p321qa.o.d(this.mContext).a();
Future futureF = oVarA.f(); // queryPurchaseHistory
Future<z> futureG = oVarA.g(null); // queryPurchases
Future<x> futureE = oVarA.e("subs", C2460y.f31208b, null); // queryProductDetails("subs" 组)
updatePurchaseHistoryRecord(this.mContext, futureF);
updateProInfo(futureG);
updateProductDetails(futureE);
p321qa.o.f60295b = Integer.parseInt(C1246g.f14024b.h("key_subscription_replacement_mode")); // 订阅替换模式,默认3
- DAU 埋点:
sendGoogleProEvent()按 SKU 组发pro_permanent_dau/pro_monthly_dau/pro_yearly_dau/pro_dau(+pro_dau_token_valid),sendInShotProEvent()发all_pro_dau。
2.2 计费抽象层
类:p321qa/o.java(BillingService,抽象基类)
public abstract class o {
public static boolean f60294a = false; // forcePlayBilling(SettingFragment 调试开关可开)
public static int f60295b = 3; // 订阅替换模式(远程下发)
public static a d(Context context) { ... }
// a() → 两个实现:
// z10 ? new p321qa.a(context, mode) // BillingFakePaymentFailure(调试)
// : new g(context, mode) // BillingManager(真实 Google Play Billing)
public abstract Future<i> a(String sku, InterfaceC1513m listener); // launchPurchaseFlow
public abstract void c(j jVar, ...); // consumeAsync
public abstract Future<x> e(String productType, List<String> skus, ...); // queryProductDetails
public abstract Future f(); // queryPurchaseHistory
public abstract Future<z> g(InterfaceC1538z); // queryPurchases
public abstract void h(InterfaceC1503h); // 购买回调
}
p321qa/g.java(BillingManager):真实实现,含查询超时统计、pending purchase 处理("Received a pending purchase of SKU: ...")、购买结果缓存。p321qa/d.java(BillingHelper):g(purchase, skuList)(SKU 匹配)、i(purchase)(purchase.b()==1已购买)、h()(ITEM_ALREADY_OWNED 兼容)、d()(按 basePlan 推断试用天数:P3D=3/P2W1D=15/默认 7)、a/b()(introductory 价格区间选择)。
2.3 SKU 组织(订阅组)
类:com/camerasideas/instashot/store/billing/C2460y.java(SkuDefinition)
public static final List<String> f31207a = Arrays.asList(
"com.camerasideas.instashot.pro.permanent",
"com.camerasideas.instashot.vip.yearly",
"com.camerasideas.instashot.vip.yearly.freetrail",
"com.camerasideas.instashot.vip.yearly.freetrail.introductory",
"com.camerasideas.instashot.vip.monthly",
"com.camerasideas.instashot.vip.monthly.introductory",
"com.camerasideas.instashot.vip.monthly.introductory.hw", // hw = 华为渠道变体
"com.camerasideas.instashot.vip.monthly.introductory.hw2",
"com.camerasideas.instashot.fup"); // fup = 限时解锁功能
public static final List<String> f31208b = /* 订阅(subs)类型,用于 queryProductDetails:
vip.monthly(+introductory)、vip.yearly(+freetrail+introductory) */
public static final List<String> f31209c = /* pro.permanent —— 永久组 */
public static final List<String> f31210d = /* vip.monthly(+intro.hw/hw2) —— 月度组 */
public static final List<String> f31212f = /* vip.yearly(+freetrail+introductory) —— 年度组 */
// 免费试用映射
public static String a(String str) { /* vip.monthly → "p1m"; vip.yearly.freetrail → "p1y"(basePlanId) */ }
public static String b(String str, boolean z10) { /* vip.yearly.freetrail && z10 → "freetrial"(offerId) */ }
public static String c(String str) { /* 订阅 → "subs"; 其他 → "inapp" */ }
2.4 权益判定(核心)
类:com/camerasideas/instashot/store/billing/S.java(UserManager,单例,S.d(context) 获取)
public final boolean k() { return !m("com.camerasideas.instashot.remove.ads"); } // 是否展示广告
public final boolean w() { // 是否 Pro(最高级判定)
if (com.google.gson.internal.s.g(context) || u() || v()) return true; // gson 钩子 / 内购 / 华为
return !A1.j0(context) && Ce.c.a(context, p325qe.b.f60328a).getBoolean("DebugPro", false);
}
public final boolean u() { return E.c(context) || com.google.gson.internal.s.g(context) || x("...fup"); }
public final boolean v() { return prefs.getBoolean("SubscribeProOfHw", false) || gson.s.g(context); }
public final boolean m(String sku) { // 单 SKU 解锁判定:订阅态 || 本地解锁标记 || 时间窗内(fup)
I(context, str); // fup 过期检查:UnlockTimeMillis_<sku> + 有效时长(g())
return w() || E.d(context, str) || prefs.getBoolean(str, false);
}
public final boolean z() { /* 遍历 f31209c(permanent) */ }
public final boolean y() { /* 遍历 f31210d(monthly) */ }
public final boolean A() { /* 遍历 f31212f(yearly,含 freetrail) */ }
public final boolean t() { /* 购买有效标记 */ }
- 多商店渠道抽象:
InterfaceC2448l(PurchaseManager)
static InterfaceC2448l c(Context context, int i) { // 0/1/2/3 四渠道
if (i == 0) return new C2440d(context); // Google Play
if (i == 1) return new C2441e(context); // 华为?
if (i == 2) return new C2442f(context);
return i != 3 ? new C2440d(context) : new G(context); // 3 = Shantanu IAP 渠道
}
// 每渠道提供: a() 聚合有效 PurchaseToken、b() PurchaseInfo 列表、d(sku) 单 SKU token
C2450n.java(PurchaseToken):{platform:int, token:String},a()有效 = platform>-1 && token 非空。C2449m.java(PurchaseManagerImpl):聚合 4 渠道取第一个有效 token。- 持久化:
P.java(UpdateBilling,写购买/商品详情缓存)、C2458w.java(PurchaseUpdater)、D.java(UnlockInfoLoader,磁盘解锁信息)、C2439c.java(SKU 常量表)。
2.5 服务器端验证 + 账户绑定(Shantanu IAP)
类:com/shantanu/iap/v.java(IapServiceImpl,OkHttp 服务端客户端)
public final class v extends AbstractC3588c {
public final Future<BindResult> bindAsync(String accountId, List<PurchaseInfo> list);
public final Future<BindResult> bindByOrderIdAsync(String accountId, String orderId, String ...);
public final Future<QueryAccountInfoResult> queryAccountInfoAsync(String accountId);
public final Future<Result> queryAccountStateAsync(String accountId);
public final Future<QueryOrderStateResult> queryOrderStateAsync(boolean, List<PurchaseInfo>);
public final Future<QueryPurchaseResult> queryPurchasesAsync(String accountId, C);
public final Future<QueryReviewStateResult> queryReviewStateAsync(...); // 审核中状态
public final Future<Result> removeAccountAsync(String accountId);
public final Future<Result> feedbackAsync(...);
}
- 用途:把 Google Play 购买 token 绑定到自研账户(
support_bind_1484远程开关,com.google.gson.internal.s.j()判定),实现跨设备 Pro 同步与服务器端权益校验。 - UI:
com/shantanu/iap/bind/(验证码登录绑定,VerificationCodeView等)。 OwnedPurchase.java:服务端返回的已购项,isAuthorizable()+getProductId()判定(被com.google.gson.internal.s.f()使用)。
2.6 Play Integrity(注意:不在 com.camerasideas.safe,而在 com.android.auth.integrity)
类:com/android/auth/integrity/IntegrityInitializer.java
private static final String META_KEY_HUAWEI_APP_ID = "com.android.auth.HuaweiAppId";
private static final String META_KEY_INTEGRITY_TYPE = "com.android.auth.IntegrityType";
private static final String META_KEY_PROJECT_ID = "com.android.auth.IntegrityProjectNumber"; // ← 用户提到的键
...
String string = bundle.getString(META_KEY_INTEGRITY_TYPE, "google"); // google | huawei
String string2 = bundle.getString(META_KEY_PROJECT_ID, "0"); // "number-xxx"
string2 = string2.replace("number-", "");
gVar.init(context, new h(Long.parseLong(string2), Long.parseLong(string3), string));
类:g.java(IntegrityManager)+ GoogleIntegrityManager.java / HuaweiIntegrityManager.java(反射选择)
// g.init(): 按 type 反射实例化
Class.forName(type.equalsIgnoreCase("huawei") ? "com.android.auth.integrity.HuaweiIntegrityManager"
: "com.android.auth.integrity.GoogleIntegrityManager");
// 接口: requestStandardToken / requestTraditionToken / syncRequestStandardToken / showIntegrityLicenseDialog
- 回调:
com/camerasideas/startup/j.java(IntegrityCallBack):失败 →M.e(new IntegrityException(...))上报 Crashlytics;成功仅记日志。 - 注册点:
InitializeEnvTask.run():com.android.auth.integrity.g.a.f19665a.setCallback(new j())。 - 联动:Integrity/审核态与盗版检测——
InitializeEnvTask校验签名 !=1965C6BCCFBDF28DBE977E5460F386A1DB9E7366则上报pirate_app事件并M.e(InstallSourceException);com.google.gson.internal.s.l()把isInReview写入本地。
3. 分析/埋点
重要更正:
com/camerasideas/track/目录的 43 个文件是视频编辑时间轴 UI(TimelineDelegate、AudiolineDelegate、FilterlineDelegate、seekbar*等),不是埋点框架。真正的埋点框架如下。
3.1 门面与实现
类:p348ri/M.java(分析门面,编译名 DebugStrings.kt)
public final class M implements com.google.gson.internal.q { // ← Gson 实例创建钩子(见 3.3)
public static X3.Q f60737a; // 由 InitializeApmTask 注入: new Q(context, isMainProcess)
public static void c(Context context, String str) { // 自定义事件(select_content)
if (f60737a == null || TextUtils.isEmpty(str)) return;
f60737a.a(context, str, new String[]{"content_type","item_id"}, new Object[]{"",""});
}
public static void d(Context context, String str, String[] keys, Object[] vals); // 自定义事件
public static void e(Throwable th2) { ... new H(th2, null) ... } // → FirebaseCrashlytics.recordException
public static void f(Activity activity, String str) { // screen_view
q7.a(activity, "screen_view", new String[]{"screen_name"}, new Object[]{str});
}
public static void g(Context context, String str, String str2, Object... kv) { // select_content 变体
... "content_type"=str, "item_id"=str2, "item_name"=str2 + 附加 kv ...
}
@Override public Object construct() { return new LinkedHashSet(); } // Gson InstanceCreator 钩子
}
类:X3/Q.java(ISFirebaseListener)+ X3/F.java(DefaultFirebaseAnalytics)+ X3/G.java(logEvent 协程)+ X3/H.java(logException 协程)
// X3/F$LogConsumer: 事件队列(Channel,容量 MAX_VALUE,溢出 DROP_OLDEST)
public final p386ti.b f13838a = p386ti.j.a(Integer.MAX_VALUE, 6, null);
// X3/G.invokeSuspend: 发送前富化
if (!z10) { // 未被 firebase_event_filter_config 过滤
bundle.putString("uuid", aVar2.f14092a);
bundle.putLong("sample_number", aVar2.f14095d);
bundle.putString("user_id", aVar2.f14093b);
bundle.putString("user_identity", aVar2.f14094c); // "default_vip"/"fake_vip"/"normal"
FirebaseAnalytics.getInstance(context).zzh(event, bundle);
}
// X3/H: 异常 → Crashlytics
FirebaseCrashlytics.getInstance().recordException(th2);
类:X3/AbstractC1284t.java(BaseFirebaseAnalytics)——采样与用户身份
// 启动时初始化:
if (Ce.c.a(context, f60328a).getLong("sample_number", -1L) < 0)
Ce.c.a(context, f60328a).putLong("sample_number", Cf.X.a(0L, 10000L)); // 0-10000 随机采样号
String strI = com.camerasideas.instashot.store.billing.S.i(context); // uuid
if (sD.w()) str2 = sD.f31149b.a().a() ? "default_vip" : "fake_vip"; else str2 = "normal";
this.f14090c = new a(sampleNumber, uuid, uuid, userIdentity);
// 之后: FirebaseCrashlytics.getInstance().setUserId(uuid);
// 读取远程 "firebase_event_filter_config"(Ig.c) 决定事件是否上送
3.2 日志管道(队列 + 批量镜像)
- 类:
Dd/e.java(Logger)+Dd/f.java(LoggerPrinter):Dd.e.a(tag).d(level,msg,args)标签式日志;f.e()把日志扇出到可插拔 printer 列表(f2351c);InitializeApmTask会追加startup/l.java(LogPrinter)。 - 类:
X3/E.java(LoggerImpl,协程消费者):从F.a的 Channel 迭代日志串,逐条写入FirebaseCrashlytics.log()——App 日志成为崩溃面包屑(失败则E7.d.a(-1)兜底)。 - 类:
p192je/a.java(Log,全链路日志门面):未初始化 xlog 时走android.util.Log;初始化后按级别转发到com.tencent.mars.xlog.Log(见第 5 节)。 - 类:
p348ri.Z / p481yi.b / p481yi.c:kotlinx-coroutines 的 Job/Dispatcher 基础设施(f64521b= IO 调度器)。
3.3 Gson 钩子(用户提到的"脱敏"疑点,实为锚定类技巧)
com/google/gson/internal/s.java:R8 把 gson 自己的Primitives类改名成s,而应用把自己的类也编进了 gson 包(编译名 Primitives.java,构造器new LinkedHashSet(20))。静态字段在 Application 注入:
// InstashotApplication.onCreate():
com.google.gson.internal.s.f42708a = new J7.k(this); // J7.k = Suppliers(包一个 LinkedHashSet)
注入后该字段被用作特性开关:s.f(OwnedPurchase) 里 f42708a != null ? C2460y.f31207a(新 SKU 列表) : Th.t.f11116a(旧列表);s.j() 读 support_bind_1484 决定是否启用 1484 绑定。同时该类提供 accountId/appUserId/isInReview/isPro 的本地存取(p382te.s MMKV 偏好)与 MD5、网络判断。目的:借用 gson 内部类不被 R8 删除的特性,把应用逻辑"锚"在稳定包内,并兼作跨混淆边界的全局注册表。
p348ri/M.java implements com.google.gson.internal.q:construct()返回LinkedHashSet,是真正的 Gson 实例创建钩子(Y2/b.java、D9/b.java也实现同接口)——埋点门面 M 挂钩 Gson 内部构造器,使事件去重/序列化路径上的集合类型可控。
3.4 第三方归因 SDK
- 未发现 AppsFlyer / Adjust:
com/下无com.appsflyer、com.adjust包;全源码 grep 无调用(仅有 "AdjustLayout" 之类误命中)。归因主要靠 Firebase + 各广告平台自己的回调。
4. 远程配置 / A/B 测试
4.1 配置链
FirebaseRemoteConfigWrapper(拉取,60s 超时/1h 最小间隔)
→ com.camerasideas.instashot.remote.d (AppRemoteConfig,本地 debug 覆盖 + RC 合并)
→ X3.C1246g (AppCapabilities 静态门面,全 app 引用)
→ V5.c (AdDeploy 投放) / 各业务模块
类:com/camerasideas/instashot/FirebaseRemoteConfigWrapper.java
aVar2.f62906a = 3600L; // minimum_fetch_interval
editorEdit.putLong("fetch_timeout_in_seconds", 60L) // fetch timeout 60s
... .putLong("minimum_fetch_interval_in_seconds", 3600) // commit
// 完成回调: postTaskResultInfo → dispatchComplete(this, success, activated)
// getString/getBoolean/getLong/getDouble: 读 activated 值,回退 defaults
类:com/camerasideas/instashot/remote/d.java(AppRemoteConfig):h()/b()/g()/c()/e() 读合并后的 HashMap(f30986b,本地 prefs 覆盖 + Firebase RC);X3/C1246g.java(AppCapabilities)以静态方法暴露,如:
public static C2437a b() { // 订阅弹窗自动拉起配置
return Gson.fromJson(f14024b.h("key_subscribe_auto_launch"), C2437a.class);
}
public static com.camerasideas.instashot.remote.t h() { // 订阅页 A/B
return Gson.fromJson(f14024b.h("key_subscribe_ab_test_info"), ...);
}
public static com.camerasideas.instashot.remote.t i() { // A/B debug 变体
return Gson.fromJson(f14024b.h("key_subscribe_ab_test_info_debug"), ...);
}
public static List f(B b10) { // 弹窗序列控制
strH = f14024b.h("popup_items_control");
if (empty) strH = B.c(b10.getResources().openRawResource(R.raw.popup_config));
}
public static boolean l(Context context) { // 水印展示黑名单
return f14024b.h("disallow_show_watermark_list") 机型匹配;
}
4.2 典型远程键清单
| 域 | 键 | 用途 |
|---|---|---|
| 广告 | ad_waterfall_list_v_max |
自定义 waterfall JSON(回退 raw/local_ad_waterfall.json) |
| 广告 | ad_deploy_list_v_1530 |
广告位部署(OS/RAM/机型定向,回退 raw/local_ad_deploy_list.json) |
| 广告 | ad_supported_info_android |
AdConfig(banner 支持/默认 ad unit) |
| 广告 | popup_interstitial_ads |
插屏频控 JSON |
| 广告 | waiting_reward_timeout_millis / is_ad_load_failure_can_rewarded / reward_ad_load_position |
激励广告策略 |
| 广告 | banner_refresh_time_millis / mrec_refresh_time_millis / ad_request_time_millis / ad_expiration_time_millis |
横幅/MREC 刷新、超时、过期 |
| 广告 | app_ad_info_list |
自推 house ad 列表 |
| 广告 | remove_banner_ad / remove_card_ad |
广告关闭按钮开关 |
| 订阅 | key_subscribe_auto_launch |
订阅页自动拉起配置 |
| 订阅 | key_subscribe_ab_test_info(+_debug) |
订阅页 A/B 测试 |
| 订阅 | key_subscription_replacement_mode |
Play 订阅替换模式 |
| 功能 | ai_cut_free_tries_count / feature_score_style / transition_filter_versions / giphy_types / latest_version_update_info / notification_permission_requested_parameter |
功能与 UI 开关 |
| 稳定性 | glesv2_mtk_crash_list / lottie_engine_render_black_screen |
机型黑名单降级 |
| 分析 | firebase_event_filter_config |
Firebase 事件过滤 JSON |
4.3 设备能力分级
类:com/camerasideas/instashot/SdkInitializer.java(实现 S0.b<h2>,依赖注入初始化)
if (!((ActivityManager) context.getSystemService("activity")).isLowRamDevice()
&& Build.VERSION.SDK_INT > 28
&& reqGlEsVersion >= 196608 // GLES 3.0
&& memoryInfo.totalMem >= 2.147483648E9d) { // 2GB
i = 1; // 高端设备
}
if (i == 0) cVar.f63725a = "1"; // 低端设备标记 → 关闭部分特效
5. 崩溃与监控(APM)
5.1 初始化
类:com/camerasideas/startup/InitializeApmTask.java(@Keep,优先级最高,其它任务 dependOn 它;InstashotApplication 中 initializeApmTask.start() 后调度器阻塞等待)
private void initializeApp(Context context) {
InstashotApplication.a(context);
Thread.setDefaultUncaughtExceptionHandler(new C2871v()); // 自定义 CrashHandler
setRxJavaErrorHandler(); // RxJava 错误 → M.e 上报
if (this.isMainProcess) {
z.b(context); // ...
((ArrayList) Dd.e.f2349a.f2351c).add(new l()); // 追加 LogPrinter
// WebView 多进程 data-dir 锁清理(webview_data.lock 文件锁修复)
// Kf.g(AiTaskDI) 注入: f6709l = startup.a(CommonRequestHook), f6708k = startup.k(IntegrityExtensions)
}
M.f60737a = new X3.Q(context, this.isMainProcess); // Firebase 分析初始化
p305pc.d.f59680k.a(context).f59691h = new J7.i();
}
startup/a.java(CommonRequestHook):请求层钩子,向 AI/网络请求体注入uuid+appUserId(com.google.gson.internal.s.c())。
5.2 崩溃过滤(关键:第三方 SDK 白名单静默)
类:com/camerasideas/utils/C2871v.java(CrashHandler)
对下列来源的崩溃直接吞掉不上报(既是防误报,也侧面暴露了广告 SDK 清单):
// 过滤: com.google.android.gms.ads.omid / nonagon(AdMob)
// org.chromium(WebView)、com.vungle.warren(Vungle)
// com.applovin.impl.sdk(AppLovin)、com.unity3d.services(Unity Ads)
// com.inmobi.media(InMobi)、com.yandex.mobile(Yandex)
// com.google.firebase.installations、androidx.work、GoogleSignatureVerifier/chimera/internal
// 例如:
if ("java.lang.NullPointerException".equals(name2)) {
for (...) if ((stackTraceElement.getClassName()+"").contains("com.vungle.warren.VungleApiClient")) return;
}
if ("java.util.ConcurrentModificationException".equals(name2) || ...) {
for (...) if ((...).contains("com.applovin.impl.sdk")) return;
}
5.3 微信 mars xlog
- 类:
com/tencent/mars/xlog/Xlog.java+p192je/a.java(Log) - 初始化:
InitializeEnvTask.initializeLog():
p192je.a.e(p107f5.b.A(this.mContext), "instashot"); // xlog 目录 + 文件前缀
// p192je.a.e():
System.loadLibrary("marsxlog");
Xlog xlog = new Xlog();
xlog.appenderOpen(2, 0, "", str, str2, 2); // 异步模式,level=2(Debug)
com.tencent.mars.xlog.Log.setLogImp(xlog);
com.tencent.mars.xlog.Log.setConsoleLogOpen(false);
- 之后
p192je.a.g(level, tag, msg)全量切到 xlog;p192je.a.d(false)=Log.appenderFlushSync。 - 使用方(业务日志):
MainActivity、store/billing/C2440d、store/billing/G、InitializeBillingTask等直接import com.tencent.mars.xlog.Log。
5.4 启动编排(AnchorsManager)
类:com/camerasideas/instashot/InstashotApplication.java + p434w7/d.java(调度器)+ B7/a.java
// onCreate: 仅主进程 + videoprocess 进程初始化
p434w7.d dVar = new p434w7.d();
dVar.f(initializeApmTask, {initializeApmTask}); // 锚点任务
initializeApmTask.start();
while (dVar.c()) { ... 按优先级排序执行任务 ... } // 主线程阻塞等待启动链完成
任务链:InitializeApmTask → InitializeEnvTask → InitializeBillingTask → InitializeStateTask → InitializeLibTask → InitializeResourceTask。
5.5 其他监控点
C1237d(AppLifecycleHandler,Application 注册的 ActivityLifecycleCallbacks,InstashotApplication中c1237d.f14006e = new Handler(mainLooper)挂载)。- 内存压力:
onTrimMemory→M4.p.f7255a.a()。 - 异常上报类型:
com/camerasideas/exception/(RxJavaException、WebViewFixedException、IntegrityException、InstallSourceException、ReverseInstallApkSourceException 等)全部经M.e进 Crashlytics。 - 未发现独立的 ANR/卡顿采样器;启动耗时依赖 xlog 时间戳 + AnchorsManager 任务计时。
6. 广告 ID / 隐私 / UMP 同意流
6.1 Google UMP(混淆在广告 SDK 内)
类:De/d.java(CmpService)+ De/m.java(SdkCmpService 实现)+ De/h.java(OnConsentFormLoadListener)+ De/i.java(OnConsentInfoUpdateListener)+ De/j/k.java(SdkCmpService 业务回调)
// De/m:
public final ConsentInformation f2378a = UserMessagingPlatform.getConsentInformation(context);
public final ConsentRequestParameters f2379b = new ConsentRequestParameters.Builder()
.setTagForUnderAgeOfConsent(false).setConsentDebugSettings(null).build();
// 触发点 SettingFragment.java:408:
mVar.f2378a.requestConsentInfoUpdate(dVar3, mVar.f2379b, lVar, lVar);
// De/j.onConsentFormLoadSuccess:
int consentStatus = this.f2371d.f2378a.getConsentStatus();
if (z10 || consentStatus == 2) consentForm.show(this.f2370c, new a()); // REQUIRED 或老用户
// onConsentFormDismissed:
boolean hasConsent = mVar.f2378a.getConsentStatus() == 3; // OBTAINED
SettingFragment.zj((SettingFragment) ..., hasConsent);
6.2 应用内隐私页与 AppLovin 同步
类:com/camerasideas/instashot/PolicyFragment.java
// 同意状态 ↔ AppLovinPrivacySettings 双向同步
AppLovinPrivacySettings.setHasUserConsent(!"disagree".equals(new JSONObject(str).getString("status")));
jSONObject.put("status", AppLovinPrivacySettings.hasUserConsent() ? "agree" : "disagree");
// EU 用户切换 EU 隐私页
int consentStatus = ((De.m) De.d.a.a(this.mContext)).f2378a.getConsentStatus();
this.mWebView.loadUrl((consentStatus == 3 || consentStatus == 2)
? C1264m.h("https://inshot.cc/website/InShotAndroid/privacypolicy_eu.html")
: A1.S(this.mContext));
类:com/camerasideas/instashot/fragment/common/C1897w.java:设置页同意开关 → AppLovinPrivacySettings.setHasUserConsent(true)。
类:com/camerasideas/startup/MobileAdsInitializer.java:初始化时 AppLovinPrivacySettings.setDoNotSell(true)(CCPA)、setExtraParameter("user_agent_collection_enabled","false")、禁用 MAX TCF flow——同意完全由自研 UMP 流程驱动,并通过 AppLovinPrivacySettings 透传给 MAX。
6.3 广告 ID 与设备标识
com/camerasideas/startup/InitializeResourceTask.java:com.google.android.gms.ads.identifier.AdvertisingIdClient获取 GAID。S.java.i(context):UUID(优先取p202k4.o.P(context)存储值,否则UUID.randomUUID()),用于埋点uuid。com.google.gson.internal.s:accountId/appUserId/isInReview/isPro本地标记(p382te.sMMKV 偏好)。- 崩溃过滤里专门忽略
GoogleSignatureVerifier/chimera/gms.internal崩溃,降低广告 SDK 引发的崩溃率。
7. 结论:这家公司的商业化工程特征
- 自研商业化 SDK(Shantanu)+ 全远程化:广告(waterfall/deploy/频控/自推 house ad)与订阅(弹窗/A-B/替换模式)几乎全部由远程配置驱动,本地 assets 均有回退 JSON——线上可热切换广告主、广告位、价格页与 A-B 实验。
- 广告聚合 = AppLovin MAX + 自定义 AdMob waterfall:不是标准 mediation;MAX 只做兜底调度,真实填充是自定义 event 直连 AdMob(32 步全部
GoogleMediationAdapter),多网络(Vungle/Unity/InMobi/Fyber/Yandex/TradPlus/PubMatic)仅随包存在。 - 反分析手段密集:R8 单字母混淆、
\u200b零宽空格包名水印、Le.n字符串池解密、应用类编入com.google.gson.internal包做静态钩子(s.f42708a特性开关、M implements q实例创建钩子)、广告异常类刻意 keep 原包名。 - 订阅-广告强耦合:
remove.ads即去广告 SKU;广告初始化/展示处处以UserManager.k()为前提;Pro 判定走w()(gson 钩子 + 内购 + 华为 + DebugPro)。 - 分析收敛:全事件经
p348ri.M门面 → Firebase Analytics/Crashlytics;协程 Channel 队列 +sample_number采样 +firebase_event_filter_config过滤;日志三路(Android Log / xlog / Crashlytics.log 镜像);无 AppsFlyer/Adjust。 - 服务器侧商业闭环:Shantanu IAP(账户绑定/订单查询/审核态)+ Play/Huawei Integrity(反欺诈)+ 盗版签名检测(
pirate_app)+ 崩溃白名单,形成"商业化-安全-稳定性"一体的工程体系。
附:关键类路径速查表
| 主题 | 类路径 |
|---|---|
| 广告初始化 | com/camerasideas/startup/MobileAdsInitializer.java、De/g.java、De/o.java、De/p.java、com/shantanu/mobileads/SdkInitializer.java |
| 广告位解析 | V5/c.java(AdDeploy)、N/a.java、com/camerasideas/instashot/remote/a.java(AdConfig) |
| 插屏 | V5/n.java、V5/l.java、Fe/g.java、Fe/e.java、Fe/f.java、Fe/q.java |
| 激励 | V5/r.java、V5/s.java、V5/t.java、Je/a.java、Je/c.java、Je/q.java |
| 横幅/MREC | V5/g.java、V5/p.java、com/camerasideas/mobileads/BannerContainer.java、Ie/d.java、Je/o.java |
| 收益/适配器 | He/e.java(NetworkAdRevenueAdapter)、He/d.java、De/a.java(AdRevenue)、De/b.java |
| 订阅入口 | com/camerasideas/startup/InitializeBillingTask.java |
| 计费抽象 | p321qa/o.java、p321qa/g.java(BillingManager)、p321qa/d.java(BillingHelper)、p321qa/a.java(Fake) |
| SKU/权益 | com/camerasideas/instashot/store/billing/C2460y.java、S.java(UserManager)、InterfaceC2448l.java、C2449m.java、C2450n.java、P.java、C2458w.java、D.java |
| 服务端 IAP | com/shantanu/iap/v.java、com/shantanu/iap/OwnedPurchase.java、com/shantanu/iap/bind/* |
| Play Integrity | com/android/auth/integrity/IntegrityInitializer.java、g.java、GoogleIntegrityManager.java、com/camerasideas/startup/j.java |
| 埋点 | p348ri/M.java、X3/Q.java、X3/F.java、X3/G.java、X3/H.java、X3/AbstractC1284t.java、X3/E.java |
| 日志管道 | Dd/e.java、Dd/f.java、p192je/a.java、com/tencent/mars/xlog/Xlog.java |
| 远程配置 | com/camerasideas/instashot/FirebaseRemoteConfigWrapper.java、remote/d.java、X3/C1246g.java、X3/U0.java |
| APM/崩溃 | com/camerasideas/startup/InitializeApmTask.java、com/camerasideas/utils/C2871v.java、com/camerasideas/startup/InitializeEnvTask.java、startup/a.java(CommonRequestHook) |
| 隐私/同意 | De/d.java、De/m.java、De/h.java、De/i.java、De/j.java、De/k.java、PolicyFragment.java、fragment/common/C1897w.java |
| 启动编排 | com/camerasideas/instashot/InstashotApplication.java、p434w7/d.java、B7/a.java |
| Gson 钩子 | com/google/gson/internal/s.java、J7/k.java、p348ri/M.java |