调整 广告逻辑

This commit is contained in:
Jin857
2024-11-25 11:54:02 +08:00
parent d4d67247a2
commit e834f3b943
63 changed files with 4078 additions and 1644 deletions

View File

@@ -1,18 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.union_ad_ssgf">
<!-- 需要权限 start -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application>
<activity android:name=".page.AdSplashActivity"
android:configChanges="keyboard|orientation|screenSize"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme"/>
<!-- 文件兼容 -->
<provider
android:name="com.qq.e.comm.GDTFileProvider"
android:authorities="${applicationId}.gdt.fileprovider"
@@ -20,8 +17,21 @@
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/gdt_file_path" />
android:resource="@xml/gdt_file_path"
tools:replace="android:resource"
/>
</provider>
<activity
android:name="com.qq.e.ads.PortraitADActivity"
android:screenOrientation="portrait"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
tools:replace="android:screenOrientation" />
<activity
android:name="com.qq.e.ads.LandscapeADActivity"
android:screenOrientation="landscape"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
tools:replace="android:screenOrientation" />
</application>
</manifest>

View File

@@ -0,0 +1,198 @@
package com.example.union_ad_ssgf;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Configuration;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.qq.e.comm.compliance.DownloadConfirmCallBack;
public class DownloadApkConfirmDialog extends Dialog implements View.OnClickListener {
private static final String TAG = "ConfirmDialogWebView";
private Context context;
private int orientation;
private DownloadConfirmCallBack callBack;
private WebView webView;
private ImageView close;
private Button confirm;
private ViewGroup contentHolder;
private ProgressBar loadingBar;
private Button reloadButton;
private String url;
private boolean urlLoadError = false;
private static final String RELOAD_TEXT = "重新加载";
private static final String LOAD_ERROR_TEXT = "抱歉,应用信息获取失败";
public DownloadApkConfirmDialog(Context context, String infoUrl,
DownloadConfirmCallBack callBack) {
super(context, R.style.DownloadConfirmDialogFullScreen);//需要全屏显示,同时显示非窗口蒙版
this.context = context;
this.callBack = callBack;
this.url = infoUrl;
orientation = context.getResources().getConfiguration().orientation;
requestWindowFeature(Window.FEATURE_NO_TITLE);
setCanceledOnTouchOutside(true);
initView();
}
private void initView() {
setContentView(R.layout.download_confirm_dialog);
View root = findViewById(R.id.download_confirm_root);
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
root.setBackgroundResource(R.drawable.download_confirm_background_portrait);
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
root.setBackgroundResource(R.drawable.download_confirm_background_landscape);
}
close = findViewById(R.id.download_confirm_close);
close.setOnClickListener(this);
reloadButton = findViewById(R.id.download_confirm_reload_button);
reloadButton.setOnClickListener(this);
confirm = findViewById(R.id.download_confirm_confirm);
confirm.setOnClickListener(this);
loadingBar = findViewById(R.id.download_confirm_progress_bar);
contentHolder = findViewById(R.id.download_confirm_content);
createTextView();
}
private void createTextView(){
FrameLayout layout = findViewById(R.id.download_confirm_holder);
webView = new WebView(context);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new Client());
layout.addView(webView);
}
@Override
public void show() {
super.show();
try {
loadUrl(url);
} catch (Exception e) {
Log.e(DownloadApkConfirmDialog.TAG, "load error url:" + url, e);
}
}
private void loadUrl(String url) {
if (TextUtils.isEmpty(url)) {
loadingBar.setVisibility(View.GONE);
contentHolder.setVisibility(View.GONE);
reloadButton.setVisibility(View.VISIBLE);
reloadButton.setText(LOAD_ERROR_TEXT);
reloadButton.setEnabled(false);
return;
}
urlLoadError = false;
Log.d(TAG, "download confirm load url:" + url);
webView.loadUrl(url);
}
public void setInstallTip() {
confirm.setText("立即安装");
}
@Override
protected void onStart() {
int height = (int) UIUtils.INSTANCE.getScreenHeight(context);
int width =(int) UIUtils.INSTANCE.getScreenWidth(context);
Window window = getWindow();
window.getDecorView().setPadding(0, 0, 0, 0);
WindowManager.LayoutParams layoutParams = window.getAttributes();
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
layoutParams.height = (int) (height * 0.6);
layoutParams.gravity = Gravity.BOTTOM;
layoutParams.windowAnimations = R.style.DownloadConfirmDialogAnimationUp;
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
layoutParams.width = (int) (width * 0.5);
layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;
layoutParams.gravity = Gravity.RIGHT;
layoutParams.windowAnimations = R.style.DownloadConfirmDialogAnimationRight;
}
//弹窗外区域蒙版50%透明度
layoutParams.dimAmount = 0.5f;
//resume后动画会重复在显示出来后重置无动画
window.setAttributes(layoutParams);
setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
try {
Window window = getWindow();
window.setWindowAnimations(0);
} catch (Throwable t) {
}
}
});
}
@Override
public void onClick(View v) {
if (v == close) {
if (callBack != null) {
callBack.onCancel();
}
dismiss();
} else if (v == confirm) {
if (callBack != null) {
callBack.onConfirm();
}
dismiss();
} else if (v == reloadButton) {
loadUrl(url);
}
}
@Override
public void cancel() {
super.cancel();
if (callBack != null) {
callBack.onCancel();
}
}
class Client extends WebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (!urlLoadError) {
loadingBar.setVisibility(View.GONE);
reloadButton.setVisibility(View.GONE);
contentHolder.setVisibility(View.VISIBLE);
}
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
Log.d(TAG, "doConfirmWithInfo onReceivedError:" + error + " " + request);
urlLoadError = true;
loadingBar.setVisibility(View.GONE);
contentHolder.setVisibility(View.GONE);
reloadButton.setVisibility(View.VISIBLE);
reloadButton.setText(RELOAD_TEXT);
reloadButton.setEnabled(true);
}
}
}

View File

@@ -0,0 +1,818 @@
package com.example.union_ad_ssgf;
import android.app.Activity;
import android.text.TextUtils;
import android.util.Log;
import com.qq.e.comm.compliance.ApkDownloadComplianceInterface;
import com.qq.e.comm.compliance.DownloadConfirmCallBack;
import com.qq.e.comm.compliance.DownloadConfirmListener;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/*
* created by timfeng 2021/2/2
*/
public class DownloadConfirmHelper {
public static final String TAG = "DownloadConfirmHelper";
public static boolean USE_CUSTOM_DIALOG = false;
private static final String JSON_INFO_PARAMETER = "&resType=api";
private static final String JSON_RESULT_KEY = "ret";
private static final String JSON_DATA_KEY = "data";
private static final String ICON_URL_KEY = "iconUrl";//App Icon
private static final String APP_NAME_KEY = "appName";//App 名称
private static final String VERSION_NAME_KEY = "versionName";//版本号
private static final String AUTHOR_NAME_KEY = "authorName";//开发者
private static final String PERMISSIONS_KEY = "permissions";//权限列表具体权限信息非URL
private static final String PRIVACY_AGREEMENT_KEY = "privacyAgreement";//隐私政策URL这个url需要使用外部浏览器或者webview展示
private static final String UPDATE_TIME_KEY = "apkPublishTime";//版本更新时间
private static final String APK_FILE_SIZE_KEY = "fileSize";//apk文件大小bytes
private static boolean USE_RAW_PERMISSIONS = false;
private static JSONObject PERMISSIONS_MAP_JSON;
public static class ApkInfo {
public String iconUrl;
public String appName;
public String versionName;
public String authorName;
public List<String> permissions;
public String privacyAgreementUrl;
public long apkPublishTime;
public long fileSize;
}
public static final DownloadConfirmListener DOWNLOAD_CONFIRM_LISTENER =
new DownloadConfirmListener() {
@Override
public void onDownloadConfirm(Activity context, int scenes, String infoUrl,
DownloadConfirmCallBack callBack) {
Log.d(TAG, "scenes:" + scenes + " info url:" + infoUrl);
//获取对应的json数据并自定义显示
// DownloadApkConfirmDialog dialog = new DownloadApkConfirmDialog(context, getApkJsonInfoUrl(infoUrl), callBack);
DownloadApkConfirmDialog dialog = new DownloadApkConfirmDialog(context, infoUrl, callBack);//使用webview显示
if((scenes & ApkDownloadComplianceInterface.INSTALL_BITS) != 0){
dialog.setInstallTip();
scenes &= ~ApkDownloadComplianceInterface.INSTALL_BITS;
Log.d(TAG, "real scenes:" + scenes );
}
dialog.show();
}
};
public static String getApkJsonInfoUrl(String infoUrl) {
return infoUrl + JSON_INFO_PARAMETER;
}
public static ApkInfo getAppInfoFromJson(String jsonStr) {
ApkInfo result = null;
if (TextUtils.isEmpty(jsonStr)) {
Log.d(TAG, "请求应用信息返回值为空");
return null;
}
try {
JSONObject json = new JSONObject(jsonStr);
int retCode = json.optInt(JSON_RESULT_KEY, -1);
if (retCode != 0) {
Log.d(TAG, "请求应用信息返回值错误");
return null;
}
JSONObject dataJson = json.optJSONObject(JSON_DATA_KEY);
if (dataJson == null) {
Log.d(TAG, "请求应用信息返回值错误" + JSON_DATA_KEY);
return null;
}
if (dataJson != null) {
result = new ApkInfo();
result.iconUrl = dataJson.optString(ICON_URL_KEY);
result.appName = dataJson.optString(APP_NAME_KEY);
result.versionName = dataJson.optString(VERSION_NAME_KEY);
result.authorName = dataJson.optString(AUTHOR_NAME_KEY);
//这里获得是原始的权限数组android.permission.ACCESS_NETWORK_STATE这种
JSONArray jsonPermissions = dataJson.optJSONArray(PERMISSIONS_KEY);
if (jsonPermissions != null) {
result.permissions = new ArrayList<>();
for (int i = 0; i < jsonPermissions.length(); i++) {
String rawPermission = jsonPermissions.getString(i);
if (USE_RAW_PERMISSIONS) {
result.permissions.add(jsonPermissions.getString(i));
continue;
}
//字面权限,不会显示所有,只会显示一部分常用权限
JSONObject permissionContent = PERMISSIONS_MAP_JSON.optJSONObject(rawPermission);
if (permissionContent != null) {
String permissionTitle = permissionContent.optString("title");
if (!TextUtils.isEmpty(permissionTitle)) {
result.permissions.add(permissionTitle);
}
}
}
}
result.privacyAgreementUrl = dataJson.optString(PRIVACY_AGREEMENT_KEY);
//后台返回的时间可能是秒也可能是毫秒,这里需要统一下为毫秒
//2000年1月1日1时0分0秒对应的 秒是946688401 毫秒是 946688401000
long publicTime = dataJson.optLong(UPDATE_TIME_KEY);
result.apkPublishTime = publicTime > 946688401000L ? publicTime : publicTime * 1000;
result.fileSize = dataJson.optLong(APK_FILE_SIZE_KEY);//单位是字节
}
} catch (JSONException e) {
e.printStackTrace();
}
return result;
}
/**
* 1. 权限对照表作为开发者参考使用
* 2. level = 1代表敏感权限0代表普通权限开发者可以按需使用
* 3. 权限未穷举部分,需要开发者自行翻译
*/
public static final String PERMISSIONS_MAP_JSON_STR = "{\n" +
"\"android.permission.ACCESS_CHECKIN_PROPERTIES\": {\n" +
"\"desc\": \"允许应用对登记服务上传的属性拥有读/写权限。普通应用不应使用此权限。\",\n" +
"\"title\": \"访问检入属性\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.ACCESS_COARSE_LOCATION\": {\n" +
"\"desc\": \"允许该应用获取您的大致位置信息,这类位源信息来自于使用网络位置信息源(例如基站和WLAN)" +
"的位置信息服务。您必须在设备上开启这些位置信息服务,应用才能获得位置信息。应用会使用此类服务确定您的大概位置。\",\n" +
"\"title\": \"大致位置(基于网络)\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS\": {\n" +
"\"desc\": \"允许应用程序访问其他的位置信息提供程序命令。此权限使该应用可以干扰GPS或其他位置信息源的运作。\",\n" +
"\"title\": \"获取额外的位置信息提供程序命令。\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.ACCESS_NETWORK_STATE\": {\n" +
"\"desc\": \"允许该应用查看网络连接的相关信息,例如存在和连接的网络。\",\n" +
"\"title\": \"查看网络连接。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.ACCESS_NOTIFICATION_POLICY\": {\n" +
"\"desc\": \"允许访问通知策略的应用程序的标记权限。\",\n" +
"\"title\": \"访问通知策略\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.ACCESS_WIFI_STATE\": {\n" +
"\"desc\": \"允许该应用查看WLAN网络的相关信息。例如是否启用了WLAN以及连接的WLAN设备的名称。\",\n" +
"\"title\": \"查看WLAN连接\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.ACCOUNT_MANAGER\": {\n" +
"\"desc\": \"允许应用使用AccountManager的帐户身份验证程序功能包括创建帐户以及获取和设置复密码。\",\n" +
"\"title\": \"创建帐户并设置密码\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.ACTIVITY_RECOGNITION\": {\n" +
"\"desc\": \"允许应用程序识别身体活动。\",\n" +
"\"title\": \"识别身体活动\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.ANSWER_PHONE_CALLS\": {\n" +
"\"desc\": \"允许该应用接听来电。\",\n" +
"\"title\": \"接听来电\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.BATTERY_STATS\": {\n" +
"\"desc\": \"允许应用读取当前电量使用情况的基础数据。此权限可让应用了解关于您使用了哪些应用的详细信息。\",\n" +
"\"title\": \"读取电池使用统计信息。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.BIND_CARRIER_SERVICES\": {\n" +
"\"desc\": \"允许应用告知系统哪些小部件可提供哪个应用使用。拥有此权限的应用可向其他应用授予对个人数据的访问权限。普通应用不应使用此权限。\",\n" +
"\"title\": \"选择小部件\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.BIND_CONTROLS\": {\n" +
"\"desc\": \"允许SystemUI请求第三方控件。\",\n" +
"\"title\": \"请求第三方控件\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.BIND_QUICK_SETTINGS_TILE\": {\n" +
"\"desc\": \"允许应用程序绑定到第三方快速设置磁贴。\",\n" +
"\"title\": \"绑定快速设置磁贴\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.BLUETOOTH\": {\n" +
"\"desc\": \"允许该应用查看手机上的蓝牙配置,以及建立和接受与配对设备的连接。\",\n" +
"\"title\": \"与蓝牙设备配对。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.BLUETOOTH_ADMIN\": {\n" +
"\"desc\": \"允许应用配置本地蓝牙手机,以及发现远程设备并进行配对。\",\n" +
"\"title\": \"访问蓝牙设置。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.BLUETOOTH_PRIVILEGED\": {\n" +
"\"desc\": \"允许应用程序与蓝牙设备配对,而无需用户交互。\",\n" +
"\"title\": \"运行应用进行蓝牙配对。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.BODY_SENSORS\": {\n" +
"\"desc\": \"允许应用访问您用于测量身体状况(如心跳速率)的传感器中的数据。\",\n" +
"\"title\": \"人体传感器\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.BROADCAST_PACKAGE_REMOVED\": {\n" +
"\"desc\": \"允许应用程序广播已删除应用程序的通知。\",\n" +
"\"title\": \"发送软件包被移除的广播通知。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.BROADCAST_SMS\": {\n" +
"\"desc\": \"允许应用广播一条有关已收到短信的通知。恶意应用可能借此伪造接到的短信。\",\n" +
"\"title\": \"发送短信收到的广播\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.BROADCAST_STICKY\": {\n" +
"\"desc\": \"允许应用程序广播粘性意图。\",\n" +
"\"title\": \"发送持久广播。\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.BROADCAST_WAP_PUSH\": {\n" +
"\"desc\": \"允许应用程序广播WAP PUSH接收通知。\",\n" +
"\"title\": \"发送WAP-PUSH 收到的广播。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.CALL_COMPANION_APP\": {\n" +
"\"desc\": \"允许应用作为呼叫伴随应用启用。\",\n" +
"\"title\": \"伴随呼叫启动\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.CALL_PHONE\": {\n" +
"\"desc\": \"允许应用程序在不通过拨号界面的情况下发起电话呼叫。\",\n" +
"\"title\": \"直接拨打电话号码\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.CALL_PRIVILEGED\": {\n" +
"\"desc\": \"允许应用程序拨打任何电话号码,包括紧急号码,而无需通过拨号界面来让用户确认正在拨打的电话。\",\n" +
"\"title\": \"直接呼叫任何电话号码\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.CAMERA\": {\n" +
"\"desc\": \"允许该应用使用相机拍摄照片和视频。此权限可让该应用随时使用相机,而无需您的确认。\",\n" +
"\"title\": \"拍摄照片和视频\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.CAPTURE_AUDIO_OUTPUT\": {\n" +
"\"desc\": \"允许该应用捕获和重定向音频输出。\",\n" +
"\"title\": \"捕获音频输出。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.CHANGE_COMPONENT_ENABLED_STATE\": {\n" +
"\"desc\": \"允许应用启用或停用其他应用的组件。\",\n" +
"\"title\": \"启用或停用应用组件\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.CHANGE_CONFIGURATION\": {\n" +
"\"desc\": \"允许应用程序修改当前设备配置。\",\n" +
"\"title\": \"更改系统显示设置。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.CHANGE_NETWORK_STATE\": {\n" +
"\"desc\": \"允许应用程序更改网络连接状态。\",\n" +
"\"title\": \"更改网络连接状态。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.CHANGE_WIFI_MULTICAST_STATE\": {\n" +
"\"desc\": \"允许该应用使用多播地址接收发送到WLAN网络上所有设备的数据包。该操作的耗电量比非多播模式更大。\",\n" +
"\"title\": \"允许接收WLAN多播。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.CHANGE_WIFI_STATE\": {\n" +
"\"desc\": \"允许该应用与 WLAN 接入点建立和断开连接以及更改WLAN网络的设备配置。\",\n" +
"\"title\": \"连接WLAN网络和断开连接。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.CLEAR_APP_CACHE\": {\n" +
"\"desc\": \"允许应用程序清除设备上所有已安装应用程序的缓存。\",\n" +
"\"title\": \"删除所有应用缓存数据\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.CONTROL_LOCATION_UPDATES\": {\n" +
"\"desc\": \"允许应用启用/停止来自无线装置的位置更新通知。普通应用应该使用此权限。\",\n" +
"\"title\": \"控制位置更新通知。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.DELETE_CACHE_FILES\": {\n" +
"\"desc\": \"允许删除应用程序的缓存文件。\",\n" +
"\"title\": \"删除其他应用程序的缓存文件。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.DELETE_PACKAGES\": {\n" +
"\"desc\": \"允许应用程序删除其他应用。\",\n" +
"\"title\": \"删除应用\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.DIAGNOSTIC\": {\n" +
"\"desc\": \"允许应用读取/写入诊断拥有的所有资源(例如/dev中的文件。这可能会影响系统的稳定性和安全性。\",\n" +
"\"title\": \"允许应用程序读写诊断资源。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.DISABLE_KEYGUARD\": {\n" +
"\"desc\": \"允许该应用停用键锁以及任何关联的密码安全措施。例如,让手机在接听来电时停用键锁,在通话结束后重新启用键。\",\n" +
"\"title\": \"停用屏幕锁定\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.DUMP\": {\n" +
"\"desc\": \"允许应用程序从系统服务中检索状态转储信息。\",\n" +
"\"title\": \"检索系统内部状态\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.EXPAND_STATUS_BAR\": {\n" +
"\"desc\": \"允许应用程序展开或折叠状态栏。\",\n" +
"\"title\": \"展开/收拢状态栏\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.FACTORY_TEST\": {\n" +
"\"desc\": \"允许应用以制造商测试应用程序的身份运行以root用户身份运行。\",\n" +
"\"title\": \"在出厂测试模式下运行\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.FOREGROUND_SERVICE\": {\n" +
"\"desc\": \"允许应用程序使用前台服务。\",\n" +
"\"title\": \"使用前台服务\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.GET_ACCOUNTS\": {\n" +
"\"desc\": \"允许访问帐户服务中的帐户列表。\",\n" +
"\"title\": \"查找设备上的账户\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.GET_ACCOUNTS_PRIVILEGED\": {\n" +
"\"desc\": \"允许访问帐户服务中的帐户列表。\",\n" +
"\"title\": \"访问账户列表\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.GET_PACKAGE_SIZE\": {\n" +
"\"desc\": \"允许应用程序查看其他应用使用的空间大小。\",\n" +
"\"title\": \"计算应用存储空间\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.GET_TASKS\": {\n" +
"\"desc\": \"允许应用程序获取当前的信息或最近运行的任务。\",\n" +
"\"title\": \"检索正在运行的应用。\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.INSTALL_LOCATION_PROVIDER\": {\n" +
"\"desc\": \"创建用于测试的模拟位置源或安装新的位置提供程序。\",\n" +
"\"title\": \"允许安装位置信息提供程序\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.INSTALL_PACKAGES\": {\n" +
"\"desc\": \"允许应用程序安装其他应用。\",\n" +
"\"title\": \"直接安装应用\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.INSTANT_APP_FOREGROUND_SERVICE\": {\n" +
"\"desc\": \"允许即时应用创建前台服务。\",\n" +
"\"title\": \"创建前台服务\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.INTERNET\": {\n" +
"\"desc\": \"允许该应用创建网络套接字和使用自定义网络协议。浏览器和其他某些应用提供了向互联网发送数据的途径,因此应用无需该权限即可向互联网发送数据。\",\n" +
"\"title\": \"完全的网络访问权限\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.KILL_BACKGROUND_PROCESSES\": {\n" +
"\"desc\": \"允许应用清理后台应用进程。\",\n" +
"\"title\": \"关闭其他应用\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.LOADER_USAGE_STATS\": {\n" +
"\"desc\": \"允许数据加载器读取程序包的访问日志。\",\n" +
"\"title\": \"允许读取程序包日志\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.MANAGE_DOCUMENTS\": {\n" +
"\"desc\": \"允许应用程序管理档存储空间。\",\n" +
"\"title\": \"管理文档存储空间\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.MANAGE_EXTERNAL_STORAGE\": {\n" +
"\"desc\": \"允许应用程序访问作用域存储中的外部存储。\",\n" +
"\"title\": \"允许程序访问APP通知方式\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.MEDIA_CONTENT_CONTROL\": {\n" +
"\"desc\": \"允许应用控制媒体播放及使用媒体信息(标题、作者..)。\",\n" +
"\"title\": \"控制媒体播放和使用元数据。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.MODIFY_AUDIO_SETTINGS\": {\n" +
"\"desc\": \"允许该应用修改全局音频设置,例如音量和用于输出的扬声器。\",\n" +
"\"title\": \"更改您的音频设置\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.MODIFY_PHONE_STATE\": {\n" +
"\"desc\": \"允许应用控制设备的电话功能。拥有此权限的应用可在不通知您的情况下执行切换网络、开关手机无线装置等此类操。\",\n" +
"\"title\": \"修改手机状态\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.MOUNT_FORMAT_FILESYSTEMS\": {\n" +
"\"desc\": \"允许格式化可移动存储设备。\",\n" +
"\"title\": \"清除USB存储设备内容\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\": {\n" +
"\"desc\": \"允许挂载和卸载可移动存储的文件系统。\",\n" +
"\"title\": \"访问USB存储设备的文件系统\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.NFC\": {\n" +
"\"desc\": \"允许应用与近距离无线通信(NFC)标签、卡和读取器通信。\",\n" +
"\"title\": \"控制近距离通信\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.PACKAGE_USAGE_STATS\": {\n" +
"\"desc\": \"允许应用收集组件使用情况统计信息。\",\n" +
"\"title\": \"更新组件使用情况统计\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.PERSISTENT_ACTIVITY\": {\n" +
"\"desc\": \"允许应用程序在内存中持续保留其自身的某些组件。\",\n" +
"\"title\": \"让应用始终运行\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.PROCESS_OUTGOING_CALLS\": {\n" +
"\"desc\": \"允许应用在拨出电话时查看拨打的电话号码,并选择改为拨打其他号码或完全中止通话。\",\n" +
"\"title\": \"重新设置外拨电话的路径\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.QUERY_ALL_PACKAGES\": {\n" +
"\"desc\": \"允许查询设备上的任何应用程序。\",\n" +
"\"title\": \"查看设备上所有应用\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.READ_CALENDAR\": {\n" +
"\"desc\": \"允许应用读取用户的日历数据。\",\n" +
"\"title\": \"读取日历活动信息\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.READ_CALL_LOG\": {\n" +
"\"desc\": \"允许应用程序读取用户的通话记录。\",\n" +
"\"title\": \"读取通话记录\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.READ_CONTACTS\": {\n" +
"\"desc\": \"允许该应用读取您手机上存储的联系人的相关数据,包括您通过打电话、发送电子邮件或以其他方式与特定个人通信的频率。\",\n" +
"\"title\": \"读取您的通讯录\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.READ_EXTERNAL_STORAGE\": {\n" +
"\"desc\": \"允许应用程序从外部存储读取数据。\",\n" +
"\"title\": \"读取您的USB存储设备中的内容\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.READ_LOGS\": {\n" +
"\"desc\": \"允许应用程序读取系统日志文件。\",\n" +
"\"title\": \"查阅敏感日志数据\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.READ_PHONE_NUMBERS\": {\n" +
"\"desc\": \"允许读取访问设备的电话号码。\",\n" +
"\"title\": \"读取手机电话号码\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.READ_PHONE_STATE\": {\n" +
"\"desc\": \"允许该应用访问设备的电话功能此权限可以让应用确定本机号码和设备ID、是否正处于通话状态以及拨打的号码。\",\n" +
"\"title\": \"读取手机状态和身份\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.READ_PRECISE_PHONE_STATE\": {\n" +
"\"desc\": \"允许访问精确的电话状态。\",\n" +
"\"title\": \"读取确切的手机状态\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.READ_SMS\": {\n" +
"\"desc\": \"允许该应用读取您手机或SIM卡上存储的短信。\",\n" +
"\"title\": \"读取您的讯息(短信或彩信)\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.READ_SYNC_SETTINGS\": {\n" +
"\"desc\": \"许该应用读取某个账户的同步设置。\",\n" +
"\"title\": \"读取同步设置。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.READ_SYNC_STATS\": {\n" +
"\"desc\": \"允许该应用读取某个账户的同步统计信息,包括同步活动历史记录和同步数据量。\",\n" +
"\"title\": \"读取同步统计信息。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.REBOOT\": {\n" +
"\"desc\": \"允许应用强行重新启动设备。\",\n" +
"\"title\": \"强行重新启动手机。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.RECEIVE_BOOT_COMPLETED\": {\n" +
"\"desc\": \"允许应用在系统完成引导后立即自动启动。\",\n" +
"\"title\": \"开机启动\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.RECEIVE_MMS\": {\n" +
"\"desc\": \"允许应用程序监听彩信消息。\",\n" +
"\"title\": \"接收讯息(彩信)\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.RECEIVE_SMS\": {\n" +
"\"desc\": \"允许应用程序接收短信消息。\",\n" +
"\"title\": \"接收讯息(短信)\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.RECEIVE_WAP_PUSH\": {\n" +
"\"desc\": \"允许应用程序接收WAP推送消息。\",\n" +
"\"title\": \"接收讯息(WAP)\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.RECORD_AUDIO\": {\n" +
"\"desc\": \"允许应用程序录制音频。\",\n" +
"\"title\": \"录音\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.REORDER_TASKS\": {\n" +
"\"desc\": \"允允许该应用将任务移动到前台和后台。\",\n" +
"\"title\": \"对正在运行的应用重新排序\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND\": {\n" +
"\"desc\": \"允许合作应用在后台运行。\",\n" +
"\"title\": \"合作应用后台运行\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND\": {\n" +
"\"desc\": \"允许合作应用在后台使用数据。\",\n" +
"\"title\": \"合作应用后台使用数据\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.REQUEST_DELETE_PACKAGES\": {\n" +
"\"desc\": \"允许应用程序请求卸载其他应用。\",\n" +
"\"title\": \"卸载其他应用\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\": {\n" +
"\"desc\": \"允许用户请求应用忽略电池优化。\",\n" +
"\"title\": \"忽略电池优化\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.REQUEST_INSTALL_PACKAGES\": {\n" +
"\"desc\": \"允许应用程序请求安装软件包。\",\n" +
"\"title\": \"安装软件\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.REQUEST_PASSWORD_COMPLEXITY\": {\n" +
"\"desc\": \"允许应用程序请求屏幕锁定复杂性,并提示用户将屏幕锁定更新到特定的复杂性级别。\",\n" +
"\"title\": \"获取屏幕锁定复杂性\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.RESTART_PACKAGES\": {\n" +
"\"desc\": \"允许应用程序结束其他应用的后台进程。\",\n" +
"\"title\": \"关闭其他应用\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SEND_RESPOND_VIA_MESSAGE\": {\n" +
"\"desc\": \"允许应用程序将请求发送到其他应用程序,以便处理来电的“通过信息回复”事件。\",\n" +
"\"title\": \"发送“通过信息回复”事件\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SEND_SMS\": {\n" +
"\"desc\": \"允许应用程序发送短信消息。\",\n" +
"\"title\": \"发送短信\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.SET_ANIMATION_SCALE\": {\n" +
"\"desc\": \"允许修改全局动画缩放比例。\",\n" +
"\"title\": \"修改全局动画\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SET_DEBUG_APP\": {\n" +
"\"desc\": \"允许该应用对其他应用程序进行调试。\",\n" +
"\"title\": \"启动应用调试\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SET_PREFERRED_APPLICATIONS\": {\n" +
"\"desc\": \"允许程序修改您的首选应用。\",\n" +
"\"title\": \"设置首选应用。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SET_PROCESS_LIMIT\": {\n" +
"\"desc\": \"允许应用程序设置可以运行的应用程序进程的最大数量。\",\n" +
"\"title\": \"限制运行的进程数量。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SET_TIME\": {\n" +
"\"desc\": \"允许应用程序直接设置系统时间。\",\n" +
"\"title\": \"设置时间。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SET_TIME_ZONE\": {\n" +
"\"desc\": \"允许应用程序直接设置系统时区。\",\n" +
"\"title\": \"设置时区\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SET_WALLPAPER\": {\n" +
"\"desc\": \"允许应用程序设置墙纸。\",\n" +
"\"title\": \"设置壁纸\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SET_WALLPAPER_HINTS\": {\n" +
"\"desc\": \" 允许应用程序设置墙纸大小的提示。\",\n" +
"\"title\": \"调整您的壁纸大小\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SIGNAL_PERSISTENT_PROCESSES\": {\n" +
"\"desc\": \"允许应用程序请求将信号发送到所有持久性进程。\",\n" +
"\"title\": \"向应用发送 Linux 信号\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SMS_FINANCIAL_TRANSACTIONS\": {\n" +
"\"desc\": \"允许金融应用读取过滤的短信。\",\n" +
"\"title\": \"读取过滤短信\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.START_VIEW_PERMISSION_USAGE\": {\n" +
"\"desc\": \"允许启动应用程序的权限使用屏幕。\",\n" +
"\"title\": \"使用手机屏幕\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.STATUS_BAR\": {\n" +
"\"desc\": \"允许应用程序打开,关闭或禁用状态栏及其图标。\",\n" +
"\"title\": \"停用或修改状态栏\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.SYSTEM_ALERT_WINDOW\": {\n" +
"\"desc\": \"允许应用显示在所有其他应用的顶部。\",\n" +
"\"title\": \"在其他应用之上显示内容\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.TRANSMIT_IR\": {\n" +
"\"desc\": \"允许使用设备的红外发射器。\",\n" +
"\"title\": \"使用红外线发射器\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.UPDATE_DEVICE_STATS\": {\n" +
"\"desc\": \"允许应用程序更新设备统计信息。\",\n" +
"\"title\": \"修改使用统计信息。\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.USE_BIOMETRIC\": {\n" +
"\"desc\": \"允许应用使用设备支持的生物特征识别方式。\",\n" +
"\"title\": \"允许使用指纹等识别方式\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.USE_FINGERPRINT\": {\n" +
"\"desc\": \"允许应用使用指纹硬件。\",\n" +
"\"title\": \"允许应用使用指纹硬件\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.USE_FULL_SCREEN_INTENT\": {\n" +
"\"desc\": \"允许应用显示全屏通知。\",\n" +
"\"title\": \"允许应用显示全屏通知\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.USE_SIP\": {\n" +
"\"desc\": \"允许应用程序使用SIP服务。\",\n" +
"\"title\": \"拨打/接听SIP电话\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.VIBRATE\": {\n" +
"\"desc\": \"允许使用振动器。\",\n" +
"\"title\": \"控制振动\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.WAKE_LOCK\": {\n" +
"\"desc\": \"允许应用阻止手机进入休眠状态。\",\n" +
"\"title\": \"防止手机休眠\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.WRITE_APN_SETTINGS\": {\n" +
"\"desc\": \"允许应用更改网络设置,并拦截和检查所有网络流量。\",\n" +
"\"title\": \"更改/拦截网络设置和流量\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.WRITE_CALENDAR\": {\n" +
"\"desc\": \"允许应用程序写入、删除、更改用户的日历数据。\",\n" +
"\"title\": \"添加或修改日历活动,并在所有者不知情的情况下向邀请对象发送电子邮件\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.WRITE_CALL_LOG\": {\n" +
"\"desc\": \"允许应用程序添加/修改用户的通话记录。\",\n" +
"\"title\": \"写入通话记录\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.WRITE_CONTACTS\": {\n" +
"\"desc\": \"允许应用程序写入用户的联系人数据。\",\n" +
"\"title\": \"修改您的通讯录\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.WRITE_EXTERNAL_STORAGE\": {\n" +
"\"desc\": \"允许应用程序读取/写入外部存储。\",\n" +
"\"title\": \"修改或删除您的USB存储设备中的内容\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.WRITE_SECURE_SETTINGS\": {\n" +
"\"desc\": \"允许应用读取或写入安全系统设置。\",\n" +
"\"title\": \"修改安全系统设置\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.WRITE_SETTINGS\": {\n" +
"\"desc\": \"允许应用读取或写入系统设置。\",\n" +
"\"title\": \"修改系统设置\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.WRITE_SYNC_SETTINGS\": {\n" +
"\"desc\": \"允许应用程序修改某个账户的同步设置。\",\n" +
"\"title\": \"启用和停用同步\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.WRITE_VOICEMAIL\": {\n" +
"\"desc\": \"允许应用程序修改和删除系统中现有的语音邮件。\",\n" +
"\"title\": \"读取修改语音邮件\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.DOWNLOAD_WITHOUT_NOTIFICATION\": {\n" +
"\"desc\": \"允许该应用通过下载管理器下载文件,而不会向用户显示任何通知。\",\n" +
"\"title\": \"直接下载文件而不显示通知\",\n" +
"\"level\": 1\n" +
"},\n" +
"\"android.permission.ACCESS_ALL_EXTERNAL_STORAGE\": {\n" +
"\"desc\": \"允许该应用访问所有用户的外部存储。\",\n" +
"\"title\": \"访问所有用户的外部存储设备\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.ASEC_ACCESS\": {\n" +
"\"desc\": \"允许该应用获取有关内部存储的信息。\",\n" +
"\"title\": \"获取有关内部存储设备的信息\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.ACCESS_MOCK_LOCATION\": {\n" +
"\"desc\": \"允许模拟位置源进行测试。\",\n" +
"\"title\": \"停用模拟地点来源进行测试\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"android.permission.AUTHENTICATE_ACCOUNTS\": {\n" +
"\"desc\": \"允许创建帐户并设置密码。\",\n" +
"\"title\": \"创建账户并设置密码\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"com.android.alarm.permission.SET_ALARM\": {\n" +
"\"desc\": \"允许应用程序为用户设置闹钟。\",\n" +
"\"title\": \"设置闹钟\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"com.android.voicemail.permission.READ_VOICEMAIL\": {\n" +
"\"desc\": \"允许应用程序读取系统中的语音邮件。\",\n" +
"\"title\": \"读取语言邮件\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"com.android.launcher.permission.INSTALL_SHORTCUT\": {\n" +
"\"desc\": \"允许应用程序在桌面中创建快捷方式。\",\n" +
"\"title\": \"安装快捷方式\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"com.android.launcher.permission.UNINSTALL_SHORTCUT\": {\n" +
"\"desc\": \"允许删除快捷方式。\",\n" +
"\"title\": \"卸载快捷方式\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"com.android.voicemail.permission.ADD_VOICEMAIL\": {\n" +
"\"desc\": \"允许该应用向您的语音信箱中添加邮件。\",\n" +
"\"title\": \"添加语音邮件\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"com.android.email.permission.READ_ATTACHMENT\": {\n" +
"\"desc\": \"允许该应用读取您的电子邮件附件。\",\n" +
"\"title\": \"读取电子邮件附件\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"com.android.browser.permission.WRITE_HISTORY_BOOKMARKS\": {\n" +
"\"desc\": \"允许该应用修改浏览器的历史记录或手机上存储的书签。\",\n" +
"\"title\": \"写入网络书签和历史记录\",\n" +
"\"level\": 0\n" +
"},\n" +
"\"com.android.browser.permission.READ_HISTORY_BOOKMARKS\": {\n" +
"\"desc\": \"允许该应用读取浏览器访问过的所有URL的历史记录以及浏览器的所有书签的历史记录。\",\n" +
"\"title\": \"读取您的网络书签和历史记录\",\n" +
"\"level\": 0\n" +
"}\n" +
"}";
static {
try {
PERMISSIONS_MAP_JSON = new JSONObject(PERMISSIONS_MAP_JSON_STR);
} catch (JSONException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,136 @@
package com.example.union_ad_ssgf;
import android.text.TextUtils
import android.util.Log
/**
* @Description: 打印工具类
* 可自动把调用位置所在的类名和方法名作为tag并可设置打印级别Ï
*/
object LogUtil {
// 以下为打印级别,级别从低到高
const val LOG_LEVEL_VERBOSE = 1
const val LOG_LEVEL_DEBUG = 2
const val LOG_LEVEL_INFO = 3
const val LOG_LEVEL_WARN = 4
const val LOG_LEVEL_ERROR = 5
const val LOG_LEVEL_NOLOG = 6
private var AppName = ""
private var PrintLine = false
private var LogLevel = LOG_LEVEL_VERBOSE
private var isShow = false
/**
* 可在打印的TAG前添加应用名标识不设置则不输出
*/
fun setAppName(appName: String) {
AppName = appName
}
/**
* 是否输出打印所在的行数,默认不输出
*/
fun setPrintLine(enable: Boolean) {
PrintLine = enable
}
/**
* 可在打印的TAG前添加应用名标识不设置则不输出
*/
fun setShow(show: Boolean) {
isShow = show
}
/**
* 设置打印级别,且只有等于或高于该级别的打印才会输出
*/
fun setLogLevel(logLevel: Int) {
LogLevel = logLevel
}
fun v() {
log(LOG_LEVEL_VERBOSE, "")
}
fun d() {
log(LOG_LEVEL_DEBUG, "")
}
fun i() {
log(LOG_LEVEL_INFO, "")
}
fun w() {
log(LOG_LEVEL_WARN, "")
}
fun e() {
log(LOG_LEVEL_ERROR, "")
}
fun v(msg: String) {
if (LogLevel <= LOG_LEVEL_VERBOSE) {
log(LOG_LEVEL_VERBOSE, msg)
}
}
fun d(msg: String) {
if (LogLevel <= LOG_LEVEL_DEBUG) {
log(LOG_LEVEL_DEBUG, msg)
}
}
fun i(msg: String) {
if (LogLevel <= LOG_LEVEL_INFO) {
log(LOG_LEVEL_INFO, msg)
}
}
fun w(msg: String) {
if (LogLevel <= LOG_LEVEL_WARN) {
log(LOG_LEVEL_WARN, msg)
}
}
fun e(msg: String) {
if (LogLevel <= LOG_LEVEL_ERROR) {
log(LOG_LEVEL_ERROR, msg)
}
}
private fun log(logLevel: Int, msg: String) {
if(!isShow){
return
}
val caller = Thread.currentThread().stackTrace[4]
var callerClazzName = caller.className
if (callerClazzName.contains(".")) {
callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1)
}
if (callerClazzName.contains("$")) {
callerClazzName = callerClazzName.substring(0, callerClazzName.indexOf("$"))
}
var tag = callerClazzName
if (!TextUtils.isEmpty(AppName)) {
tag = AppName + "_" + tag
}
if (PrintLine) {
tag += "(Line:%d)"
tag = String.format(tag, caller.lineNumber)
}
tag = String.format(tag, callerClazzName)
val message = "---" + caller.methodName + "---" + msg
when (logLevel) {
LOG_LEVEL_VERBOSE -> Log.v(tag, message)
LOG_LEVEL_DEBUG -> Log.d(tag, message)
LOG_LEVEL_INFO -> Log.i(tag, message)
LOG_LEVEL_WARN -> Log.w(tag, message)
LOG_LEVEL_ERROR -> Log.e(tag, message)
LOG_LEVEL_NOLOG -> {
}
}
}
}

View File

@@ -1,183 +0,0 @@
package com.example.union_ad_ssgf
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.util.Log
import com.example.union_ad_ssgf.config.UnionADConfig
import com.example.union_ad_ssgf.page.AdSplashActivity
import com.example.union_ad_ssgf.page.RewardVideoPage
import com.qq.e.comm.managers.GDTAdSdk
import com.qq.e.comm.managers.setting.GlobalSetting.setPersonalizedState
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
/**
* 插件代理中心
*
* @param activity 当前 Activity
* @param bind Flutter 插件绑定对象
*/
class PluginDelegate(var activity: Activity, bind: FlutterPluginBinding) :
MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
private val s = PluginDelegate::class.java.getSimpleName();
// 事件通道
private var eventSink: EventSink? = null
/**
* 初始化代码块
*/
init {
_instance = this
}
companion object {
/**
* 插件代理对象
*/
@SuppressLint("StaticFieldLeak")
private lateinit var _instance: PluginDelegate
fun getInstance(): PluginDelegate {
return _instance
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
val method = call.method
Log.d(s, "MethodChannel onMethodCall method:" + method + " arguments:" + call.arguments)
when (method) {
/**
* 获取当前版本
*/
"getPlatformVersion" -> {
getPlatformVersion(call, result)
}
/**
* 初始化
*/
"initAd" -> {
initAd(call, result)
}
/**
* 设置广告个性化
*/
"setPersonalizedState" -> {
setPersonalizedState(call, result)
}
/**
* 开屏广告
*/
"showSplashAd" -> {
showSplashAd(call, result)
}
/**
* 激励广告
*/
"showRewardVideoAd" -> {
showRewardVideoAd(call, result);
}
/**
* 错误桥接
*/
else -> {
result.notImplemented()
}
}
}
override fun onListen(arguments: Any, events: EventChannel.EventSink) {
Log.d(s, "EventChannel onListen arguments:$arguments")
eventSink = events
}
/**
* 取消事件通道监听
* @param arguments 参数
*/
override fun onCancel(arguments: Any?) {
Log.d(s, "EventChannel onCancel")
eventSink = null
}
/**
* 获取当前版本
* @param call MethodCall
* @param result Result
*/
private fun getPlatformVersion(call: MethodCall, result: MethodChannel.Result) {
result.success(" Union AD Android " + android.os.Build.VERSION.RELEASE);
}
/**
* 初始化广告
* @param call MethodCall
* @param result Result
*/
private fun initAd(call: MethodCall, result: MethodChannel.Result) {
val appId = call.argument<String>("appId")
/**
* 该接口不会采集用户信息
*/
GDTAdSdk.initWithoutStart(activity.applicationContext, appId)
/**
* 调用initWithoutStart后请尽快调用start否则可能影响广告填充造成收入下降
*/
GDTAdSdk.start(object : GDTAdSdk.OnStartListener {
/**
* 推荐开发者在 onStartSuccess 回调后开始拉广告
*/
override fun onStartSuccess() {
Log.e("gdt onStartSuccess", "加载成功")
}
override fun onStartFailed(e: Exception) {
Log.e("gdt onStartFailed:", e.toString())
}
})
result.success(true)
}
/**
* 设置广告个性化
* @param call MethodCall
* @param result Result
*/
private fun setPersonalizedState(call: MethodCall, result: MethodChannel.Result) {
val state = call.argument<Int>("state")!!
setPersonalizedState(state)
result.success(true);
}
/**
* 开屏广告
* @param call MethodCall
* @param result Result
*/
private fun showSplashAd(call: MethodCall, result: MethodChannel.Result) {
val posId: String? = call.argument(UnionADConfig.KEY_POSID)
val logo: String? = call.argument(UnionADConfig.KEY_LOGO)
val fetchDelay: Double = call.argument(UnionADConfig.KEY_FETCH_DELAY)!!
val intent = Intent(activity, AdSplashActivity::class.java)
intent.putExtra(UnionADConfig.KEY_POSID, posId)
intent.putExtra(UnionADConfig.KEY_LOGO, logo)
intent.putExtra(UnionADConfig.KEY_FETCH_DELAY, fetchDelay)
activity.startActivity(intent)
result.success(true)
}
/**
* 显示激励视频广告
* @param call MethodCall
* @param result Result
*/
private fun showRewardVideoAd(call: MethodCall, result: MethodChannel.Result) {
val iad = RewardVideoPage()
iad.showAd(activity, 1, call)
result.success(true)
}
}

View File

@@ -0,0 +1,249 @@
package com.example.union_ad_ssgf;
import android.app.Activity
import android.content.Context
import android.os.Build
import android.util.DisplayMetrics
import android.view.View
import android.view.WindowManager
import java.lang.reflect.InvocationTargetException
object UIUtils {
fun getScreenWidth(context: Context): Int {
return context.resources.displayMetrics.widthPixels
}
fun getScreenHeight(context: Context): Int {
return getRealHeight(context)
}
fun getScreenWidthDp(context: Context): Float {
val scale = context.resources.displayMetrics.density.toFloat()
val width = context.resources.displayMetrics.widthPixels.toFloat()
val num = if (scale <= 0) 1f else scale
return width / num + 0.5f
}
//全面屏、刘海屏适配
fun getHeightDp(activity: Activity): Float {
hideBottomUIMenu(activity)
val height: Float
val realHeight = getRealHeight(activity)
height = if (hasNotchScreen(activity)) {
px2dip(activity, realHeight - getStatusBarHeight(activity))
} else {
px2dip(activity, realHeight.toFloat())
}
return height
}
fun hideBottomUIMenu(activity: Activity?) {
if (activity == null) {
return
}
try {
//隐藏虚拟按键,并且全屏
if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api
val v = activity.window.decorView
v.systemUiVisibility = View.GONE
} else if (Build.VERSION.SDK_INT >= 19) {
//for new api versions.
val decorView = activity.window.decorView
val uiOptions = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
// | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
or View.SYSTEM_UI_FLAG_IMMERSIVE)
decorView.systemUiVisibility = uiOptions
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
//获取屏幕真实高度,不包含下方虚拟导航栏
fun getRealHeight(context: Context): Int {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = windowManager.defaultDisplay
val dm = DisplayMetrics()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
display.getRealMetrics(dm)
} else {
display.getMetrics(dm)
}
return dm.heightPixels
}
//获取状态栏高度
fun getStatusBarHeight(context: Context): Float {
var height = 0f
val resourceId = context.applicationContext.resources.getIdentifier(
"status_bar_height",
"dimen",
"android"
)
if (resourceId > 0) {
height =
context.applicationContext.resources.getDimensionPixelSize(resourceId).toFloat()
}
return height
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
fun px2dip(context: Context, pxValue: Float): Float {
val scale = context.resources.displayMetrics.density
var num = if (scale <= 0) 1f else scale
return pxValue / num + 0.5f
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
fun dip2px(context: Context, dpValue: Float): Float {
val scale = context.resources.displayMetrics.density
return dpValue * scale + 0.5f
}
/**
* 判断是否是刘海屏
* @return
*/
fun hasNotchScreen(activity: Activity): Boolean {
return if (getInt(
"ro.miui.notch",
activity
) == 1 || hasNotchAtHuawei(activity) || hasNotchAtOPPO(activity)
|| hasNotchAtVivo(activity) || isAndroidPHasNotch(activity)
) {
true
} else false
}
/**
* Android P 刘海屏判断
* @param activity
* @return
*/
fun isAndroidPHasNotch(activity: Activity?): Boolean {
var ret = false
if (Build.VERSION.SDK_INT >= 28) {
try {
val windowInsets = Class.forName("android.view.WindowInsets")
val method = windowInsets.getMethod("getDisplayCutout")
val displayCutout = method.invoke(windowInsets)
if (displayCutout != null) {
ret = true
}
} catch (e: Exception) {
e.printStackTrace()
}
}
return ret
}
/**
* 小米刘海屏判断.
* @return 0 if it is not notch ; return 1 means notch
* @throws IllegalArgumentException if the key exceeds 32 characters
*/
fun getInt(key: String?, activity: Activity): Int {
var result = 0
if (isMiui()) {
try {
val classLoader = activity.classLoader
val SystemProperties = classLoader.loadClass("android.os.SystemProperties")
//参数类型
val paramTypes = arrayOfNulls<Class<*>?>(2)
paramTypes[0] = String::class.java
paramTypes[1] = Int::class.javaPrimitiveType
val getInt = SystemProperties.getMethod("getInt", *paramTypes)
//参数
val params = arrayOfNulls<Any>(2)
params[0] = String
params[1] = Int
result = getInt.invoke(SystemProperties, *params) as Int
} catch (e: ClassNotFoundException) {
e.printStackTrace()
} catch (e: NoSuchMethodException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: IllegalArgumentException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
}
}
return result
}
/**
* 华为刘海屏判断
* @return
*/
fun hasNotchAtHuawei(context: Context): Boolean {
var ret = false
try {
val classLoader = context.classLoader
val HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil")
val get = HwNotchSizeUtil.getMethod("hasNotchInScreen")
ret = get.invoke(HwNotchSizeUtil) as Boolean
} catch (e: ClassNotFoundException) {
} catch (e: NoSuchMethodException) {
} catch (e: Exception) {
} finally {
return ret
}
}
val VIVO_NOTCH = 0x00000020 //是否有刘海
val VIVO_FILLET = 0x00000008 //是否有圆角
/**
* VIVO刘海屏判断
* @return
*/
fun hasNotchAtVivo(context: Context): Boolean {
var ret = false
try {
val classLoader = context.classLoader
val FtFeature = classLoader.loadClass("android.util.FtFeature")
val method = FtFeature.getMethod("isFeatureSupport", Int::class.javaPrimitiveType)
ret = method.invoke(FtFeature, VIVO_NOTCH) as Boolean
} catch (e: ClassNotFoundException) {
} catch (e: NoSuchMethodException) {
} catch (e: Exception) {
} finally {
return ret
}
}
/**
* OPPO刘海屏判断
* @return
*/
fun hasNotchAtOPPO(context: Context): Boolean {
return context.packageManager.hasSystemFeature("com.oppo.feature.screen.heteromorphism")
}
fun isMiui(): Boolean {
var sIsMiui = false
try {
val clz = Class.forName("miui.os.Build")
if (clz != null) {
sIsMiui = true
return sIsMiui
}
} catch (e: Exception) {
// ignore
}
return sIsMiui
}
}

View File

@@ -0,0 +1,17 @@
package com.example.union_ad_ssgf;
class UnionAdSsgfConfig {
companion object {
// event事件
const val adevent = "com.example.union_ad_ssgf/adevent"
// BannerAdView
const val bannerAdView = "com.example.union_ad_ssgf/BannerAdView"
// SplashAdView
const val splashAdView = "com.example.union_ad_ssgf/SplashAdView"
// NativeExpressADView
const val nativeAdView = "com.example.union_ad_ssgf/NativeExpressAdView"
}
}

View File

@@ -0,0 +1,38 @@
package com.example.union_ad_ssgf;
import android.content.Context
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
class UnionAdSsgfEventPlugin : FlutterPlugin, EventChannel.StreamHandler {
companion object {
private var eventChannel: EventChannel? = null
private var eventSink: EventChannel.EventSink? = null
private var context: Context? = null
fun sendContent(content: MutableMap<String, Any?>) {
eventSink?.success(content);
}
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
eventChannel = EventChannel(binding.binaryMessenger, UnionAdSsgfConfig.adevent)
eventChannel!!.setStreamHandler(this)
context = binding.applicationContext
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
eventChannel = null
eventChannel!!.setStreamHandler(null)
}
}

View File

@@ -1,87 +1,117 @@
package com.example.union_ad_ssgf
package com.example.union_ad_ssgf;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel;
import android.app.Activity
import android.content.Context
import android.util.Log
import androidx.annotation.NonNull
import com.example.union_ad_ssgf.interstitialad.InterstitialAd
import com.example.union_ad_ssgf.rewardvideoad.RewardVideoAd
import com.qq.e.comm.DownloadService
import com.qq.e.comm.managers.GDTAdSdk
import com.qq.e.comm.managers.setting.GlobalSetting
import com.qq.e.comm.managers.status.SDKStatus
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.lang.Exception
/**
* 三商共富 - 三方插件
* 腾讯优量汇插件
*/
class UnionAdSsgfPlugin : FlutterPlugin, ActivityAware {
/**
* 方法通道:
*
* 用于Flutter与原生Android之间通信的 MethodChannel
* 此本地引用用于向Flutter引擎注册插件并在Flutter引擎与Activity分离时注销它
*/
private lateinit var methodChannel: MethodChannel
class UnionAdSsgfPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
/**
* 事件通道
*
* 用于Flutter与原生Android之间通信的 EventChannel
* 此本地引用用于向Flutter引擎注册插件并在Flutter引擎与Activity分离时注销它
*/
private lateinit var eventChannel: EventChannel
private lateinit var channel: MethodChannel
private var applicationContext: Context? = null
private var mActivity: Activity? = null
private var mFlutterPluginBinding: FlutterPlugin.FlutterPluginBinding? = null
/**
* 插件代理
*/
var delegate: PluginDelegate? = null
/**
* 插件连接器
*/
private lateinit var bind: FlutterPlugin.FlutterPluginBinding
/**
* 初始化方法通道和事件通道
*/
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
bind = flutterPluginBinding
// 注册 - 方法通道
methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "union_ad_ssgf_method")
// 注册 - 监听通道
eventChannel = EventChannel(flutterPluginBinding.getBinaryMessenger(), "union_ad_ssgf_event")
}
/**
* 注销通道
*/
override fun onDetachedFromEngine(p0: FlutterPlugin.FlutterPluginBinding) {
// 解除 - 方法通道
methodChannel.setMethodCallHandler(null)
// 解除 - 事件通道
eventChannel.setStreamHandler(null)
}
/**
* 绑定activity
*/
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
delegate = PluginDelegate(binding.activity, bind)
methodChannel.setMethodCallHandler(delegate)
eventChannel.setStreamHandler(delegate)
}
override fun onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity()
mActivity = binding.activity
Log.e("UnionAdSsgfPlugin->", "onAttachedToActivity");
UnionAdSsgfViewPlugin.registerWith(mFlutterPluginBinding!!, mActivity!!)
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
onAttachedToActivity(binding)
mActivity = binding.activity
Log.e("UnionAdSsgfPlugin->", "onReattachedToActivityForConfigChanges")
}
override fun onDetachedFromActivityForConfigChanges() {
mActivity = null
Log.e("UnionAdSsgfPlugin->", "onDetachedFromActivityForConfigChanges")
}
override fun onDetachedFromActivity() {
this.delegate = null
mActivity = null
Log.e("UnionAdSsgfPlugin->", "onDetachedFromActivity")
}
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_union_ad")
channel.setMethodCallHandler(this)
applicationContext = flutterPluginBinding.applicationContext
mFlutterPluginBinding = flutterPluginBinding
UnionAdSsgfEventPlugin().onAttachedToEngine(flutterPluginBinding)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
//注册初始化
if (call.method == "register") {
val appId = call.argument<String>("androidId")
val debug = call.argument<Boolean>("debug")
val channelId = call.argument<Int>("channelId")
val personalized = call.argument<Int>("personalized")
// 日志
LogUtil.setAppName("flutter_union_ad")
LogUtil.setShow(debug!!)
// 是否开启个性化
GlobalSetting.setPersonalizedState(personalized!!)
// 设置渠道id
GlobalSetting.setChannel(channelId!!)
GDTAdSdk.initWithoutStart(applicationContext, appId)
GDTAdSdk.start(object : GDTAdSdk.OnStartListener {
override fun onStartSuccess() {
result.success(true)
}
override fun onStartFailed(p0: Exception?) {
result.success(false)
}
})
// 获取sdk版本
} else if (call.method == "getSDKVersion") {
result.success(SDKStatus.getIntegrationSDKVersion())
// 预加载激励广告
} else if (call.method == "loadRewardVideoAd") {
RewardVideoAd.init(applicationContext!!, call.arguments as Map<*, *>)
result.success(true)
// 展示激励广告
} else if (call.method == "showRewardVideoAd") {
RewardVideoAd.showAd(call.arguments as Map<*, *>)
result.success(true)
// 预加载插屏广告
} else if (call.method == "loadInterstitialAD") {
InterstitialAd.init(mActivity!!, call.arguments as Map<*, *>)
result.success(true)
// 展示插屏广告
} else if (call.method == "showInterstitialAD") {
InterstitialAd.showAd(call.arguments as Map<*, *>)
result.success(true)
// 进入下载列表
} else if (call.method == "enterAPPDownloadListPage") {
DownloadService.enterAPPDownloadListPage(mActivity)
result.success(true)
} else {
result.notImplemented()
}
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}

View File

@@ -0,0 +1,27 @@
package com.example.union_ad_ssgf;
import android.app.Activity
import com.example.union_ad_ssgf.bannerad.BannerAdViewFactory
import com.example.union_ad_ssgf.expressad.NativeExpressAdViewFactory
import com.example.union_ad_ssgf.splashad.SplashAdViewFactory
import io.flutter.embedding.engine.plugins.FlutterPlugin
object UnionAdSsgfViewPlugin {
fun registerWith(binding: FlutterPlugin.FlutterPluginBinding, activity: Activity) {
// 注册banner广告
binding.platformViewRegistry.registerViewFactory(
UnionAdSsgfConfig.bannerAdView,
BannerAdViewFactory(binding.binaryMessenger, activity)
)
// 注册splash广告
binding.platformViewRegistry.registerViewFactory(
UnionAdSsgfConfig.splashAdView,
SplashAdViewFactory(binding.binaryMessenger, activity)
)
// 注册Express广告
binding.platformViewRegistry.registerViewFactory(
UnionAdSsgfConfig.nativeAdView,
NativeExpressAdViewFactory(binding.binaryMessenger, activity)
)
}
}

View File

@@ -0,0 +1,175 @@
package com.example.union_ad_ssgf.bannerad
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import com.example.union_ad_ssgf.*
import com.qq.e.ads.banner2.UnifiedBannerADListener
import com.qq.e.ads.banner2.UnifiedBannerView
import com.qq.e.comm.pi.IBidding
import com.qq.e.comm.util.AdError
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView
/**
* @Description: 平台模板Banner广告
**/
internal class BannerAdView(
var activity: Activity,
messenger: BinaryMessenger,
id: Int,
params: Map<String?, Any?>
) :
PlatformView, UnifiedBannerADListener, MethodChannel.MethodCallHandler {
private val TAG = "BannerAdView"
private var mContainer: FrameLayout? = null
//广告所需参数
private var codeId: String
private var viewWidth: Float
private var viewHeight: Float
private var unifiedBannerView: UnifiedBannerView? = null
private var channel: MethodChannel?
private var downloadConfirm: Boolean
//是否开启竞价
private var isBidding: Boolean = params["isBidding"] as Boolean
init {
codeId = params["androidId"] as String
var width = params["viewWidth"] as Double
var height = params["viewHeight"] as Double
downloadConfirm = params["downloadConfirm"] as Boolean
viewWidth = width.toFloat()
viewHeight = height.toFloat()
mContainer = FrameLayout(activity)
mContainer?.layoutParams?.width = ViewGroup.LayoutParams.WRAP_CONTENT
mContainer?.layoutParams?.height = ViewGroup.LayoutParams.WRAP_CONTENT
channel = MethodChannel(messenger, UnionAdSsgfConfig.bannerAdView + "_" + id)
channel?.setMethodCallHandler(this)
loadBannerAd()
}
private fun loadBannerAd() {
unifiedBannerView = UnifiedBannerView(activity, codeId, this)
unifiedBannerView?.loadAD()
}
override fun getView(): View {
return mContainer!!
}
//广告加载失败error 对象包含了错误码和错误信息
override fun onNoAD(p0: AdError?) {
LogUtil.e("$TAG Banner广告加载失败 ${p0?.errorCode} ${p0?.errorMsg}")
var map: MutableMap<String, Any?> =
mutableMapOf("code" to p0?.errorCode, "message" to p0?.errorMsg)
channel?.invokeMethod("onFail", map)
}
//广告加载成功回调表示广告相关的资源已经加载完毕Ready To Show
override fun onADReceive() {
mContainer?.removeAllViews()
if (unifiedBannerView == null) {
LogUtil.e("$TAG Banner广告加载失败 unifiedBannerView不存在或已销毁")
var map: MutableMap<String, Any?> =
mutableMapOf("code" to 0, "message" to "BannerView不存在或已销毁")
channel?.invokeMethod("onFail", map)
return
}
if (downloadConfirm) {
unifiedBannerView?.setDownloadConfirmListener(DownloadConfirmHelper.DOWNLOAD_CONFIRM_LISTENER)
}
//竞价 则返回价格 不直接加载
if (isBidding) {
channel?.invokeMethod("onECPM", mutableMapOf(
"ecpmLevel" to unifiedBannerView?.ecpmLevel,
"ecpm" to unifiedBannerView?.ecpm
))
} else {
LogUtil.e("$TAG Banner广告加载成功回调")
mContainer?.addView(unifiedBannerView)
val map: MutableMap<String, Any?> = mutableMapOf(
"width" to UIUtils.px2dip(activity, unifiedBannerView?.width!!.toFloat()),
"height" to UIUtils.px2dip(activity, unifiedBannerView?.height!!.toFloat())
)
channel?.invokeMethod("onShow", map)
}
}
//当广告曝光时发起的回调
override fun onADExposure() {
LogUtil.e("$TAG Banner广告曝光")
channel?.invokeMethod("onExpose", "")
}
//当广告关闭时调用
override fun onADClosed() {
LogUtil.e("$TAG Banner广告关闭")
channel?.invokeMethod("onClose", "")
}
//当广告点击时发起的回调,由于点击去重等原因可能和平台最终的统计数据有差异
override fun onADClicked() {
LogUtil.e("$TAG Banner广告点击")
channel?.invokeMethod("onClick", "")
}
//由于广告点击离开 APP 时调用
override fun onADLeftApplication() {
LogUtil.e("$TAG Banner广告点击离开 APP")
}
override fun dispose() {
unifiedBannerView?.destroy()
unifiedBannerView = null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
//竞价成功
"biddingSucceeded" -> {
var arguments = call.arguments as Map<*, *>
val map: MutableMap<String, Any?> = mutableMapOf(
//对应值为竞胜出价类型为Integer
IBidding.EXPECT_COST_PRICE to arguments["expectCostPrice"],
//对应值为最大竞败方出价类型为Integer
IBidding.HIGHEST_LOSS_PRICE to arguments["highestLossPrice"],
)
unifiedBannerView?.sendWinNotification(map)
//展示banner
mContainer?.addView(unifiedBannerView)
val map2: MutableMap<String, Any?> = mutableMapOf(
"width" to UIUtils.px2dip(activity, unifiedBannerView?.width!!.toFloat()),
"height" to UIUtils.px2dip(activity, unifiedBannerView?.height!!.toFloat())
)
channel?.invokeMethod("onShow", map2)
}
//竞价失败
"biddingFail" -> {
var arguments = call.arguments as Map<*, *>
val map: MutableMap<String, Any?> = mutableMapOf(
//值为本次竞胜方出价单位类型为Integer。选填
IBidding.WIN_PRICE to arguments["winPrice"],
//值为优量汇广告竞败原因类型为Integer。必填
IBidding.LOSS_REASON to arguments["lossReason"],
//值为本次竞胜方渠道ID类型为Integer。必填。
IBidding.ADN_ID to arguments["adnId"],
)
unifiedBannerView?.sendLossNotification(map)
}
}
}
}

View File

@@ -0,0 +1,17 @@
package com.example.union_ad_ssgf.bannerad
import android.app.Activity
import android.content.Context
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
internal class BannerAdViewFactory(private val messenger: BinaryMessenger, private val activity: Activity) : PlatformViewFactory(
StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
val params = args as Map<String?, Any?>
return BannerAdView(activity,messenger, viewId, params)
}
}

View File

@@ -1,40 +0,0 @@
package com.example.union_ad_ssgf.config
import android.annotation.SuppressLint
class UnionADConfig {
/*初始化代码块*/
init {
_instance = this
}
companion object {
// 插件代理对象
@SuppressLint("StaticFieldLeak")
private lateinit var _instance: UnionADConfig
fun getInstance(): UnionADConfig {
return _instance
}
// Banner View
const val KEY_BANNER_VIEW = "flutter_qq_ads_banner"
// Feed View
const val KEY_FEED_VIEW = "flutter_qq_ads_feed"
// 广告参数
const val KEY_POSID = "posId"
// 广告参数
const val KEY_ADID = "adId"
// logo 参数
const val KEY_LOGO = "logo"
// fetchDelay 参数
const val KEY_FETCH_DELAY = "fetchDelay"
}
}

View File

@@ -1,28 +0,0 @@
package com.example.union_ad_ssgf.event
class AdErrorEvent(posId: String?, adId: String?, viewId: Int?, errCode: Int, errMsg: String?) :
AdEvent(posId, adId, viewId, AdEventAction.onAdError) {
// 错误码
private var errCode = 0
// 错误信息
private var errMsg: String? = null
// 错误事件实体
init {
this.errMsg = errMsg
this.errCode = errCode
}
/**
* 重写 toMap 方法
* @return 返回错误事件的map
*/
override fun toMap(): HashMap<String, Any?> {
val newMap = super.toMap()
newMap["errCode"] = errCode
newMap["errMsg"] = errMsg
return newMap
}
}

View File

@@ -1,36 +0,0 @@
package com.example.union_ad_ssgf.event
import java.util.HashMap
open class AdEvent(posId: String?, adId: String?, viewId: Int?, action: String?) {
// 广告位 id
private var posId: String? = null
// 广告 id
private var adId: String? = null
private var viewId: Int? = null
// 操作
private var action: String? = null
init {
this.adId = adId
this.posId = posId
this.action = action
this.viewId = viewId
}
/**
* 转换为 Map 方面传输
* @return 转换后的 Map 对象
*/
open fun toMap(): HashMap<String, Any?> {
val map = HashMap<String, Any?>()
map["adId"] = adId
map["posId"] = posId
map["action"] = action
map["viewId"] = viewId
return map
}
}

View File

@@ -1,32 +0,0 @@
package com.example.union_ad_ssgf.event
class AdEventAction {
companion object {
// 广告错误
val onAdError = "onAdError"
// 广告加载成功
val onAdLoaded = "onAdLoaded"
// 广告填充
val onAdPresent = "onAdPresent"
// 广告曝光
val onAdExposure = "onAdExposure"
// 广告关闭
val onAdClosed = "onAdClosed"
// 广告点击
val onAdClicked = "onAdClicked"
// 广告跳过
val onAdSkip = "onAdSkip"
// 广告播放或计时完毕
val onAdComplete = "onAdComplete"
// 获得广告激励
val onAdReward = "onAdReward"
}
}

View File

@@ -1,31 +0,0 @@
package com.example.union_ad_ssgf.event
class AdEventHandler {
companion object {
// 广告事件处理对象
@Volatile private var _instance: AdEventHandler? = null
/**
* 获取广告事件处理类
* @return 广告事件处理对象
*/
fun getInstance(): AdEventHandler? {
if (_instance == null) {
synchronized(AdEventHandler::class.java) { _instance = AdEventHandler() }
}
return _instance
}
}
/**
* 添加广告事件
* @param event 广告事件
*/
fun sendEvent(event: AdEvent?) {
if (event != null) {
// PluginDelegate.getInstance().addEvent(event.toMap())
}
}
}

View File

@@ -1,25 +0,0 @@
package com.example.union_ad_ssgf.event
/**
* @param transId 服务端验证唯一id
* @param customData 服务端验证的自定义信息
* @param userId 服务端验证的用户信息
*/
class AdRewardEvent(
posId: String?,
adId: String?,
viewId: Int?,
action: String?,
var transId: String,
var customData: String,
var userId: String,
) : AdEvent(posId, adId, viewId, action) {
override fun toMap(): HashMap<String, Any?> {
val newMap = super.toMap()
newMap["transId"] = transId
newMap["customData"] = customData
newMap["userId"] = userId
return newMap
}
}

View File

@@ -0,0 +1,267 @@
package com.example.union_ad_ssgf.expressad
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import com.example.union_ad_ssgf.*
import com.qq.e.ads.cfg.VideoOption
import com.qq.e.ads.nativ.ADSize
import com.qq.e.ads.nativ.NativeExpressAD
import com.qq.e.ads.nativ.NativeExpressADView
import com.qq.e.ads.nativ.NativeExpressMediaListener
import com.qq.e.comm.constants.AdPatternType
import com.qq.e.comm.pi.IBidding
import com.qq.e.comm.util.AdError
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView
/**
* @Description: 平台模板NativeExpressAdView广告
**/
internal class NativeExpressAdView(
var activity: Activity,
messenger: BinaryMessenger,
id: Int,
params: Map<String?, Any?>
) :
PlatformView, NativeExpressAD.NativeExpressADListener, NativeExpressMediaListener ,
MethodChannel.MethodCallHandler {
private var mContainer: FrameLayout? = null
//广告所需参数
private var codeId: String = params["androidId"] as String
private var viewWidth: Int = params["viewWidth"] as Int
private var viewHeight: Int = params["viewHeight"] as Int
private var downloadConfirm: Boolean = params["downloadConfirm"] as Boolean
//是否开启竞价
private var isBidding: Boolean = params["isBidding"] as Boolean
private var nativeExpressAD: NativeExpressAD? = null
private var nativeExpressAdView: NativeExpressADView? = null
private var channel: MethodChannel?
init {
mContainer = FrameLayout(activity)
mContainer?.layoutParams?.width = ViewGroup.LayoutParams.WRAP_CONTENT
mContainer?.layoutParams?.height = ViewGroup.LayoutParams.WRAP_CONTENT
channel = MethodChannel(messenger, UnionAdSsgfConfig.nativeAdView + "_" + id)
channel?.setMethodCallHandler(this)
loadExpressAd()
}
private fun loadExpressAd() {
val adSize: ADSize = when {
viewWidth == 0 -> {
ADSize(ADSize.FULL_WIDTH, viewHeight)
}
viewHeight == 0 -> {
ADSize(viewWidth, ADSize.AUTO_HEIGHT)
}
else -> {
ADSize(viewWidth, viewHeight)
}
}
nativeExpressAD = NativeExpressAD(activity, adSize, codeId, this)
nativeExpressAD?.setVideoOption(
VideoOption.Builder()
.setAutoPlayPolicy(VideoOption.AutoPlayPolicy.ALWAYS)
.setAutoPlayMuted(true)
.build()
)
nativeExpressAD?.loadAD(1)
}
override fun getView(): View {
return mContainer!!
}
/**************平台模板广告加载、渲染、点击状态的回调。****************/
//无广告填充
override fun onNoAD(p0: AdError?) {
LogUtil.e("广告加载失败 ${p0?.errorCode} ${p0?.errorMsg}")
val map: MutableMap<String, Any?> =
mutableMapOf("code" to p0?.errorCode, "message" to p0?.errorMsg)
channel?.invokeMethod("onFail", map)
}
//广告数据加载成功,返回了可以用来展示广告的 NativeExpressADView
// 但是想让广告曝光还需要调用 NativeExpressADView 的 render 方法
override fun onADLoaded(p0: MutableList<NativeExpressADView>?) {
// 释放前一个 NativeExpressADView 的资源
if (nativeExpressAdView != null) {
nativeExpressAdView?.destroy()
nativeExpressAdView = null
}
if (p0?.size == 0) {
LogUtil.e("未拉取到广告")
val map: MutableMap<String, Any?> = mutableMapOf("code" to 0, "message" to "未拉取到广告")
channel?.invokeMethod("onFail", map)
}
LogUtil.e("广告数据加载成功")
nativeExpressAdView = p0!![0]
if (nativeExpressAdView?.boundData?.adPatternType == AdPatternType.NATIVE_VIDEO) {
nativeExpressAdView?.setMediaListener(this)
}
if (downloadConfirm) {
nativeExpressAdView?.setDownloadConfirmListener(DownloadConfirmHelper.DOWNLOAD_CONFIRM_LISTENER)
}
//竞价 则返回价格 不直接加载
if (isBidding) {
channel?.invokeMethod("onECPM", mutableMapOf(
"ecpmLevel" to nativeExpressAdView?.ecpmLevel,
"ecpm" to nativeExpressAdView?.ecpm
))
} else {
if (nativeExpressAdView != null) {
if (mContainer?.childCount!! > 0) {
mContainer?.removeAllViews()
}
mContainer?.addView(nativeExpressAdView!!.rootView)
nativeExpressAdView!!.render()
}
}
}
//NativeExpressADView 渲染广告失败
override fun onRenderFail(p0: NativeExpressADView?) {
LogUtil.e("渲染广告失败")
val map: MutableMap<String, Any?> = mutableMapOf("code" to 0, "message" to "渲染广告失败")
channel?.invokeMethod("onFail", map)
nativeExpressAdView?.destroy()
}
//NativeExpressADView 渲染广告成功
override fun onRenderSuccess(p0: NativeExpressADView?) {
val map: MutableMap<String, Any?> = mutableMapOf(
"width" to UIUtils.px2dip(activity, p0?.width!!.toFloat()),
"height" to UIUtils.px2dip(activity, p0?.height!!.toFloat())
)
channel?.invokeMethod("onShow", map)
}
//广告曝光
override fun onADExposure(p0: NativeExpressADView?) {
LogUtil.e("广告曝光")
channel?.invokeMethod("onExpose", "")
}
//广告点击
override fun onADClicked(p0: NativeExpressADView?) {
LogUtil.e("广告点击")
channel?.invokeMethod("onClick", "")
}
//广告被关闭,将不再显示广告,此时广告对象已经释放资源,不可以再次用来展示了
override fun onADClosed(p0: NativeExpressADView?) {
LogUtil.e("广告被关闭")
channel?.invokeMethod("onClose", "")
p0?.destroy()
}
//因为广告点击等原因离开当前 app 时调用
override fun onADLeftApplication(p0: NativeExpressADView?) {
LogUtil.e("因为广告点击等原因离开当前 app")
}
/*****************平台模板视频广告播放状态回调接口,专用于带有视频素材的广告对象**************/
//视频播放 View 初始化完成
override fun onVideoInit(p0: NativeExpressADView?) {
LogUtil.e("视频播放 View 初始化完成")
}
//视频下载中
override fun onVideoLoading(p0: NativeExpressADView?) {
LogUtil.e("视频下载中")
}
//视频下载完成
override fun onVideoCached(p0: NativeExpressADView?) {
LogUtil.e("视频下载完成")
// nativeExpressAdView?.
}
//视频播放器初始化完成准备好可以播放了videoDuration 是视频素材的时间长度,单位为 ms
override fun onVideoReady(p0: NativeExpressADView?, p1: Long) {
LogUtil.e("视频播放器初始化完成")
// nativeExpressAdView?
}
//视频开始播放
override fun onVideoStart(p0: NativeExpressADView?) {
LogUtil.e("视频开始播放")
}
//视频暂停
override fun onVideoPause(p0: NativeExpressADView?) {
LogUtil.e("视频暂停")
}
//视频播放结束,手动调用 stop 或者自然播放到达最后一帧时都会触发
override fun onVideoComplete(p0: NativeExpressADView?) {
LogUtil.e("视频播放结束")
}
//视频播放时出现错误error 对象包含了错误码和错误信息错误码的详细内容可以参考文档第5章
override fun onVideoError(p0: NativeExpressADView?, p1: AdError?) {
LogUtil.e("视频播放时出现错误 ${p1?.errorCode} ${p1?.errorMsg}")
}
//进入视频落地页
override fun onVideoPageOpen(p0: NativeExpressADView?) {
LogUtil.e("进入视频落地页")
}
//退出视频落地页
override fun onVideoPageClose(p0: NativeExpressADView?) {
LogUtil.e("退出视频落地页")
}
override fun dispose() {
nativeExpressAdView?.destroy()
mContainer?.removeAllViews()
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method){
//竞价成功
"biddingSucceeded" -> {
var arguments = call.arguments as Map<*, *>
val map: MutableMap<String, Any?> = mutableMapOf(
//对应值为竞胜出价类型为Integer
IBidding.EXPECT_COST_PRICE to arguments["expectCostPrice"],
//对应值为最大竞败方出价类型为Integer
IBidding.HIGHEST_LOSS_PRICE to arguments["highestLossPrice"],
)
nativeExpressAdView?.sendWinNotification(map)
//展示信息流
mContainer?.addView(nativeExpressAdView!!.rootView)
nativeExpressAdView!!.render()
}
//竞价失败
"biddingFail" -> {
var arguments = call.arguments as Map<*, *>
val map: MutableMap<String, Any?> = mutableMapOf(
//值为本次竞胜方出价单位类型为Integer。选填
IBidding.WIN_PRICE to arguments["winPrice"],
//值为优量汇广告竞败原因类型为Integer。必填
IBidding.LOSS_REASON to arguments["lossReason"],
//值为本次竞胜方渠道ID类型为Integer。必填。
IBidding.ADN_ID to arguments["adnId"],
)
nativeExpressAdView?.sendLossNotification(map)
}
}
}
}

View File

@@ -0,0 +1,17 @@
package com.example.union_ad_ssgf.expressad
import android.app.Activity
import android.content.Context
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
internal class NativeExpressAdViewFactory(private val messenger: BinaryMessenger, private val activity: Activity) : PlatformViewFactory(
StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
val params = args as Map<String?, Any?>
return NativeExpressAdView(activity,messenger, viewId, params)
}
}

View File

@@ -0,0 +1,227 @@
package com.example.union_ad_ssgf.interstitialad
import android.annotation.SuppressLint
import android.app.Activity
import com.example.union_ad_ssgf.DownloadConfirmHelper
import com.example.union_ad_ssgf.LogUtil
import com.qq.e.ads.interstitial2.UnifiedInterstitialAD
import com.qq.e.ads.interstitial2.UnifiedInterstitialADListener
import com.example.union_ad_ssgf.UnionAdSsgfEventPlugin
import com.qq.e.ads.interstitial2.ADRewardListener
import com.qq.e.comm.pi.IBidding
import com.qq.e.comm.util.AdError
@SuppressLint("StaticFieldLeak")
object InterstitialAd {
private val TAG = "InterstitialAd"
private lateinit var context: Activity
private var unifiedInterstitialAD: UnifiedInterstitialAD? = null
private var codeId: String? = null
private var isFullScreen: Boolean? = false
private var downloadConfirm: Boolean = false
//是否开启竞价
private var isBidding: Boolean = false
fun init(context: Activity, params: Map<*, *>) {
this.context = context
this.codeId = params["androidId"] as String
this.isFullScreen = params["isFullScreen"] as Boolean
this.downloadConfirm = params["downloadConfirm"] as Boolean
this.isBidding = params["isBidding"] as Boolean
loadInterstitialAD()
}
private fun loadInterstitialAD() {
unifiedInterstitialAD = UnifiedInterstitialAD(
context,
codeId,
interstitialADListener
)
if (isFullScreen!!) {
unifiedInterstitialAD?.setRewardListener(adRewardListener)
unifiedInterstitialAD?.loadFullScreenAD()
} else {
unifiedInterstitialAD?.loadAD()
}
}
fun showAd(params: Map<*, *>) {
if (unifiedInterstitialAD == null) {
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "interactAd", "onAdMethod" to "onUnReady")
UnionAdSsgfEventPlugin.sendContent(map)
LogUtil.e("$TAG 插屏全屏视频广告显示失败,无广告")
return
}
// 广告是否有效,无效广告将无法展示
if (!unifiedInterstitialAD?.isValid!!) {
val map: MutableMap<String, Any?> = mutableMapOf(
"adType" to "interactAd",
"onAdMethod" to "onFail",
"code" to 1,
"message" to "插屏全屏视频视频广告显示失败,无效广告"
)
UnionAdSsgfEventPlugin.sendContent(map)
LogUtil.e("$TAG 插屏全屏视频广告显示失败,无效广告")
unifiedInterstitialAD?.close()
unifiedInterstitialAD?.destroy()
unifiedInterstitialAD = null
return
}
//是否为竞价模式
if (isBidding) {
val isSuccess: Boolean = params["isSuccess"] as Boolean
//是否成功
if (isSuccess) {
unifiedInterstitialAD?.sendWinNotification(
mutableMapOf(
//对应值为竞胜出价类型为Integer
IBidding.EXPECT_COST_PRICE to params["expectCostPrice"],
//对应值为最大竞败方出价类型为Integer
IBidding.HIGHEST_LOSS_PRICE to params["highestLossPrice"],
)
)
if (isFullScreen!!) {
unifiedInterstitialAD?.showFullScreenAD(context)
} else {
unifiedInterstitialAD?.show()
}
} else {
unifiedInterstitialAD?.sendLossNotification(
mutableMapOf(
//值为本次竞胜方出价单位类型为Integer。选填
IBidding.WIN_PRICE to params["winPrice"],
//值为优量汇广告竞败原因类型为Integer。必填
IBidding.LOSS_REASON to params["lossReason"],
//值为本次竞胜方渠道ID类型为Integer。必填。
IBidding.ADN_ID to params["adnId"],
)
)
}
} else {
if (isFullScreen!!) {
unifiedInterstitialAD?.showFullScreenAD(context)
} else {
unifiedInterstitialAD?.show()
}
}
}
private var interstitialADListener = object : UnifiedInterstitialADListener {
//插屏全屏视频广告加载完毕,此回调后才可以调用 show 方法
override fun onADReceive() {
LogUtil.e("$TAG 插屏全屏视频广告加载完毕 $downloadConfirm")
if (downloadConfirm) {
unifiedInterstitialAD?.setDownloadConfirmListener(DownloadConfirmHelper.DOWNLOAD_CONFIRM_LISTENER)
}
}
//插屏全屏视频视频广告,视频素材下载完成
override fun onVideoCached() {
LogUtil.e("$TAG 插屏全屏视频视频广告,视频素材下载完成")
}
//广告加载失败error 对象包含了错误码和错误信息
override fun onNoAD(p0: AdError?) {
LogUtil.e("$TAG 插屏全屏视频视频广告,加载失败 ${p0?.errorCode} ${p0?.errorMsg}")
val map: MutableMap<String, Any?> = mutableMapOf(
"adType" to "interactAd",
"onAdMethod" to "onFail",
"code" to p0?.errorCode,
"message" to p0?.errorMsg
)
UnionAdSsgfEventPlugin.sendContent(map)
}
//插屏全屏视频广告展开时回调
override fun onADOpened() {
LogUtil.e("$TAG 插屏全屏视频广告展开时回调")
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "interactAd", "onAdMethod" to "onShow")
UnionAdSsgfEventPlugin.sendContent(map)
}
//插屏全屏视频广告曝光时回调
override fun onADExposure() {
LogUtil.e("$TAG 插屏全屏视频广告曝光时回调")
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "interactAd", "onAdMethod" to "onExpose")
UnionAdSsgfEventPlugin.sendContent(map)
}
//插屏全屏视频广告点击时回调
override fun onADClicked() {
LogUtil.e("$TAG 插屏全屏视频广告点击时回调")
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "interactAd", "onAdMethod" to "onClick")
UnionAdSsgfEventPlugin.sendContent(map)
}
override fun onADLeftApplication() {
LogUtil.e("$TAG 插屏全屏视频视频广告,渲染成功")
}
//插屏全屏视频广告关闭时回调
override fun onADClosed() {
LogUtil.e("$TAG 插屏全屏视频广告关闭时回调")
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "interactAd", "onAdMethod" to "onClose")
UnionAdSsgfEventPlugin.sendContent(map)
unifiedInterstitialAD?.close()
unifiedInterstitialAD?.destroy()
unifiedInterstitialAD = null
}
// 插屏全屏视频视频广告,渲染成功
override fun onRenderSuccess() {
LogUtil.e("$TAG 插屏全屏视频视频广告,渲染成功")
if (isBidding) {
val map: MutableMap<String, Any?> =
mutableMapOf(
"adType" to "interactAd",
"onAdMethod" to "onECPM",
"ecpmLevel" to unifiedInterstitialAD?.ecpmLevel,
"ecpm" to unifiedInterstitialAD?.ecpm
)
UnionAdSsgfEventPlugin.sendContent(map)
} else {
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "interactAd", "onAdMethod" to "onReady")
UnionAdSsgfEventPlugin.sendContent(map)
}
}
//插屏全屏视频视频广告,渲染失败
override fun onRenderFail() {
LogUtil.e("$TAG 插屏全屏视频视频广告,渲染失败")
val map: MutableMap<String, Any?> = mutableMapOf(
"adType" to "interactAd",
"onAdMethod" to "onFail",
"code" to 0,
"message" to "插屏全屏视频视频广告渲染失败"
)
UnionAdSsgfEventPlugin.sendContent(map)
unifiedInterstitialAD?.close()
unifiedInterstitialAD?.destroy()
unifiedInterstitialAD = null
}
}
private var adRewardListener = object : ADRewardListener {
override fun onReward(p0: MutableMap<String, Any>?) {
LogUtil.e("$TAG 激励奖励 $p0")
val map: MutableMap<String, Any?> = mutableMapOf(
"adType" to "interactAd",
"onAdMethod" to "onVerify",
"transId" to p0!!["transId"]
)
UnionAdSsgfEventPlugin.sendContent(map)
}
}
}

View File

@@ -1,109 +0,0 @@
package com.example.union_ad_ssgf.load
import android.app.Activity
import android.content.Intent
import android.util.Log
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.example.union_ad_ssgf.config.UnionADConfig
import com.example.union_ad_ssgf.event.AdEventAction
import com.example.union_ad_ssgf.page.BaseAdPage
import com.qq.e.ads.nativ.ADSize
import com.qq.e.ads.nativ.NativeExpressAD
import com.qq.e.ads.nativ.NativeExpressADView
import com.qq.e.comm.util.AdError
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.util.Locale
/** 信息流加载对象 */
class FeedAdLoad : BaseAdPage(), NativeExpressAD.NativeExpressADListener {
private val TAG = FeedAdLoad::class.java.getSimpleName()
private var result: MethodChannel.Result? = null
/**
* 加载信息流广告列表
* @param call
* @param result
*/
fun loadFeedAdList(activity: Activity?, call: MethodCall, result: MethodChannel.Result) {
this.result = result
showAd(activity, null, call)
}
override fun onNoAD(error: AdError) {
val msg =
String.format(
Locale.getDefault(),
"onError, error code: %d, error msg: %s",
error.getErrorCode(),
error.getErrorMsg()
)
Log.i(TAG, "onError, adError=$msg")
sendErrorEvent(error.getErrorCode(), error.getErrorMsg())
this.result!!.success(ArrayList<Int>())
}
override fun onADLoaded(list: MutableList<NativeExpressADView>) {
Log.i(TAG, "onADLoaded")
val adResultList: MutableList<Int> = ArrayList()
if (list.isEmpty()) {
result!!.success(adResultList)
return
}
for (adItem in list) {
val key = adItem.hashCode()
adResultList.add(key)
FeedAdManager.getInstance()?.putAd(key, adItem)
}
// 添加广告事件
sendEvent(AdEventAction.onAdLoaded)
result!!.success(adResultList)
}
override fun onRenderFail(nativeExpressADView: NativeExpressADView) {
Log.i(TAG, "onRenderFail")
sendErrorEvent(-100, "onRenderFail")
sendBroadcastEvent(nativeExpressADView, AdEventAction.onAdError)
}
private fun sendBroadcastEvent(adView: NativeExpressADView, event: String) {
val intent = Intent()
intent.setAction(UnionADConfig.KEY_FEED_VIEW + "_" + adView.hashCode())
intent.putExtra("event", event)
LocalBroadcastManager.getInstance(activity!!).sendBroadcast(intent)
}
override fun onRenderSuccess(nativeExpressADView: NativeExpressADView) {
Log.i(TAG, "onRenderSuccess")
sendEvent(AdEventAction.onAdPresent)
sendBroadcastEvent(nativeExpressADView, AdEventAction.onAdPresent)
}
override fun onADExposure(nativeExpressADView: NativeExpressADView?) {
Log.i(TAG, "onADExposure")
sendEvent(AdEventAction.onAdExposure)
}
override fun onADClicked(nativeExpressADView: NativeExpressADView?) {
Log.i(TAG, "onADClicked")
sendEvent(AdEventAction.onAdClicked)
}
override fun onADClosed(nativeExpressADView: NativeExpressADView) {
Log.i(TAG, "onADClosed")
sendEvent(AdEventAction.onAdClosed)
sendBroadcastEvent(nativeExpressADView, AdEventAction.onAdClosed)
}
override fun onADLeftApplication(p0: NativeExpressADView?) {}
override fun loadAd(call: MethodCall?) {
// 获取请求模板广告素材的尺寸
val width: Int = call!!.argument("width")!!
val height: Int = call.argument("height")!!
val count: Int = call.argument("count")!!
val ad = NativeExpressAD(activity, ADSize(width, height), posId, this)
ad.loadAD(count)
}
}

View File

@@ -1,57 +0,0 @@
package com.example.union_ad_ssgf.load
import com.qq.e.ads.nativ.NativeExpressADView
/**
* 信息流广告管理
*/
class FeedAdManager {
private val TAG = FeedAdManager::class.java.getSimpleName()
companion object {
// 信息流广告管理类对象
private var _instance: FeedAdManager? = null
@Synchronized
fun getInstance(): FeedAdManager? {
if (_instance == null) {
synchronized(FeedAdManager::class.java) { _instance = FeedAdManager() }
}
return _instance
}
}
// 信息流广告列表
private val feedAdList: MutableMap<Int, NativeExpressADView> = HashMap()
/**
* 添加广告渲染对象
*
* @param key 广告缓存id
* @param ad 广告渲染对象
*/
fun putAd(key: Int, ad: NativeExpressADView) {
feedAdList[key] = ad
}
/**
* 获取广告渲染对象
*
* @param key 广告缓存 id
* @return 广告渲染对象
*/
fun getAd(key: Int): NativeExpressADView? {
return feedAdList[key]
}
/**
* 删除广告渲染对象
*
* @param key 广告缓存id
* @return 广告渲染对象
*/
fun removeAd(key: Int): NativeExpressADView? {
return feedAdList.remove(key)
}
}

View File

@@ -1,97 +0,0 @@
package com.example.union_ad_ssgf.page
import android.content.Context
import android.view.View
import android.widget.FrameLayout
import com.qq.e.ads.banner2.UnifiedBannerADListener
import com.qq.e.ads.banner2.UnifiedBannerView
import com.qq.e.comm.util.AdError
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.platform.PlatformView
import java.util.Locale
import android.util.Log;
import com.example.union_ad_ssgf.PluginDelegate
import com.example.union_ad_ssgf.event.AdEventAction
class AdBannerView(
context: Context,
private var id: Int,
creationParams: Map<String, Any>?,
private var pluginDelegate: PluginDelegate?
) : BaseAdPage(), PlatformView, UnifiedBannerADListener {
private val TAG = AdBannerView::class.java.getSimpleName()
private var frameLayout = FrameLayout(context)
private var bv: UnifiedBannerView? = null
private var params = creationParams
init {
val call = MethodCall("AdBannerView", creationParams)
showAd(this.pluginDelegate!!.activity, id, call);
}
override fun loadAd(call: MethodCall?) {
// 获取轮播时间间隔参数
val interval = params!!["interval"] as Int
// 加载广告 Banner
bv = UnifiedBannerView(activity, posId, this)
frameLayout!!.addView(bv)
// 设置轮播时间间隔
bv!!.setRefresh(interval)
bv!!.loadAD()
}
override fun getView(): View? {
return frameLayout;
}
override fun dispose() {
disposeAd();
}
override fun onNoAD(error: AdError) {
val msg = java.lang.String.format(
Locale.getDefault(), "onNoAD, error code: %d, error msg: %s",
error.getErrorCode(), error.getErrorMsg()
)
Log.e(TAG, msg)
sendErrorEvent(error.getErrorCode(), error.getErrorMsg())
disposeAd()
}
override fun onADReceive() {
Log.i(TAG, "onADReceive");
sendEvent(AdEventAction.onAdLoaded);
}
override fun onADExposure() {
Log.i(TAG, "onADExposure");
sendEvent(AdEventAction.onAdExposure);
}
override fun onADClosed() {
Log.i(TAG, "onADClosed");
sendEvent(AdEventAction.onAdClosed);
disposeAd();
}
override fun onADClicked() {
Log.i(TAG, "onADClicked");
sendEvent(AdEventAction.onAdClicked);
}
override fun onADLeftApplication() {
Log.i(TAG, "onADLeftApplication");
}
/**
* 销毁广告
*/
private fun disposeAd() {
frameLayout!!.removeAllViews()
if (bv != null) {
bv!!.destroy()
}
}
}

View File

@@ -1,91 +0,0 @@
package com.example.union_ad_ssgf.page
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.view.View
import android.widget.FrameLayout
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.example.union_ad_ssgf.PluginDelegate
import com.example.union_ad_ssgf.config.UnionADConfig
import com.example.union_ad_ssgf.event.AdEventAction
import com.example.union_ad_ssgf.load.FeedAdManager
import com.qq.e.ads.nativ.NativeExpressADView
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.platform.PlatformView
class AdFeedView(
context: Context,
private var id: Int,
private var creationParams: Map<String, Any>?,
private var pluginDelegate: PluginDelegate?
) : BaseAdPage(), PlatformView {
private var frameLayout = FrameLayout(context)
private var fad: NativeExpressADView? = null
private var receiver: BroadcastReceiver? = null
init {
val call = MethodCall("AdFeedView", creationParams)
showAd(this.pluginDelegate!!.activity, id, call)
}
override fun loadAd(call: MethodCall?) {
val key = adId!!.toInt()
regReceiver(key)
fad = FeedAdManager.getInstance()!!.getAd(key)
if (fad != null) {
if (frameLayout.childCount > 0) {
frameLayout.removeAllViews()
}
fad!!.render()
frameLayout.addView(fad)
}
}
override fun getView(): View? {
return frameLayout
}
override fun dispose() {
removeAd()
}
/**
* 注册广播
* @param key key
*/
private fun regReceiver(key: Int) {
// 注册广播
receiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val event = intent.getStringExtra("event")
sendEvent(event)
if (AdEventAction.onAdClosed == event || AdEventAction.onAdError == event) {
this@AdFeedView.disposeAd()
}
}
}
val intentFilter = IntentFilter(UnionADConfig.KEY_FEED_VIEW + "_" + key)
LocalBroadcastManager.getInstance(activity!!).registerReceiver(receiver!!, intentFilter)
}
/** 移除广告 */
private fun removeAd() {
frameLayout!!.removeAllViews()
// 注销广播
if (receiver != null) {
LocalBroadcastManager.getInstance(activity!!).unregisterReceiver(receiver!!)
}
}
/** 销毁广告 */
private fun disposeAd() {
removeAd()
FeedAdManager.getInstance()!!.removeAd(adId!!.toInt())
if (fad != null) {
fad!!.destroy()
}
}
}

View File

@@ -1,187 +0,0 @@
package com.example.union_ad_ssgf.page
import android.annotation.SuppressLint
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatImageView
import com.example.union_ad_ssgf.R
import com.example.union_ad_ssgf.config.UnionADConfig
import com.example.union_ad_ssgf.event.AdErrorEvent
import com.example.union_ad_ssgf.event.AdEvent
import com.example.union_ad_ssgf.event.AdEventAction
import com.example.union_ad_ssgf.event.AdEventHandler
import com.qq.e.ads.splash.SplashAD
import com.qq.e.ads.splash.SplashADListener
import com.qq.e.comm.util.AdError
/**
* 开屏广告
*/
class AdSplashActivity() : AppCompatActivity(), SplashADListener {
private val TAG = AdSplashActivity::class.java.getSimpleName()
/**
* 广告容器
*/
private var ad_container: FrameLayout? = null
/**
* 自定义品牌 logo
*/
private var ad_logo: AppCompatImageView? = null
/**
* 广告位 id
*/
private var posId: String? = null
/**
* 是否全屏
*/
private var isFullScreen = false
/**
* 开屏广告
*/
private var splashAD: SplashAD? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ad_splash);
initView();
initData();
}
/**
* 初始化View
*/
private fun initView() {
ad_container = findViewById<FrameLayout>(R.id.splash_ad_container)
ad_logo = findViewById<AppCompatImageView>(R.id.splash_ad_logo)
}
/**
* 初始化数据
*/
private fun initData() {
// 获取参数
posId = intent.getStringExtra(UnionADConfig.KEY_POSID)
val logo = intent.getStringExtra(UnionADConfig.KEY_LOGO)
val fetchDelay = intent.getDoubleExtra(UnionADConfig.KEY_FETCH_DELAY, 0.0)
val absFetchDelay = (fetchDelay * 1000).toInt()
isFullScreen = TextUtils.isEmpty(logo)
// 创建开屏广告
splashAD = SplashAD(this, posId, this, absFetchDelay)
if (isFullScreen) {
// logo 为空则加载全屏广告
ad_logo!!.setVisibility(View.GONE)
splashAD!!.fetchFullScreenAdOnly()
} else {
// 加载 logo
val resId: Int = getMipmapId(logo!!)
if (resId > 0) {
ad_logo!!.setVisibility(View.VISIBLE)
ad_logo!!.setImageResource(resId)
}
// 加载广告
splashAD!!.fetchAdOnly()
}
}
/**
* 广告关闭时调用,可能是用户关闭或者展示时间到。此时一般需要跳过开屏的 Activity进入应用内容页面
*/
override fun onADDismissed() {
Log.d(TAG, "onADDismissed")
finishPage()
AdEventHandler.getInstance()?.sendEvent(AdEvent(posId, "", 1, AdEventAction.onAdClosed));
}
/**
* 广告加载或展示过程中出错AdError中包含了错误码和错误描述具体错误码内容可参考错误码部分
*/
override fun onNoAD(adError: AdError) {
Log.d(TAG, "onNoAD adError:" + adError.errorMsg);
finishPage()
AdEventHandler.getInstance()!!
.sendEvent(AdErrorEvent(posId, "", 1, adError.errorCode, adError.errorMsg));
}
/**
* 广告成功展示时调用,成功展示不等于有效展示(比如广告容器高度不够)
*/
override fun onADPresent() {
Log.d(TAG, "onADPresent")
AdEventHandler.getInstance()!!.sendEvent(AdEvent(posId, "", 1, AdEventAction.onAdPresent))
}
/**
* 广告被点击时调用,不代表满足计费条件(如点击时网络异常)
*/
override fun onADClicked() {
Log.d(TAG, "onADClicked")
AdEventHandler.getInstance()!!.sendEvent(AdEvent(posId, "", 1, AdEventAction.onAdClicked))
}
/**
* 倒计时回调,返回广告还将被展示的剩余时间,单位是 ms
*/
override fun onADTick(millisUntilFinished: Long) {
Log.d(TAG, "onADTick millisUntilFinished$millisUntilFinished");
}
/**
* 广告曝光时调用
*/
override fun onADExposure() {
Log.d(TAG, "onADExposure")
AdEventHandler.getInstance()!!.sendEvent(AdEvent(posId, "", 1, AdEventAction.onAdExposure))
}
/**
* 广告加载成功的回调在fetchAdOnly的情况下表示广告拉取成功可以显示了。
* 广告需要在SystemClock.elapsedRealtime <expireTimestamp前展示否则在showAd时会返回广告超时错误。
*/
override fun onADLoaded(expireTimestamp: Long) {
Log.d(TAG, "onADLoaded expireTimestamp$expireTimestamp")
AdEventHandler.getInstance()!!.sendEvent(AdEvent(posId, "", 1, AdEventAction.onAdLoaded))
if (isFullScreen) {
splashAD!!.showFullScreenAd(ad_container)
} else {
splashAD!!.showAd(ad_container)
}
}
/**
* 完成广告,退出开屏页面
*/
private fun finishPage() {
finish()
// 设置退出动画
overridePendingTransition(0, android.R.anim.fade_out)
}
/**
* 开屏页一定要禁止用户对返回按钮的控制否则将可能导致用户手动退出了App而广告无法正常曝光和计费
*/
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME) {
true
} else super.onKeyDown(keyCode, event)
}
/**
* 获取图片资源的id
* @param resName 资源名称,不带后缀
* @return 返回资源id
*/
@SuppressLint("DiscouragedApi")
private fun getMipmapId(resName: String): Int {
return getResources().getIdentifier(resName, "mipmap", packageName)
}
}

View File

@@ -1,69 +0,0 @@
package com.example.union_ad_ssgf.page
import android.app.Activity
import com.example.union_ad_ssgf.config.UnionADConfig
import com.example.union_ad_ssgf.event.AdErrorEvent
import com.example.union_ad_ssgf.event.AdEvent
import com.example.union_ad_ssgf.event.AdEventHandler
import io.flutter.plugin.common.MethodCall
abstract class BaseAdPage {
// 上下文
protected var activity: Activity? = null
// 广告位 id
protected var posId: String? = null
// 广告位 id
protected var adId: String? = null
// 广告定位ID
protected var viewId: Int? = null
/**
* 显示广告
*
* @param activity 上下文
* @param call 方法调用
*/
fun showAd(activity: Activity?, viewId: Int?, call: MethodCall) {
this.activity = activity
this.viewId = viewId
this.posId = call.argument<String>(UnionADConfig.KEY_POSID)
this.adId = call.argument<String>(UnionADConfig.KEY_ADID)
loadAd(call)
}
/**
* 加载广告
* @param call 方法调用
*/
abstract fun loadAd(call: MethodCall?)
/**
* 发送广告事件
* @param event 广告事件
*/
protected fun sendEvent(event: AdEvent?) {
AdEventHandler.getInstance()?.sendEvent(event)
}
/**
* 发送广告事件
*
* @param action 操作
*/
protected fun sendEvent(action: String?) {
sendEvent(AdEvent(posId, adId, viewId, action))
}
/**
* 发送错误事件
*
* @param errCode 错误码
* @param errMsg 错误事件
*/
protected fun sendErrorEvent(errCode: Int, errMsg: String?) {
sendEvent(AdErrorEvent(posId, adId, viewId, errCode, errMsg))
}
}

View File

@@ -1,29 +0,0 @@
package com.example.union_ad_ssgf.page
import android.content.Context
import com.example.union_ad_ssgf.PluginDelegate
import com.example.union_ad_ssgf.config.UnionADConfig
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
/**
* View 名字
* @param viewName: 插件代理类
*/
class NativeViewFactory(
private val viewName: String,
private val pluginDelegate: PluginDelegate
) :
PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
val creationParams = args as Map<String, Object>
return if (viewName == UnionADConfig.KEY_BANNER_VIEW) {
AdBannerView(context, viewId, creationParams, pluginDelegate)
} else {
AdFeedView(context, viewId, creationParams, pluginDelegate)
}
}
}

View File

@@ -1,138 +0,0 @@
package com.example.union_ad_ssgf.page
import android.util.Log
import com.example.union_ad_ssgf.event.AdEventAction
import com.example.union_ad_ssgf.event.AdRewardEvent
import com.qq.e.ads.rewardvideo.RewardVideoAD
import com.qq.e.ads.rewardvideo.RewardVideoADListener
import com.qq.e.ads.rewardvideo.ServerSideVerificationOptions
import com.qq.e.comm.util.AdError
import io.flutter.plugin.common.MethodCall
import java.util.Locale
/**
* 激励视频页面
*/
class RewardVideoPage() : BaseAdPage(), RewardVideoADListener {
private val TAG = RewardVideoPage::class.java.getSimpleName()
/**
* 激励广告对象
*/
private lateinit var rewardVideoAD: RewardVideoAD
/**
* 设置激励视频服务端验证的自定义信息
*/
private lateinit var customData: String
/**
* 设置服务端验证的用户信息
*/
private lateinit var userId: String
/**
* 加载激励视频广告加载成功则调用回调RewardVideoADListener.onADLoad()
* 加载失败则会调用RewardVideoADListener.onError(AdError error)
*/
override fun loadAd(call: MethodCall?) {
/**
* 获取对应参数
*/
val playMuted: Boolean = call!!.argument("playMuted")!!
customData = call.argument("customData")!!
userId = call.argument("userId")!!
// 1. 初始化激励视频广告
rewardVideoAD = RewardVideoAD(activity, posId, this, !playMuted)
// 2. 设置服务端验证信息
val options = ServerSideVerificationOptions.Builder()
.setCustomData(customData)
.setUserId(userId) // 设置服务端验证的用户信息
.build()
// 3. 服务端验证信息
rewardVideoAD.setServerSideVerificationOptions(options)
// 4. 加载激励视频广告
rewardVideoAD.loadAD()
}
/**
* 广告加载成功,可在此回调后进行广告展示
**/
override fun onADLoad() {
Log.i(TAG, "onADLoad")
rewardVideoAD.showAD()
sendEvent(AdEventAction.onAdLoaded)
}
/**
* 视频素材缓存成功,可在此回调后进行广告展示
*/
override fun onVideoCached() {
Log.i(TAG, "onVideoCached")
}
/**
* 激励视频广告页面展示
*/
override fun onADShow() {
Log.i(TAG, "onADShow")
sendEvent(AdEventAction.onAdPresent)
}
/**
* 激励视频广告曝光
*/
override fun onADExpose() {
Log.i(TAG, "onADExpose")
sendEvent(AdEventAction.onAdExposure)
}
/**
* 激励视频触发激励(观看视频大于一定时长或者视频播放完毕)
* @param map 若选择了服务端验证,可以通过 ServerSideVerificationOptions#TRANS_ID 键从 map 中获取此次交易的 id若未选择服务端验证则不需关注 map 参数。
*/
override fun onReward(map: MutableMap<String, Any>?) {
val transId = map!![ServerSideVerificationOptions.TRANS_ID] as String?
Log.i(TAG, "onReward $transId") // 获取服务端验证的唯一 ID
sendEvent(AdRewardEvent(posId,"",1,AdEventAction.onAdReward, transId!!, customData, userId))
}
/**
* 激励视频广告被点击
*/
override fun onADClick() {
Log.i(TAG, "onADClick")
sendEvent(AdEventAction.onAdClicked)
}
/**
* 激励视频广告被关闭
*/
override fun onVideoComplete() {
Log.i(TAG, "onVideoComplete")
}
/**
* 激励视频广告被关闭
*/
override fun onADClose() {
Log.i(TAG, "onADClose")
sendEvent(AdEventAction.onAdClosed)
}
/**
* 广告流程出错
*/
override fun onError(error: AdError) {
val msg = java.lang.String.format(
Locale.getDefault(),
"onError, error code: %d, error msg: %s",
error.errorCode, error.errorMsg,
)
Log.i(TAG, "onError, adError=$msg")
sendErrorEvent(error.errorCode, error.errorMsg)
}
}

View File

@@ -0,0 +1,182 @@
package com.example.union_ad_ssgf.rewardvideoad
import android.annotation.SuppressLint
import android.content.Context
import com.example.union_ad_ssgf.DownloadConfirmHelper
import com.example.union_ad_ssgf.LogUtil
import com.example.union_ad_ssgf.UnionAdSsgfEventPlugin
import com.qq.e.ads.rewardvideo.RewardVideoAD
import com.qq.e.ads.rewardvideo.RewardVideoADListener
import com.qq.e.ads.rewardvideo.ServerSideVerificationOptions
import com.qq.e.comm.pi.IBidding
import com.qq.e.comm.util.AdError
@SuppressLint("StaticFieldLeak")
object RewardVideoAd {
private val TAG = "RewardVideoAd"
private lateinit var context: Context
private var rewardVideoAD: RewardVideoAD? = null
private var codeId: String? = null
private var userID: String = ""
private var rewardName: String = ""
private var rewardAmount: Int = 0
private var customData: String = ""
private var downloadConfirm: Boolean = false
private var videoMuted: Boolean = false
//是否开启竞价
private var isBidding: Boolean = false
fun init(context: Context, params: Map<*, *>) {
this.context = context
this.codeId = params["androidId"] as String
this.userID = params["userID"] as String
this.rewardName = params["rewardName"] as String
this.rewardAmount = params["rewardAmount"] as Int
this.customData = params["customData"] as String
this.downloadConfirm = params["downloadConfirm"] as Boolean
this.isBidding = params["isBidding"] as Boolean
this.videoMuted = params["videoMuted"] as Boolean
loadRewardVideoAd()
}
private fun loadRewardVideoAd() {
rewardVideoAD = RewardVideoAD(context, codeId, rewardVideoADListener,videoMuted)
val options = ServerSideVerificationOptions.Builder()
.setUserId(userID)
.setCustomData(customData)
.build()
rewardVideoAD?.setServerSideVerificationOptions(options)
rewardVideoAD?.loadAD()
}
fun showAd(params: Map<*, *>) {
if (rewardVideoAD == null) {
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onUnReady")
UnionAdSsgfEventPlugin.sendContent(map)
return
}
//是否为竞价模式
if (isBidding) {
val isSuccess: Boolean = params["isSuccess"] as Boolean
//是否成功
if (isSuccess) {
rewardVideoAD?.sendWinNotification(
mutableMapOf(
//对应值为竞胜出价类型为Integer
IBidding.EXPECT_COST_PRICE to params["expectCostPrice"],
//对应值为最大竞败方出价类型为Integer
IBidding.HIGHEST_LOSS_PRICE to params["highestLossPrice"],
)
)
rewardVideoAD?.showAD()
} else {
rewardVideoAD?.sendLossNotification(
mutableMapOf(
//值为本次竞胜方出价单位类型为Integer。选填
IBidding.WIN_PRICE to params["winPrice"],
//值为优量汇广告竞败原因类型为Integer。必填
IBidding.LOSS_REASON to params["lossReason"],
//值为本次竞胜方渠道ID类型为Integer。必填。
IBidding.ADN_ID to params["adnId"],
)
)
}
} else {
rewardVideoAD?.showAD()
}
}
private var rewardVideoADListener = object : RewardVideoADListener {
override fun onADLoad() {
LogUtil.e("$TAG 激励广告加载成功")
if (downloadConfirm) {
rewardVideoAD?.setDownloadConfirmListener(DownloadConfirmHelper.DOWNLOAD_CONFIRM_LISTENER)
}
}
override fun onVideoCached() {
LogUtil.e("$TAG 激励广告视频素材缓存成功")
if (isBidding) {
val map: MutableMap<String, Any?> =
mutableMapOf(
"adType" to "rewardAd",
"onAdMethod" to "onECPM",
"ecpmLevel" to rewardVideoAD?.ecpmLevel,
"ecpm" to rewardVideoAD?.ecpm
)
UnionAdSsgfEventPlugin.sendContent(map)
} else {
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onReady")
UnionAdSsgfEventPlugin.sendContent(map)
}
}
override fun onADShow() {
LogUtil.e("$TAG 激励视频广告页面展示")
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onShow")
UnionAdSsgfEventPlugin.sendContent(map)
}
override fun onADExpose() {
LogUtil.e("$TAG 激励视频广告曝光")
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onExpose")
UnionAdSsgfEventPlugin.sendContent(map)
}
override fun onReward(p0: MutableMap<String, Any>?) {
LogUtil.e("$TAG 激励视频广告激励发放 $p0")
val map: MutableMap<String, Any?> = mutableMapOf(
"adType" to "rewardAd",
"onAdMethod" to "onVerify",
"transId" to p0!!["transId"],
"rewardName" to rewardName,
"rewardAmount" to rewardAmount
)
UnionAdSsgfEventPlugin.sendContent(map)
}
override fun onADClick() {
LogUtil.e("$TAG 激励视频广告被点击")
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onClick")
UnionAdSsgfEventPlugin.sendContent(map)
}
override fun onVideoComplete() {
LogUtil.e("$TAG 激励视频广告视频素材播放完毕")
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onFinish")
UnionAdSsgfEventPlugin.sendContent(map)
}
override fun onADClose() {
LogUtil.e("$TAG 激励视频广告被关闭")
val map: MutableMap<String, Any?> =
mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onClose")
UnionAdSsgfEventPlugin.sendContent(map)
rewardVideoAD = null
}
override fun onError(p0: AdError?) {
LogUtil.e("$TAG 广告流程出错 ${p0?.errorCode} ${p0?.errorMsg}")
val map: MutableMap<String, Any?> = mutableMapOf(
"adType" to "rewardAd",
"onAdMethod" to "onFail",
"code" to p0?.errorCode,
"message" to p0?.errorMsg
)
UnionAdSsgfEventPlugin.sendContent(map)
}
}
}

View File

@@ -0,0 +1,151 @@
package com.example.union_ad_ssgf.splashad
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import com.example.union_ad_ssgf.DownloadConfirmHelper
import com.example.union_ad_ssgf.UnionAdSsgfConfig
import com.example.union_ad_ssgf.LogUtil
import com.qq.e.ads.splash.SplashAD
import com.qq.e.ads.splash.SplashADListener
import com.qq.e.comm.pi.IBidding
import com.qq.e.comm.util.AdError
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView
internal class SplashAdView(
var activity: Activity,
messenger: BinaryMessenger,
id: Int,
params: Map<String?, Any?>
) :
PlatformView, SplashADListener , MethodChannel.MethodCallHandler {
private var mContainer: FrameLayout? = null
private var channel: MethodChannel?
private var splashAD: SplashAD? = null
//广告所需参数
private var codeId: String = params["androidId"] as String
private var fetchDelay: Int = params["fetchDelay"] as Int
private var downloadConfirm: Boolean = params["downloadConfirm"] as Boolean
//是否开启竞价
private var isBidding: Boolean = params["isBidding"] as Boolean
init {
mContainer = FrameLayout(activity)
mContainer?.layoutParams?.width = ViewGroup.LayoutParams.WRAP_CONTENT
mContainer?.layoutParams?.height = ViewGroup.LayoutParams.WRAP_CONTENT
channel = MethodChannel(messenger, UnionAdSsgfConfig.splashAdView + "_" + id)
channel?.setMethodCallHandler(this)
loadSplashAd()
}
private fun loadSplashAd() {
splashAD = SplashAD(activity, codeId, this, fetchDelay)
mContainer?.removeAllViews()
splashAD?.fetchAdOnly()
}
override fun getView(): View {
return mContainer!!
}
/*************开屏广告回调******************/
//广告关闭时调用,可能是用户关闭或者展示时间到。此时一般需要跳过开屏的 Activity进入应用内容页面
override fun onADDismissed() {
LogUtil.e("开屏广告关闭")
channel?.invokeMethod("onClose", "")
}
//广告加载失败error 对象包含了错误码和错误信息错误码的详细内容可以参考文档第5章
override fun onNoAD(p0: AdError?) {
LogUtil.e("开屏广告加载失败 ${p0?.errorCode} ${p0?.errorMsg}")
var map: MutableMap<String, Any?> = mutableMapOf("code" to p0?.errorCode, "message" to p0?.errorMsg)
channel?.invokeMethod("onFail", map)
}
//广告成功展示时调用,成功展示不等于有效展示(比如广告容器高度不够)
override fun onADPresent() {
LogUtil.e("开屏广告成功展示")
channel?.invokeMethod("onShow", "")
}
//广告被点击时调用,不代表满足计费条件(如点击时网络异常)
override fun onADClicked() {
LogUtil.e("开屏广告被点击")
channel?.invokeMethod("onClick", "")
}
//倒计时回调,返回广告还将被展示的剩余时间,单位是 ms
override fun onADTick(p0: Long) {
LogUtil.e("开屏广告倒计时回调 $p0")
channel?.invokeMethod("onADTick", p0)
}
//广告曝光时调用
override fun onADExposure() {
LogUtil.e("开屏广告曝光")
channel?.invokeMethod("onExpose", "")
}
//广告加载成功的回调在fetchAdOnly的情况下
// 表示广告拉取成功可以显示了。广告需要在SystemClock.elapsedRealtime <expireTimestamp前展示
// 否则在showAd时会返回广告超时错误。
override fun onADLoaded(p0: Long) {
LogUtil.e("开屏广告加载成功 $p0")
if(downloadConfirm){
splashAD?.setDownloadConfirmListener(DownloadConfirmHelper.DOWNLOAD_CONFIRM_LISTENER)
}
if(isBidding){
channel?.invokeMethod("onECPM", mutableMapOf(
"ecpmLevel" to splashAD?.ecpmLevel,
"ecpm" to splashAD?.ecpm
))
}else{
splashAD?.showAd(mContainer)
}
}
override fun dispose() {
mContainer?.removeAllViews()
mContainer = null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
//竞价成功
"biddingSucceeded" -> {
var arguments = call.arguments as Map<*, *>
splashAD?.sendWinNotification(mutableMapOf(
//对应值为竞胜出价类型为Integer
IBidding.EXPECT_COST_PRICE to arguments["expectCostPrice"],
//对应值为最大竞败方出价类型为Integer
IBidding.HIGHEST_LOSS_PRICE to arguments["highestLossPrice"],
))
//展示banner
splashAD?.showAd(mContainer)
}
//竞价失败
"biddingFail" -> {
var arguments = call.arguments as Map<*, *>
splashAD?.sendLossNotification(mutableMapOf(
//值为本次竞胜方出价单位类型为Integer。选填
IBidding.WIN_PRICE to arguments["winPrice"],
//值为优量汇广告竞败原因类型为Integer。必填
IBidding.LOSS_REASON to arguments["lossReason"],
//值为本次竞胜方渠道ID类型为Integer。必填。
IBidding.ADN_ID to arguments["adnId"],
))
}
}
}
}

View File

@@ -0,0 +1,18 @@
package com.example.union_ad_ssgf.splashad
import android.app.Activity
import android.content.Context
import com.example.union_ad_ssgf.bannerad.BannerAdView
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
class SplashAdViewFactory (private val messenger: BinaryMessenger, private val activity: Activity) : PlatformViewFactory(
StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
val params = args as Map<String?, Any?>
return SplashAdView(activity,messenger, viewId, params)
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="100%"
android:interpolator="@android:anim/accelerate_interpolator"
android:toXDelta="0">
</translate>
</set>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromYDelta="100%"
android:interpolator="@android:anim/accelerate_interpolator"
android:toYDelta="0">
</translate>
</set>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF3185FC"/>
<corners
android:bottomLeftRadius="22dp"
android:bottomRightRadius="22dp"
android:topLeftRadius="22dp"
android:topRightRadius="22dp"/>
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white"/>
<corners
android:bottomLeftRadius="8dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="8dp"
android:topRightRadius="0dp"/>
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white"/>
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="8dp"
android:topRightRadius="8dp"/>
</shape>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="25dp"
android:height="25dp"
android:viewportWidth="25"
android:viewportHeight="25">
<path
android:pathData="M24.1673,0.8327C24.753,1.4185 24.753,2.3683 24.1673,2.9541L14.6213,12.5L24.1673,22.0459C24.753,22.6317 24.753,23.5815 24.1673,24.1673C23.5815,24.753 22.6317,24.753 22.0459,24.1673L12.5,14.6213L2.9541,24.1673C2.3683,24.753 1.4185,24.753 0.8327,24.1673C0.2469,23.5815 0.247,22.6317 0.8327,22.0459L10.3787,12.5L0.8327,2.9541C0.2469,2.3683 0.247,1.4185 0.8327,0.8327C1.4185,0.247 2.3683,0.2469 2.9541,0.8327L12.5,10.3787L22.0459,0.8327C22.6317,0.247 23.5815,0.2469 24.1673,0.8327Z"
android:strokeWidth="1"
android:fillColor="#B3B3B3"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/download_confirm_root"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="20dp"
android:paddingTop="16dp"
android:paddingRight="20dp"
android:background="@drawable/download_confirm_background_portrait"
android:paddingBottom="15dp">
<!--顶部标题布局-->
<RelativeLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
tools:text="Demo-APP详情"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/download_confirm_close"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_alignParentEnd="true"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_download_confirm_close"
/>
</RelativeLayout>
<!---->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp">
<LinearLayout
android:id="@+id/download_confirm_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/download_confirm_holder"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</FrameLayout>
<Button
android:id="@+id/download_confirm_confirm"
android:layout_width="match_parent"
android:layout_height="44dp"
android:text="立即下载"
android:textSize="16sp"
android:textColor="@android:color/white"
android:background="@drawable/download_confirm_background_confirm"
android:textStyle="bold"
/>
</LinearLayout>
<ProgressBar
android:id="@+id/download_confirm_progress_bar"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:visibility="gone"
android:layout_gravity="center"/>
<Button
android:id="@+id/download_confirm_reload_button"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="重新加载"
android:visibility="gone"
android:layout_gravity="center"/>
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="DownloadConfirmDialogFullScreen" parent="android:Theme.Dialog">
<item name="android:windowFullscreen">true</item>
</style>
<style name="DownloadConfirmDialogAnimationUp">
<item name="android:windowEnterAnimation">@anim/download_confirm_dialog_slide_up</item>
</style>
<style name="DownloadConfirmDialogAnimationRight">
<item name="android:windowEnterAnimation">@anim/download_confirm_dialog_slide_right_in</item>
</style>
</resources>

View File

@@ -6,20 +6,17 @@
// For more information about Flutter integration tests, please see
// https://docs.flutter.dev/cookbook/testing/integration/introduction
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:union_ad_ssgf/flutter_union_ad.dart';
import 'package:union_ad_ssgf/union_ad_ssgf.dart';
// import 'package:union_ad_ssgf/f.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('getPlatformVersion test', (WidgetTester tester) async {
final UnionAdSsgf plugin = UnionAdSsgf();
final String? version = await plugin.getPlatformVersion();
// The version string depends on the host platform running the test, so
// just assert that some non-empty string is returned.
expect(version?.isNotEmpty, true);
final String version = await FlutterUnionAd.getSDKVersion();
expect(version.isNotEmpty, true);
});
}

View File

@@ -2,9 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:union_ad_ssgf/event/ad_event_handler.dart';
import 'package:union_ad_ssgf_example/ads_config.dart';
import 'package:union_ad_ssgf/union_ad_ssgf.dart';
// 结果信息
String _result = '';
@@ -170,24 +168,11 @@ class _HomePageState extends State<HomePage> {
setState(() {
_adEvent = '设置成功';
});
UnionAdSsgf.onEventListener((event) {
_adEvent = 'adId:${event.adId} action:${event.action}';
if (event is AdErrorEvent) {
// 错误事件
_adEvent += ' errCode:${event.errCode} errMsg:${event.errMsg}';
} else if (event is AdRewardEvent) {
// 激励事件
_adEvent +=
' transId:${event.transId} customData:${event.customData} userId:${event.userId}';
}
print('onEventListener:$_adEvent');
setState(() {});
});
}
/// 请求应用跟踪透明度授权
Future<void> requestIDFA() async {
bool result = await UnionAdSsgf.requestIDFA;
bool result = false;
_adEvent = '请求广告标识符:$result';
setState(() {});
}
@@ -195,7 +180,7 @@ class _HomePageState extends State<HomePage> {
/// 设置个性化广告
/// [state] 0:不限制 1:限制
Future<void> setPersonalizedAd(int state) async {
bool result = await UnionAdSsgf.setPersonalizedState(state);
bool result = false;
_adEvent = '设置个性化广告:$result';
setState(() {});
}
@@ -206,33 +191,13 @@ class _HomePageState extends State<HomePage> {
bool showFullScreenVideo = false,
bool showRewardVideo = false,
}) async {
// try {
// bool result = await UnionAdSsgf.showInterstitialAd(
// posId,
// showPopup: false,
// showFullScreenVideo: showFullScreenVideo,
// showRewardVideo: showRewardVideo,
// autoPlayMuted: false,
// autoPlayOnWifi: false,
// userId: 'userId',
// customData: 'showInterstitialAd customData',
// );
// _result = "展示插屏广告${result ? '成功' : '失败'}";
// } on PlatformException catch (e) {
// _result = "展示插屏广告失败 code:${e.code} msg:${e.message} details:${e.details}";
// }
setState(() {});
}
/// 展示激励视频广告
Future<void> showRewardVideoAd() async {
try {
bool result = await UnionAdSsgf.showRewardVideoAd(
AdsConfig.rewardVideoId,
playMuted: false,
customData: 'showRewardVideoAd customData',
userId: 'userId',
);
bool result = false;
_result = "展示激励视频广告${result ? '成功' : '失败'}";
} on PlatformException catch (e) {
_result =
@@ -246,11 +211,7 @@ class _HomePageState extends State<HomePage> {
/// [logo] 展示如果传递则展示logo不传递不展示
Future<void> showSplashAd([String? logo]) async {
try {
bool result = await UnionAdSsgf.showSplashAd(
AdsConfig.splashId,
logo: logo,
fetchDelay: 3,
);
bool result = false;
_result = "展示开屏广告${result ? '成功' : '失败'}";
} on PlatformException catch (e) {
_result = "展示开屏广告失败 code:${e.code} msg:${e.message} details:${e.details}";

View File

@@ -1,10 +1,6 @@
import 'package:flutter/material.dart';
import 'package:union_ad_ssgf/flutter_union_ad.dart';
import 'dart:async';
import 'package:union_ad_ssgf/union_ad_ssgf.dart';
import 'package:union_ad_ssgf/event/ad_error_event.dart';
import 'package:union_ad_ssgf/event/ad_reward_event.dart';
import 'package:union_ad_ssgf_example/ads_config.dart';
import 'package:union_ad_ssgf_example/home_page.dart';
void main() {
@@ -30,31 +26,27 @@ class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
return const MaterialApp(
home: HomePage(),
);
}
/// 初始化广告 SDK
Future<bool> init() async {
bool result = await UnionAdSsgf.initAd(AdsConfig.appId);
bool result = await FlutterUnionAd.register(
androidId: "1200310001",
// androidId
iosId: "1202937154",
// iosId
debug: true,
// 是否显示日志log
personalized: FlutterTencentadPersonalized.show,
// 是否显示个性化推荐广告
channelId: FlutterTencentadChannel.tencent, //渠道id
);
print("------>>>> $result");
print("----->> result: $result");
debugPrint("广告SDK 初始化${result ? '成功' : '失败'}");
return result;
}
/// 设置广告监听
Future<void> setAdEvent() async {
UnionAdSsgf.onEventListener((event) {
debugPrint('adId:${event.adId} action:${event.action}');
if (event is AdErrorEvent) {
// 错误事件
debugPrint(' errCode:${event.errCode} errMsg:${event.errMsg}');
} else if (event is AdRewardEvent) {
// 激励事件
debugPrint(
' transId:${event.transId} userId:${event.userId} customData:${event.customData}');
}
});
}
}

View File

@@ -1,7 +1,7 @@
import Cocoa
import FlutterMacOS
@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true

View File

@@ -0,0 +1,150 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:union_ad_ssgf/flutter_union_ad.dart';
class BannerAdView extends StatefulWidget {
final String androidId;
final String iosId;
final double viewWidth;
final double viewHeight;
final FlutterUnionAdBannerCallBack? callBack;
final bool downloadConfirm;
final bool isBidding;
final FlutterUnioAdBiddingController? bidding;
const BannerAdView({
Key? key,
required this.androidId,
required this.iosId,
required this.viewWidth,
required this.viewHeight,
this.callBack,
required this.downloadConfirm,
required this.isBidding,
this.bidding,
}) : super(key: key);
@override
State<BannerAdView> createState() => _BannerAdViewState();
}
class _BannerAdViewState extends State<BannerAdView> {
final String _viewType = "com.example.union_ad_ssgf/BannerAdView";
MethodChannel? _channel;
//广告是否显示
bool _isShowAd = true;
double _width = 0;
double _height = 0;
@override
void initState() {
super.initState();
_width = widget.viewWidth;
_height = widget.viewHeight;
}
@override
Widget build(BuildContext context) {
if (!_isShowAd) {
return Container();
}
if (defaultTargetPlatform == TargetPlatform.android) {
return SizedBox(
width: _width,
height: _height,
child: AndroidView(
viewType: _viewType,
creationParams: {
"androidId": widget.androidId,
"viewWidth": widget.viewWidth,
"viewHeight": widget.viewHeight,
"downloadConfirm": widget.downloadConfirm,
"isBidding": widget.isBidding,
},
onPlatformViewCreated: _registerChannel,
creationParamsCodec: const StandardMessageCodec(),
),
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return SizedBox(
width: _width,
height: _height,
child: UiKitView(
viewType: _viewType,
creationParams: {
"iosId": widget.iosId,
"viewWidth": widget.viewWidth,
"viewHeight": widget.viewHeight,
"isBidding": widget.isBidding,
},
onPlatformViewCreated: _registerChannel,
creationParamsCodec: const StandardMessageCodec(),
),
);
} else {
return Container();
}
}
//注册cannel
void _registerChannel(int id) {
_channel = MethodChannel("${_viewType}_$id");
_channel?.setMethodCallHandler(_platformCallHandler);
widget.bidding?.init(_channel);
}
//监听原生view传值
Future<dynamic> _platformCallHandler(MethodCall call) async {
print("${call.method} ==== ${call.arguments}");
switch (call.method) {
//显示广告
case FlutterTencentadMethod.onShow:
Map map = call.arguments;
if (mounted) {
setState(() {
_width = map["width"];
_height = map["height"];
_isShowAd = true;
});
}
widget.callBack?.onShow!();
break;
//广告加载失败
case FlutterTencentadMethod.onFail:
if (mounted) {
setState(() {
_isShowAd = false;
});
}
Map map = call.arguments;
widget.callBack?.onFail!(map["code"], map["message"]);
break;
//点击
case FlutterTencentadMethod.onClick:
widget.callBack?.onClick!();
break;
//曝光
case FlutterTencentadMethod.onExpose:
widget.callBack?.onExpose!();
break;
//关闭
case FlutterTencentadMethod.onClose:
if (mounted) {
setState(() {
_isShowAd = false;
});
}
widget.callBack?.onClose!();
break;
//竞价
case FlutterTencentadMethod.onECPM:
Map map = call.arguments;
widget.callBack?.onECPM!(map["ecpmLevel"], map["ecpm"]);
break;
}
}
}

View File

@@ -1,24 +0,0 @@
import 'ad_event.dart';
/// 广告错误事件
class AdErrorEvent extends AdEvent {
AdErrorEvent(
{required this.errCode,
required this.errMsg,
required String adId,
required String action})
: super(adId: adId, action: action);
// 错误码
final int errCode;
// 错误信息
final String errMsg;
// 解析 json 为错误事件对象
factory AdErrorEvent.fromJson(Map<dynamic, dynamic> json) {
return AdErrorEvent(
errCode: json['errCode'],
errMsg: json['errMsg'],
adId: json['adId'],
action: json['action'],
);
}
}

View File

@@ -1,30 +0,0 @@
import 'ad_error_event.dart';
import 'ad_event_action.dart';
import 'ad_reward_event.dart';
export 'ad_error_event.dart';
export 'ad_event_action.dart';
export 'ad_reward_event.dart';
/// 广告事件
class AdEvent {
AdEvent({required this.adId, required this.action});
// 广告 id
final String adId;
// 操作
final String action;
/// 解析 AdEvent
factory AdEvent.fromJson(Map<dynamic, dynamic> json) {
String action = json['action'];
if (action == AdEventAction.onAdError) {
return AdErrorEvent.fromJson(json);
} else if (action == AdEventAction.onAdReward) {
return AdRewardEvent.fromJson(json);
} else {
return AdEvent(
adId: json['adId'],
action: action,
);
}
}
}

View File

@@ -1,21 +0,0 @@
/// 广告事件操作
class AdEventAction {
// 广告错误
static final String onAdError = "onAdError";
// 广告加载成功
static final String onAdLoaded = "onAdLoaded";
// 广告填充
static final String onAdPresent = "onAdPresent";
// 广告曝光
static final String onAdExposure = "onAdExposure";
// 广告关闭(开屏计时结束或者用户点击关闭)
static final String onAdClosed = "onAdClosed";
// 广告点击
static final String onAdClicked = "onAdClicked";
// 广告跳过
static final String onAdSkip = "onAdSkip";
// 广告播放或计时完毕
static final String onAdComplete = "onAdComplete";
// 获得广告激励
static final String onAdReward = "onAdReward";
}

View File

@@ -1,13 +0,0 @@
import 'ad_event.dart';
export 'ad_event.dart';
/// 广告事件回调监听
typedef OnAdEventListener = void Function(AdEvent event);
/// 处理广告事件
void hanleAdEvent(dynamic data, OnAdEventListener listener) {
if (data != null) {
AdEvent adEvent = AdEvent.fromJson(data);
listener.call(adEvent);
}
}

View File

@@ -1,28 +0,0 @@
import 'ad_event.dart';
/// 广告激励事件
class AdRewardEvent extends AdEvent {
AdRewardEvent(
{required this.transId,
this.customData,
this.userId,
required String adId,
required String action})
: super(adId: adId, action: action);
// 激励服务端验证的唯一 ID
final String transId;
// 服务端验证的自定义信息
final String? customData;
// 服务端验证的用户信息
final String? userId;
// 解析 json 为激励事件对象
factory AdRewardEvent.fromJson(Map<dynamic, dynamic> json) {
return AdRewardEvent(
adId: json['adId'],
action: json['action'],
transId: json['transId'],
customData: json['customData'],
userId: json['userId'],
);
}
}

View File

@@ -0,0 +1,150 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:union_ad_ssgf/flutter_union_ad.dart';
class ExpressAdView extends StatefulWidget {
final String androidId;
final String iosId;
final int viewWidth;
final int viewHeight;
final FlutterUnionAdExpressCallBack? callBack;
final bool downloadConfirm;
final bool isBidding;
final FlutterUnioAdBiddingController? bidding;
const ExpressAdView({
Key? key,
required this.androidId,
required this.iosId,
required this.viewWidth,
required this.viewHeight,
this.callBack,
required this.downloadConfirm,
required this.isBidding,
this.bidding,
}) : super(key: key);
@override
State<ExpressAdView> createState() => _ExpressAdViewState();
}
class _ExpressAdViewState extends State<ExpressAdView> {
final String _viewType = "com.example.union_ad_ssgf/NativeExpressAdView";
MethodChannel? _channel;
//广告是否显示
bool _isShowAd = true;
double _width = 0;
double _height = 0;
@override
void initState() {
super.initState();
_width = widget.viewWidth.toDouble();
_height = widget.viewHeight.toDouble();
setState(() {});
}
@override
Widget build(BuildContext context) {
if (!_isShowAd) {
return Container();
}
if (defaultTargetPlatform == TargetPlatform.android) {
return SizedBox(
width: _width,
height: _height,
child: AndroidView(
viewType: _viewType,
creationParams: {
"androidId": widget.androidId,
"viewWidth": widget.viewWidth,
"viewHeight": widget.viewHeight,
"downloadConfirm": widget.downloadConfirm,
"isBidding": widget.isBidding,
},
onPlatformViewCreated: _registerChannel,
creationParamsCodec: const StandardMessageCodec(),
),
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return SizedBox(
width: _width,
height: _height,
child: UiKitView(
viewType: _viewType,
creationParams: {
"iosId": widget.iosId,
"viewWidth": widget.viewWidth,
"viewHeight": widget.viewHeight,
"isBidding": widget.isBidding,
},
onPlatformViewCreated: _registerChannel,
creationParamsCodec: const StandardMessageCodec(),
),
);
} else {
return Container();
}
}
//注册cannel
void _registerChannel(int id) {
_channel = MethodChannel("${_viewType}_$id");
_channel?.setMethodCallHandler(_platformCallHandler);
widget.bidding?.init(_channel);
}
//监听原生view传值
Future<dynamic> _platformCallHandler(MethodCall call) async {
switch (call.method) {
//显示广告
case FlutterTencentadMethod.onShow:
Map map = call.arguments;
if (mounted) {
setState(() {
_width = map["width"];
_height = map["height"];
_isShowAd = true;
});
}
widget.callBack?.onShow!();
break;
//广告加载失败
case FlutterTencentadMethod.onFail:
if (mounted) {
setState(() {
_isShowAd = false;
});
}
Map map = call.arguments;
widget.callBack?.onFail!(map["code"], map["message"]);
break;
//点击
case FlutterTencentadMethod.onClick:
widget.callBack?.onClick!();
break;
//曝光
case FlutterTencentadMethod.onExpose:
widget.callBack?.onExpose!();
break;
//关闭
case FlutterTencentadMethod.onClose:
if (mounted) {
setState(() {
_isShowAd = false;
});
}
widget.callBack?.onClose!();
break;
//竞价
case FlutterTencentadMethod.onECPM:
Map map = call.arguments;
widget.callBack?.onECPM!(map["ecpmLevel"], map["ecpm"]);
break;
}
}
}

View File

@@ -0,0 +1,87 @@
part of 'flutter_union_ad.dart';
/// @Description: 优量汇竞价
class FlutterUnioAdBiddingController {
late MethodChannel? _methodChannel;
init(MethodChannel? method) {
_methodChannel = method;
}
//回传竞价结果
void biddingResult(FlutterTencentBiddingResult result) {
//竞价成功
if (result.isSuccess) {
_methodChannel?.invokeMethod('biddingSucceeded', {
'expectCostPrice': result.expectCostPrice,
'highestLossPrice': result.highestLossPrice,
});
//竞价失败
} else {
_methodChannel?.invokeMethod('biddingSucceeded', {
'winPrice': result.winPrice,
'lossReason': result.lossReason,
'adnId': result.adnId,
});
}
}
}
class FlutterTencentBiddingResult {
int? expectCostPrice;
int? highestLossPrice;
int? winPrice;
int? lossReason;
String? adnId;
bool isSuccess = true;
FlutterTencentBiddingResult();
///竞价成功
///
///[expectCostPrice] 竞胜出价类型为Integer
///
///[highestLossPrice] 最大竞败方出价类型为Integer
FlutterTencentBiddingResult success(
int expectCostPrice, int highestLossPrice) {
this.isSuccess = true;
this.expectCostPrice = expectCostPrice;
this.highestLossPrice = highestLossPrice;
return this;
}
///竞价失败
///
/// [winPrice] 本次竞胜方出价单位类型为Integer。选填
///
/// [lossReason] 优量汇广告竞败原因类型为Integer。必填 [FlutterTencentAdBiddingLossReason]
///
/// [adnId] 本次竞胜方渠道ID类型为Integer。必填。 [FlutterTencentAdADNID]
FlutterTencentBiddingResult fail(int winPrice, int lossReason, String adnId) {
this.isSuccess = false;
this.winPrice = winPrice;
this.lossReason = lossReason;
this.adnId = adnId;
return this;
}
FlutterTencentBiddingResult.fromJson(Map<String, dynamic> json) {
expectCostPrice = json['expectCostPrice'];
highestLossPrice = json['highestLossPrice'];
winPrice = json['winPrice'];
lossReason = json['lossReason'];
adnId = json['adnId'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['isSuccess'] = this.isSuccess;
data['expectCostPrice'] = this.expectCostPrice;
data['highestLossPrice'] = this.highestLossPrice;
data['winPrice'] = this.winPrice;
data['lossReason'] = this.lossReason;
data['adnId'] = this.adnId;
return data;
}
}

275
lib/flutter_union_ad.dart Normal file
View File

@@ -0,0 +1,275 @@
export 'flutter_union_ad_stream.dart';
export 'flutter_union_ad_code.dart.dart';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:union_ad_ssgf/banner/banner_ad_view.dart';
import 'package:union_ad_ssgf/splash/splash_ad_view.dart';
import 'flutter_union_ad_code.dart.dart';
import 'express/express_ad_view.dart';
part 'flutter_union_ad_callback.dart';
part 'flutter_unio_ad_bidding_controller.dart';
class FlutterUnionAd {
static const MethodChannel _channel = MethodChannel('flutter_union_ad');
///
/// # SDK注册初始化
///
/// [androidId] androidId 必填
///
/// [iosId] iosId 必填
///
/// [channelId] channelId 渠道id [FlutterTencentadChannel]
///
/// [personalized] personalized 是否开启个性化广告 [FlutterTencentadPersonalized]
///
static Future<bool> register({
required String androidId,
required String iosId,
int? personalized,
bool? debug,
int? channelId,
}) async {
return await _channel.invokeMethod("register", {
"androidId": androidId,
"iosId": iosId,
"debug": debug ?? false,
"channelId": channelId ?? FlutterTencentadChannel.other,
"personalized": personalized ?? FlutterTencentadPersonalized.show,
});
}
///
/// # 获取SDK版本号
///
static Future<String> getSDKVersion() async {
return await _channel.invokeMethod("getSDKVersion");
}
///
/// # 激励视频广告预加载
///
/// [androidId] android广告ID
///
/// [iosId] ios广告ID
///
/// [rewardName] 奖励名称
///
/// [rewardAmount] 奖励金额
///
/// [userID] 用户id
///
/// [customData] 扩展参数,服务器回调使用
///
/// [downloadConfirm] 下载二次确认弹窗 默认false
///
/// [videoMuted] 是否静音 默认false
///
/// [isBidding] 是否开启竞价模式 默认false
static Future<bool> loadRewardVideoAd({
required String androidId,
required String iosId,
required String rewardName,
required int rewardAmount,
required String userID,
String? customData,
bool? downloadConfirm,
bool? videoMuted,
bool? isBidding,
}) async {
return await _channel.invokeMethod("loadRewardVideoAd", {
"androidId": androidId,
"iosId": iosId,
"rewardName": rewardName,
"rewardAmount": rewardAmount,
"userID": userID,
"customData": customData ?? "",
"videoMuted": videoMuted ?? false,
"downloadConfirm": downloadConfirm ?? false,
"isBidding": isBidding ?? false,
});
}
///
/// # 显示激励广告
///
/// [result] 竞价成功、失败后调用 [FlutterTencentBiddingResult] ,isBidding = true时必传
static Future<bool> showRewardVideoAd(
{FlutterTencentBiddingResult? result}) async {
return await _channel.invokeMethod(
"showRewardVideoAd", result?.toJson() ?? {});
}
///
/// # 预加载插屏广告
///
/// [androidId] android广告ID
///
/// [iosId] ios广告ID
///
/// [isFullScreen] 是否全屏
///
/// [downloadConfirm] 下载二次确认弹窗 默认false
///
/// [isBidding] 是否开启竞价模式 默认false
///
static Future<bool> loadUnifiedInterstitialAD({
required String androidId,
required String iosId,
required bool isFullScreen,
bool? downloadConfirm,
bool? isBidding,
}) async {
return await _channel.invokeMethod("loadInterstitialAD", {
"androidId": androidId,
"iosId": iosId,
"isFullScreen": isFullScreen,
"downloadConfirm": downloadConfirm ?? false,
"isBidding": isBidding ?? false,
});
}
///
/// # 显示新模板渲染插屏
///
///
/// [result] 竞价成功、失败后调用 [FlutterTencentBiddingResult] ,isBidding = true时必传
///
static Future<bool> showUnifiedInterstitialAD(
{FlutterTencentBiddingResult? result}) async {
return await _channel.invokeMethod(
"showInterstitialAD", result?.toJson() ?? {});
}
///
/// # banner广告
///
/// [androidId] android广告ID
///
/// [iosId] ios广告ID
///
/// [viewWidth] 广告宽 单位dp
///
/// [viewHeight] 广告高 单位dp 宽高比应该为6.4:1
///
/// [FlutterTencentAdBannerCallBack] 广告回调
///
/// [downloadConfirm] 下载二次确认弹窗 默认false
///
/// [isBidding] 是否开启竞价模式 默认false
///
/// [bidding] 竞价成功、失败后调用 [FlutterTencentAdBiddingController] ,isBidding = true时必传
///
static Widget bannerAdView({
required String androidId,
required String iosId,
required double viewWidth,
required double viewHeight,
bool? downloadConfirm,
bool? isBidding,
FlutterUnioAdBiddingController? bidding,
FlutterUnionAdBannerCallBack? callBack,
}) {
return BannerAdView(
androidId: androidId,
iosId: iosId,
viewWidth: viewWidth,
viewHeight: viewHeight,
callBack: callBack,
downloadConfirm: downloadConfirm ?? false,
isBidding: isBidding ?? false,
bidding: bidding,
);
}
///
/// # 开屏广告
///
/// [androidId] android广告ID
///
/// [iosId] ios广告ID
///
/// [fetchDelay] 设置开屏广告从请求到展示所花的最大时长(并不是指广告曝光时长),
/// 取值范围为[1500, 5000]ms。如果需要使用默认值可以调用上一个构造方法
/// 或者给 fetchDelay 设为0。
///
/// [FlutterTencentAdSplashCallBack] 广告回调
///
/// [downloadConfirm] 下载二次确认弹窗 默认false
///
/// [isBidding] 是否开启竞价模式 默认false
///
/// [bidding] 竞价成功、失败后调用 [FlutterTencentAdBiddingController] ,isBidding = true时必传
///
static Widget splashAdView({
required String androidId,
required String iosId,
required int fetchDelay,
bool? downloadConfirm,
bool? isBidding,
FlutterUnioAdBiddingController? bidding,
FlutterUnionAdSplashCallBack? callBack,
}) {
return SplashAdView(
androidId: androidId,
iosId: iosId,
fetchDelay: fetchDelay,
callBack: callBack,
downloadConfirm: downloadConfirm ?? false,
isBidding: isBidding ?? false,
bidding: bidding,
);
}
///
/// # 动态信息流/横幅/视频贴片广告
///
/// [androidId] android广告ID
///
/// [iosId] ios广告ID
///
/// [viewWidth] 广告宽 单位dp
///
/// [viewHeight] 广告高 单位dp
///
/// [FlutterTencentAdExpressCallBack] 回调事件
///
/// [downloadConfirm] 下载二次确认弹窗 默认false
///
/// [isBidding] 是否开启竞价模式 默认false
///
/// [bidding] 竞价成功、失败后调用 [FlutterTencentAdBiddingController] ,isBidding = true时必传
///
static Widget expressAdView({
required String androidId,
required String iosId,
required int viewWidth,
required int viewHeight,
bool? downloadConfirm,
bool? isBidding,
FlutterUnioAdBiddingController? bidding,
FlutterUnionAdExpressCallBack? callBack,
}) {
return ExpressAdView(
androidId: androidId,
iosId: iosId,
viewWidth: viewWidth,
viewHeight: viewHeight,
callBack: callBack,
downloadConfirm: downloadConfirm ?? false,
isBidding: isBidding ?? false,
bidding: bidding,
);
}
///进入APP下载列表页
static Future<bool> enterAPPDownloadListPage() async {
return await _channel.invokeMethod("enterAPPDownloadListPage", {});
}
}

View File

@@ -0,0 +1,158 @@
part of 'flutter_union_ad.dart';
/// @Description: dart类作用描述
///显示
typedef TOnShow = void Function();
///曝光
typedef TOnExpose = void Function();
///失败
typedef TOnFail = void Function(int code, dynamic message);
///点击
typedef TOnClick = void Function();
///视频播放
typedef TOnVideoPlay = void Function();
///视频暂停
typedef TOnVideoPause = void Function();
///视频播放结束
typedef TOnVideoStop = void Function();
///跳过
typedef TOnSkip = void Function();
///倒计时结束
typedef TOnFinish = void Function();
///加载超时
typedef TOnTimeOut = void Function();
///关闭
typedef TOnClose = void Function();
///广告预加载完成
typedef TOnReady = void Function();
///广告预加载未完成
typedef TOnUnReady = void Function();
///广告奖励验证
typedef TOnVerify = void Function(
String transId, String rewardName, int rewardAmount);
///倒计时
typedef TOnADTick = void Function(int time);
///竞价回调
typedef TOnECPM = void Function(String ecpmLevel, int ecpm);
///banner广告回调
class FlutterUnionAdBannerCallBack {
TOnShow? onShow;
TOnFail? onFail;
TOnClick? onClick;
TOnExpose? onExpose;
TOnClose? onClose;
TOnECPM? onECPM;
FlutterUnionAdBannerCallBack(
{this.onShow,
this.onFail,
this.onClick,
this.onExpose,
this.onClose,
this.onECPM});
}
///动态信息流/横幅/视频贴片广告回调
class FlutterUnionAdExpressCallBack {
TOnShow? onShow;
TOnFail? onFail;
TOnClick? onClick;
TOnExpose? onExpose;
TOnClose? onClose;
TOnECPM? onECPM;
FlutterUnionAdExpressCallBack(
{this.onShow,
this.onFail,
this.onClick,
this.onExpose,
this.onClose,
this.onECPM});
}
///开屏广告回调
class FlutterUnionAdSplashCallBack {
TOnClose? onClose;
TOnShow? onShow;
TOnFail? onFail;
TOnClick? onClick;
TOnExpose? onExpose;
TOnADTick? onADTick;
TOnECPM? onECPM;
FlutterUnionAdSplashCallBack(
{this.onShow,
this.onFail,
this.onClick,
this.onClose,
this.onExpose,
this.onADTick,
this.onECPM});
}
///插屏广告回调
class FlutterUnionAdInteractionCallBack {
TOnShow? onShow;
TOnClick? onClick;
TOnClose? onClose;
TOnFail? onFail;
TOnReady? onReady;
TOnUnReady? onUnReady;
TOnExpose? onExpose;
TOnVerify? onVerify;
TOnECPM? onECPM;
FlutterUnionAdInteractionCallBack(
{this.onShow,
this.onClick,
this.onClose,
this.onFail,
this.onExpose,
this.onReady,
this.onUnReady,
this.onVerify,
this.onECPM});
}
///激励广告回调
class FlutterUnionAdRewardCallBack {
TOnShow? onShow;
TOnClose? onClose;
TOnExpose? onExpose;
TOnFail? onFail;
TOnClick? onClick;
TOnVerify? onVerify;
TOnReady? onReady;
TOnFinish? onFinish;
TOnUnReady? onUnReady;
TOnECPM? onECPM;
FlutterUnionAdRewardCallBack(
{this.onShow,
this.onClick,
this.onExpose,
this.onClose,
this.onFail,
this.onVerify,
this.onReady,
this.onFinish,
this.onUnReady,
this.onECPM});
}

View File

@@ -0,0 +1,142 @@
///数据类型
class FlutterUnionAdCode {
static const String adType = "adType";
///激励广告
static const String rewardAd = "rewardAd";
///插屏广告
static const String interactAd = "interactAd";
}
class FlutterTencentadMethod {
///stream中 广告方法
static const String onAdMethod = "onAdMethod";
///广告加载状态 view使用
///显示view
static const String onShow = "onShow";
///广告曝光
static const String onExpose = "onExpose";
///加载失败
static const String onFail = "onFail";
///点击
static const String onClick = "onClick";
///视频播放
static const String onVideoPlay = "onVideoPlay";
///视频暂停
static const String onVideoPause = "onVideoPause";
///视频结束
static const String onVideoStop = "onVideoStop";
///倒计时结束
static const String onFinish = "onFinish";
///加载超时
static const String onTimeOut = "onTimeOut";
///广告关闭
static const String onClose = "onClose";
///广告奖励校验
static const String onVerify = "onVerify";
///广告预加载完成
static const String onReady = "onReady";
///广告未预加载
static const String onUnReady = "onUnReady";
///倒计时
static const String onADTick = "onADTick";
///竞价
static const String onECPM = "onECPM";
}
///渠道id
class FlutterTencentadChannel {
///百度
static const int baidu = 1;
///头条
static const int toutiao = 2;
///优量汇
static const int tencent = 3;
///搜狗
static const int sougou = 4;
///其他网盟
static const int otherAd = 5;
///oppe
static const int oppo = 6;
///vivo
static const int vivo = 7;
///huawei
static const int huawei = 8;
///应用宝
static const int yinyongbao = 9;
///小米
static const int xiaomi = 10;
///金立
static const int jinli = 11;
///百度手机助手
static const int baiduMobile = 12;
///魅族
static const int meizu = 13;
///App Store
static const int appStore = 14;
///其他
static const int other = 999;
}
///个性化广告
class FlutterTencentadPersonalized {
///屏蔽个性化推荐广告
static const int close = 1;
///不屏蔽个性化推荐广告
static const int show = 0;
}
///竞价失败原因
class FlutterTencentAdBiddingLossReason {
/// 竞争力不足,如优量汇不是本次竞价的最高出价方,可上报此竞败原因
static const int LOW_PRICE = 1;
/// 返回超时,如优量汇在本次竞价中未返回广告,可上报此竞败原因
static const int TIME_OUT = 2;
///其他
static const int OTHER = 10001;
}
///本次竞胜方渠道ID
class FlutterTencentAdADNID {
///1 - 输给优量汇其它广告,当优量汇目标价报价为本次竞价的最高报价时,可上报此值,仅对混合比价类型的开发者适用
static const String tencentADN = "1";
/// 2 - 输给第三方ADN当其它ADN报价为本次竞价的最高报价时可上报此值您无需回传具体竞胜方渠道
static const String othoerADN = "2";
/// 3 - 输给自售广告主,当自售广告源报价为本次竞价的最高报价时,可上报此值,仅对有自售广告源的开发者使用
static const String appADN = "3";
}

View File

@@ -0,0 +1,143 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'flutter_union_ad.dart';
/// @Description: dart类作用描述
const EventChannel tencentAdEventEvent =
EventChannel("com.example.union_ad_ssgf/adevent");
class FlutterUnionAdStream {
///
/// # 注册stream监听原生返回的信息
/// [rewardAdCallBack] 激励广告回调
/// [interactionAdCallBack] 插屏广告回调
///
static StreamSubscription initAdStream(
{FlutterUnionAdRewardCallBack? flutterTencentadRewardCallBack,
FlutterUnionAdInteractionCallBack? flutterTencentadInteractionCallBack}) {
StreamSubscription _adStream =
tencentAdEventEvent.receiveBroadcastStream().listen((data) {
switch (data[FlutterUnionAdCode.adType]) {
///激励广告
case FlutterUnionAdCode.rewardAd:
switch (data[FlutterTencentadMethod.onAdMethod]) {
case FlutterTencentadMethod.onShow:
if (flutterTencentadRewardCallBack?.onShow != null) {
flutterTencentadRewardCallBack?.onShow!();
}
break;
case FlutterTencentadMethod.onClose:
if (flutterTencentadRewardCallBack?.onClose != null) {
flutterTencentadRewardCallBack?.onClose!();
}
break;
case FlutterTencentadMethod.onFail:
if (flutterTencentadRewardCallBack?.onFail != null) {
flutterTencentadRewardCallBack?.onFail!(
data["code"], data["message"]);
}
break;
case FlutterTencentadMethod.onClick:
if (flutterTencentadRewardCallBack?.onClick != null) {
flutterTencentadRewardCallBack?.onClick!();
}
break;
case FlutterTencentadMethod.onVerify:
if (flutterTencentadRewardCallBack?.onVerify != null) {
flutterTencentadRewardCallBack?.onVerify!(
data["transId"], data["rewardName"], data["rewardAmount"]);
}
break;
case FlutterTencentadMethod.onFinish:
if (flutterTencentadRewardCallBack?.onFinish != null) {
flutterTencentadRewardCallBack?.onFinish!();
}
break;
case FlutterTencentadMethod.onReady:
if (flutterTencentadRewardCallBack?.onReady != null) {
flutterTencentadRewardCallBack?.onReady!();
}
break;
case FlutterTencentadMethod.onUnReady:
if (flutterTencentadRewardCallBack?.onUnReady != null) {
flutterTencentadRewardCallBack?.onUnReady!();
}
break;
case FlutterTencentadMethod.onExpose:
if (flutterTencentadRewardCallBack?.onExpose != null) {
flutterTencentadRewardCallBack?.onExpose!();
}
break;
case FlutterTencentadMethod.onECPM:
if (flutterTencentadRewardCallBack?.onECPM != null) {
flutterTencentadRewardCallBack?.onECPM!(
data["ecpmLevel"], data["ecpm"]);
}
break;
}
break;
///插屏广告
case FlutterUnionAdCode.interactAd:
switch (data[FlutterTencentadMethod.onAdMethod]) {
case FlutterTencentadMethod.onShow:
if (flutterTencentadInteractionCallBack?.onShow != null) {
flutterTencentadInteractionCallBack?.onShow!();
}
break;
case FlutterTencentadMethod.onClose:
if (flutterTencentadInteractionCallBack?.onClose != null) {
flutterTencentadInteractionCallBack?.onClose!();
}
break;
case FlutterTencentadMethod.onFail:
if (flutterTencentadInteractionCallBack?.onFail != null) {
flutterTencentadInteractionCallBack?.onFail!(
data["code"], data["message"]);
}
break;
case FlutterTencentadMethod.onClick:
if (flutterTencentadInteractionCallBack?.onClick != null) {
flutterTencentadInteractionCallBack?.onClick!();
}
break;
case FlutterTencentadMethod.onExpose:
if (flutterTencentadInteractionCallBack?.onExpose != null) {
flutterTencentadInteractionCallBack?.onExpose!();
}
break;
case FlutterTencentadMethod.onReady:
if (flutterTencentadInteractionCallBack?.onReady != null) {
flutterTencentadInteractionCallBack?.onReady!();
}
break;
case FlutterTencentadMethod.onUnReady:
if (flutterTencentadInteractionCallBack?.onUnReady != null) {
flutterTencentadInteractionCallBack?.onUnReady!();
}
break;
case FlutterTencentadMethod.onVerify:
if (flutterTencentadInteractionCallBack?.onVerify != null) {
flutterTencentadInteractionCallBack?.onVerify!(
data["transId"], "", 0);
}
break;
case FlutterTencentadMethod.onECPM:
if (flutterTencentadInteractionCallBack?.onECPM != null) {
flutterTencentadInteractionCallBack?.onECPM!(
data["ecpmLevel"], data["ecpm"]);
}
break;
}
break;
}
});
return _adStream;
}
static void deleteAdStream(StreamSubscription stream) {
stream.cancel();
}
}

View File

@@ -0,0 +1,137 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:union_ad_ssgf/flutter_union_ad.dart';
class SplashAdView extends StatefulWidget {
final String androidId;
final String iosId;
final int fetchDelay;
final FlutterUnionAdSplashCallBack? callBack;
final bool downloadConfirm;
final bool isBidding;
final FlutterUnioAdBiddingController? bidding;
const SplashAdView({
Key? key,
required this.androidId,
required this.iosId,
required this.fetchDelay,
this.callBack,
required this.downloadConfirm,
required this.isBidding,
this.bidding,
}) : super(key: key);
@override
State<SplashAdView> createState() => _SplashAdViewState();
}
class _SplashAdViewState extends State<SplashAdView> {
final String _viewType = "com.example.union_ad_ssgf/SplashAdView";
MethodChannel? _channel;
//广告是否显示
bool _isShowAd = true;
@override
void initState() {
super.initState();
_isShowAd = true;
}
@override
Widget build(BuildContext context) {
if (!_isShowAd) {
return Container();
}
if (defaultTargetPlatform == TargetPlatform.android) {
return SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: AndroidView(
viewType: _viewType,
creationParams: {
"androidId": widget.androidId,
"fetchDelay": widget.fetchDelay,
"downloadConfirm": widget.downloadConfirm,
"isBidding": widget.isBidding,
},
onPlatformViewCreated: _registerChannel,
creationParamsCodec: const StandardMessageCodec(),
),
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: UiKitView(
viewType: _viewType,
creationParams: {
"iosId": widget.iosId,
"fetchDelay": widget.fetchDelay,
"isBidding": widget.isBidding,
},
onPlatformViewCreated: _registerChannel,
creationParamsCodec: const StandardMessageCodec(),
),
);
} else {
return Container();
}
}
//注册cannel
void _registerChannel(int id) {
_channel = MethodChannel("${_viewType}_$id");
_channel?.setMethodCallHandler(_platformCallHandler);
widget.bidding?.init(_channel);
}
//监听原生view传值
Future<dynamic> _platformCallHandler(MethodCall call) async {
switch (call.method) {
//显示广告
case FlutterTencentadMethod.onShow:
widget.callBack?.onShow!();
if (mounted) {
setState(() {
_isShowAd = true;
});
}
break;
//关闭
case FlutterTencentadMethod.onClose:
widget.callBack?.onClose!();
break;
//广告加载失败
case FlutterTencentadMethod.onFail:
if (mounted) {
setState(() {
_isShowAd = false;
});
}
Map map = call.arguments;
widget.callBack?.onFail!(map["code"], map["message"]);
break;
//点击
case FlutterTencentadMethod.onClick:
widget.callBack?.onClick!();
break;
//曝光
case FlutterTencentadMethod.onExpose:
widget.callBack?.onExpose!();
break;
//倒计时
case FlutterTencentadMethod.onADTick:
widget.callBack?.onADTick!(call.arguments);
break;
//竞价
case FlutterTencentadMethod.onECPM:
Map map = call.arguments;
widget.callBack?.onECPM!(map["ecpmLevel"], map["ecpm"]);
break;
}
}
}

View File

@@ -1,99 +0,0 @@
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:union_ad_ssgf/event/ad_event_handler.dart';
import 'union_ad_ssgf_platform_interface.dart';
/// 三商共富 Flutter 广告插件
class UnionAdSsgf {
/// 方法通道
static const MethodChannel _methodChannel =
MethodChannel('union_ad_ssgf_method');
/// 事件通道
static const EventChannel _eventChannel = EventChannel('union_ad_ssgf_event');
/// 请求应用跟踪透明度授权
static Future<bool> get requestIDFA async {
if (Platform.isIOS) {
final bool result = await _methodChannel.invokeMethod('requestIDFA');
return result;
}
return true;
}
/// 初始化广告
/// [appId] 应用媒体ID
static Future<bool> initAd(String appId) async {
final bool result = await _methodChannel.invokeMethod(
'initAd',
{'appId': appId},
);
print("🎉🎉🎉 UnionAd ==> 初始化完成");
return result;
}
/// 设置个性化广告
/// 0代表开启个性化广告推荐1代表关闭个性化广告推荐
static Future<bool> setPersonalizedState(int state) async {
final bool result = await _methodChannel.invokeMethod(
'setPersonalizedState',
{'state': state},
);
return result;
}
/// 展示开屏广告
/// [posId] 广告位 id
/// [logo] 如果传值则展示底部logo不传不展示则全屏展示
/// [fetchDelay] 拉取广告的超时时间,默认值 3 秒,取值范围[1.5~5]秒
static Future<bool> showSplashAd(String posId,
{String? logo, double fetchDelay = 3}) async {
final bool result = await _methodChannel.invokeMethod(
'showSplashAd',
{
'posId': posId,
'logo': logo,
'fetchDelay': fetchDelay,
},
);
return result;
}
/// 展示激励视频广告
/// [posId] 广告位 id
/// [playMuted] 是否静音播放
/// [customData] 设置服务端验证的自定义信息
/// [userId] 设置服务端验证的用户信息
static Future<bool> showRewardVideoAd(
String posId, {
bool playMuted = false,
String? customData,
String? userId,
}) async {
final bool result = await _methodChannel.invokeMethod(
'showRewardVideoAd',
{
'posId': posId,
'playMuted': playMuted,
'customData': customData,
'userId': userId,
},
);
return result;
}
///事件回调
///@params onData 事件回调
static Future<void> onEventListener(
OnAdEventListener onAdEventListener) async {
_eventChannel.receiveBroadcastStream().listen((data) {
hanleAdEvent(data, onAdEventListener);
});
}
Future<String?> getPlatformVersion() {
return UnionAdSsgfPlatform.instance.getPlatformVersion();
}
}

View File

@@ -1,17 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'union_ad_ssgf_platform_interface.dart';
/// An implementation of [UnionAdSsgfPlatform] that uses method channels.
class MethodChannelUnionAdSsgf extends UnionAdSsgfPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('union_ad_ssgf_method');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
}

View File

@@ -1,29 +0,0 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'union_ad_ssgf_method_channel.dart';
abstract class UnionAdSsgfPlatform extends PlatformInterface {
/// Constructs a UnionAdSsgfPlatform.
UnionAdSsgfPlatform() : super(token: _token);
static final Object _token = Object();
static UnionAdSsgfPlatform _instance = MethodChannelUnionAdSsgf();
/// The default instance of [UnionAdSsgfPlatform] to use.
///
/// Defaults to [MethodChannelUnionAdSsgf].
static UnionAdSsgfPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [UnionAdSsgfPlatform] when
/// they register themselves.
static set instance(UnionAdSsgfPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
}

View File

@@ -1,26 +0,0 @@
// In order to *not* need this ignore, consider extracting the "web" version
// of your plugin as a separate package, instead of inlining it in the same
// package as the core of your plugin.
// ignore: avoid_web_libraries_in_flutter
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:web/web.dart' as web;
import 'union_ad_ssgf_platform_interface.dart';
/// A web implementation of the UnionAdSsgfPlatform of the UnionAdSsgf plugin.
class UnionAdSsgfWeb extends UnionAdSsgfPlatform {
/// Constructs a UnionAdSsgfWeb
UnionAdSsgfWeb();
static void registerWith(Registrar registrar) {
UnionAdSsgfPlatform.instance = UnionAdSsgfWeb();
}
/// Returns a [String] containing the version of the platform.
@override
Future<String?> getPlatformVersion() async {
final version = web.window.navigator.userAgent;
return version;
}
}

View File

@@ -1,27 +0,0 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:union_ad_ssgf/union_ad_ssgf_method_channel.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
MethodChannelUnionAdSsgf platform = MethodChannelUnionAdSsgf();
const MethodChannel channel = MethodChannel('union_ad_ssgf_method');
setUp(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
channel,
(MethodCall methodCall) async {
return '42';
},
);
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
});
test('getPlatformVersion', () async {
expect(await platform.getPlatformVersion(), '42');
});
}

View File

@@ -1,29 +1,5 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:union_ad_ssgf/union_ad_ssgf.dart';
import 'package:union_ad_ssgf/union_ad_ssgf_platform_interface.dart';
import 'package:union_ad_ssgf/union_ad_ssgf_method_channel.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class MockUnionAdSsgfPlatform
with MockPlatformInterfaceMixin
implements UnionAdSsgfPlatform {
class MockUnionAdSsgfPlatform with MockPlatformInterfaceMixin {}
@override
Future<String?> getPlatformVersion() => Future.value('42');
}
void main() {
final UnionAdSsgfPlatform initialPlatform = UnionAdSsgfPlatform.instance;
test('$MethodChannelUnionAdSsgf is the default instance', () {
expect(initialPlatform, isInstanceOf<MethodChannelUnionAdSsgf>());
});
test('getPlatformVersion', () async {
UnionAdSsgf unionAdSsgfPlugin = UnionAdSsgf();
MockUnionAdSsgfPlatform fakePlatform = MockUnionAdSsgfPlatform();
UnionAdSsgfPlatform.instance = fakePlatform;
expect(await unionAdSsgfPlugin.getPlatformVersion(), '42');
});
}
void main() {}