返回列表 发新帖

解密——清羽弹窗

  [复制链接]

33

主题

1368

回帖

5876

积分

硕士生

Rank: 6Rank: 6

金币
679
好评
13
信誉
100
发表于 2020-5-18 16:53:06 | 显示全部楼层 | 阅读模式  来自 上海
本帖最后由  青春风暴 于 2020-5-24 18:24 编辑

清羽弹窗用起来很方便,但它的代码经过了加密
导致不便于修改,而且新手想要弄清楚它的原理也是困难重重

于是我模仿着写了一个类似的,顺便增加了sharedpreferences以及跳转Activity功能
同时增加了为每个弹窗设置别名的功能,以确保不同的弹窗之间“不再提示”的缓存数据不会互相干扰


这里附上一个用于测试的Demo:https://lanzous.com/icrksob

弹窗配置链接https://share.weiyun.com/5Hh5stU

下面附上源代码,供感兴趣的朋友们研究、学习

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Process;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;

import static android.content.Context.MODE_PRIVATE;


  1. public class MyOnlineDialog extends AsyncTask {
  2.     private Context mContext;
  3.     private String url, NAME = "dialog_version";
  4.     private MyDialogButton[] button = new MyDialogButton[3];
  5.     private int version;

  6.     public MyOnlineDialog(Context context, String url) {
  7.         this.mContext = context;
  8.         this.url = url;
  9.     }

  10.     /*如果一个软件中需要添加不同的弹窗,可以用这个构造方法
  11.     这样可以使得A界面的弹窗被用户点了“不再提示”后,不会影响到其他界面的弹窗继续弹出*/
  12.     public MyOnlineDialog(Context context, String url, String name) {
  13.         this.mContext = context;
  14.         this.url = url;
  15.         this.NAME = name + "_dialog_version";
  16.     }

  17.     public void init() {
  18.         try {
  19.             Class.forName("android.os.AsyncTask");
  20.             execute();
  21.         } catch (ClassNotFoundException e) {
  22.             e.printStackTrace();
  23.         }
  24.     }

  25.     //从链接中获取弹窗的配置信息
  26.     private String initConfig() {
  27.         try {
  28.             HttpURLConnection connection = (HttpURLConnection) (new URL(url)).openConnection();
  29.             connection.setRequestMethod("GET");
  30.             connection.setUseCaches(false);
  31.             connection.setReadTimeout(20000);
  32.             connection.setConnectTimeout(20000);
  33.             connection.setInstanceFollowRedirects(false);
  34.             connection.setRequestProperty("User-Agent"
  35.                     , "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7");

  36.             if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
  37.                 BufferedReader reader = new BufferedReader(new InputStreamReader(
  38.                         connection.getInputStream(), "UTF-8"));
  39.                 StringBuilder builder = new StringBuilder();
  40.                 String config;
  41.                 while (true) {
  42.                     String s = reader.readLine();
  43.                     if (s == null) {
  44.                         config = builder.toString();
  45.                         break;
  46.                     }
  47.                     builder.append(s);
  48.                 }

  49.                 //截取出弹窗的配置信息
  50.                 config = config.split("〈Configuration〉")[2]
  51.                         .replace(" ", " ")
  52.                         .replace("\\u003C", "<")
  53.                         .replace("<p>", "")
  54.                         .replace("</p>", "\n")
  55.                         .replace("<br  />", "\n");

  56.                 //这里可以借用这个弹窗来保存一些数据
  57.                 if (config.contains("〈SharedPreferences〉")) {
  58.                     setSharedPreferences(config.split("〈SharedPreferences〉")[1].trim());
  59.                 }

  60.                 return config;

  61.             } else return "";
  62.         } catch (Exception e) {
  63.             e.printStackTrace();
  64.             return "";
  65.         }
  66.     }

  67.     private void setSharedPreferences(String sp) {
  68.         String[] sharedPreferences = sp.split("◎");
  69.         if (0 < sharedPreferences.length) {
  70.             SharedPreferences.Editor editor = mContext.getSharedPreferences("data", MODE_PRIVATE).edit();
  71.             for (String s : sharedPreferences) {
  72.                 String key = s.split("〈Key〉")[1].trim();
  73.                 String value = s.split("〈Value〉")[1].trim();
  74.                 if (!s.contains("〈Type〉")) {
  75.                     editor.putString(key, value);
  76.                 } else {
  77.                     //保存数据时有多种数据类型可供选择,若没有选定类型,则默认为String类型
  78.                     String type = s.split("〈Type〉")[1];
  79.                     if (type.toLowerCase().contains("int")) {
  80.                         editor.putInt(key, Integer.parseInt(value));
  81.                     } else if (type.toLowerCase().contains("bool")) {
  82.                         editor.putBoolean(key, value.toLowerCase().contains("true"));
  83.                     } else if (type.toLowerCase().contains("float")) {
  84.                         editor.putFloat(key, Float.parseFloat(value));
  85.                     } else if (type.toLowerCase().contains("long")) {
  86.                         editor.putLong(key, Long.parseLong(value));
  87.                     } else {
  88.                         editor.putString(key, value);
  89.                     }
  90.                 }
  91.             }
  92.             editor.apply();
  93.         }
  94.     }

  95.     private void onclick(DialogInterface dialog, int which) {
  96.         button[-which - 1].execute();
  97.         setCancelable(dialog, true);
  98.     }

  99.     //这个方法通过反射将弹窗设定为不可取消,当然必要时也可以将弹窗取消
  100.     private void setCancelable(DialogInterface dialog, Boolean cancelable) {
  101.         try {
  102.             Field field = dialog.getClass().getSuperclass().getDeclaredField("mShowing");
  103.             field.setAccessible(true);
  104.             field.set(dialog, cancelable);
  105.         } catch (Exception e) {
  106.             e.printStackTrace();
  107.         }
  108.     }

  109.     @Override
  110.     protected Object doInBackground(Object[] objects) {
  111.         return initConfig();
  112.     }

  113.     protected void onPostExecute(Object var1) {
  114.         String config = (String) var1;
  115.         super.onPostExecute(config);

  116.         /*从本地获取版本号记录
  117.         如果版本号为k的弹窗被用户点击过“不再显示”,那此时只有版本号不小于k+1的弹窗才会显示*/
  118.         if (config.split("〈isEnabled〉")[1].contains("true")) {
  119.             SharedPreferences preferences = mContext.getSharedPreferences("data", MODE_PRIVATE);
  120.             int localVersion = preferences.getInt(NAME, 0);
  121.             version = Integer.parseInt(config.split("〈Version〉")[1].trim());
  122.             if (version > localVersion) show(config);
  123.         }
  124.     }

  125.     //构造并弹出弹窗
  126.     private void show(String config) {
  127.         String title = config.split("〈Title〉")[1].trim();
  128.         String message = config.split("〈Message〉")[1].trim();
  129.         boolean cancelable = config.split("〈Cancelable〉")[1].contains("true");
  130.         button[0] = new MyDialogButton(config.split("〈Positive_Button〉")[1]);
  131.         button[1] = new MyDialogButton(config.split("〈Negative_Button〉")[1]);
  132.         button[2] = new MyDialogButton(config.split("〈Neutral_Button〉")[1]);

  133.         AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
  134.         builder.setTitle(title).setMessage(message);
  135.         if (button[0].isEnabled) builder.setPositiveButton(button[0].text, this::onclick);
  136.         if (button[1].isEnabled) builder.setNegativeButton(button[1].text, this::onclick);
  137.         if (button[2].isEnabled) builder.setNeutralButton(button[2].text, this::onclick);

  138.         AlertDialog alertDialog = builder.create();
  139.         alertDialog.show();
  140.         //将弹窗设置为不可取消的
  141.         if (!cancelable) {
  142.             setCancelable(alertDialog, false);
  143.         }
  144.     }

  145.     //弹窗按钮内部类
  146.     private class MyDialogButton {
  147.         boolean isEnabled;
  148.         String text, event;

  149.         MyDialogButton(String config) {
  150.             isEnabled = config.split("〈isEnabled〉")[1].contains("true");
  151.             text = config.split("〈Text〉")[1].trim();
  152.             event = config.split("〈Event〉")[1].trim();
  153.         }

  154.         void execute() {
  155.             if (event.contains("〈Toast〉")) {
  156.                 Toast.makeText(mContext, event.split("〈Toast〉")[1].trim(), Toast.LENGTH_LONG).show();
  157.             }
  158.             if (event.contains("〈QQGroup〉")) {
  159.                 mContext.startActivity(new Intent(Intent.ACTION_VIEW
  160.                         , Uri.parse("mqqapi://card/show_pslcard?&uin=" +
  161.                         event.split("〈QQGroup〉")[1].trim() + "&card_type=group")));
  162.             } else if (event.contains("〈QQ〉")) {
  163.                 mContext.startActivity(new Intent(Intent.ACTION_VIEW
  164.                         , Uri.parse("mqqwpa://im/chat?chat_type=wpa&uin=" +
  165.                         event.split("〈QQ〉")[1].trim())));
  166.             } else if (event.contains("〈Url〉")) {
  167.                 String url = event.split("〈Url〉")[1].trim();
  168.                 if (!url.startsWith("http")) url = "https://" + url;
  169.                 mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
  170.             } else if (event.contains("〈Activity〉")) {
  171.                 try {
  172.                     mContext.startActivity(new Intent(mContext, Class.forName(
  173.                             event.split("〈Activity〉")[1].trim())));
  174.                 } catch (ClassNotFoundException e) {
  175.                     e.printStackTrace();
  176.                 }
  177.             }
  178.             if (event.contains("〈-Dismiss-〉")) {
  179.                 mContext.getSharedPreferences("data", MODE_PRIVATE)
  180.                         .edit().putInt(NAME, version).apply();
  181.             }
  182.             if (event.contains("〈-Exit-〉")) Process.killProcess(Process.myPid());
  183.         }
  184.     }
  185. }
复制代码

下面是调用示例:
  1. //整个软件只植入这一个弹窗的调用方法
  2. new MyOnlineDialog(this, "https://share.weiyun.com/XXXXX").init();

  3. //植入多个弹窗的调用方法

  4. //比如这个要植入在主界面
  5. new MyOnlineDialog(this, "https://share.weiyun.com/XXXXX", "主界面").init();

  6. //这个则要植入在测试页
  7. new MyOnlineDialog(this, "https://share.weiyun.com/XXXXX", "测试页").init();
复制代码


【5月24日更新】非常抱歉,之前有个东西忘说了。
中间setPositiveButton那几句(也就是this::onclick那里),为了代码整洁美观,我就用了Lambda表达式。如果你直接复制,可能会报错。这个需要在项目的Module里设置一下Target Compatibility,让它引用JDK1.8。这个错误,如果按快捷键应该也是会自动跳出解决方案的

具体可以参考:https://blog.csdn.net/ocean20/article/details/83790755
已有3人评分好评 金币 理由
LittleWhiteDuck + 1 + 1
华月方昊 + 1 + 1
©麟杰 + 1 + 1 很给力!

查看全部评分 总评分:好评 +3  金币 +3 

回复

使用道具 举报

0

主题

1823

回帖

5369

积分

硕士生

Rank: 6Rank: 6

金币
7213
好评
0
信誉
100
发表于 2022-9-3 12:13:48 来自手机  | 显示全部楼层  来自 黑龙江
谢谢楼主分享
回复

使用道具 举报

5

主题

510

回帖

1296

积分

高中生

Rank: 4

金币
849
好评
0
信誉
100
发表于 2022-5-14 17:54:29 来自手机  | 显示全部楼层  来自 四川
前来捧场了
回复

使用道具 举报

0

主题

2220

回帖

6753

积分

硕士生

Rank: 6Rank: 6

金币
1305
好评
0
信誉
100

MT论坛新人

发表于 2022-5-14 13:43:55 来自手机  | 显示全部楼层  来自 河南
路过看看
回复

使用道具 举报

0

主题

474

回帖

1347

积分

高中生

Rank: 4

金币
1073
好评
0
信誉
100
发表于 2022-4-20 19:58:07 来自手机  | 显示全部楼层  来自 湖北
看看        
回复

使用道具 举报

34

主题

5021

回帖

1万

积分

博士生

闪电⚡️五连鞭

Rank: 7Rank: 7Rank: 7

金币
3496
好评
90
信誉
128

MT论坛新人考神MT论坛活跃会员MT论坛侠客MT论坛最佳新人

发表于 2022-4-20 17:29:56 来自手机  | 显示全部楼层  来自 福建
看看研究
回复

使用道具 举报

1

主题

150

回帖

434

积分

初中生

Rank: 3Rank: 3

金币
248
好评
0
信誉
100

MT论坛新人考神MT论坛帅哥

发表于 2022-3-19 00:25:35 来自手机  | 显示全部楼层  来自 海南
过来看一看
回复

使用道具 举报

0

主题

6570

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
6322
好评
0
信誉
108

考神MT论坛新人MT论坛帅哥

发表于 2022-1-11 20:41:59 来自手机  | 显示全部楼层  来自 新加
感谢分享
回复

使用道具 举报

0

主题

3203

回帖

5973

积分

硕士生

Rank: 6Rank: 6

金币
1618
好评
0
信誉
100

MT论坛最佳新人MT论坛新人考神

发表于 2022-1-11 14:10:41 来自手机  | 显示全部楼层  来自 江西
感谢分享
回复

使用道具 举报

0

主题

3203

回帖

5973

积分

硕士生

Rank: 6Rank: 6

金币
1618
好评
0
信誉
100

MT论坛最佳新人MT论坛新人考神

发表于 2021-11-12 21:24:26 来自手机  | 显示全部楼层  来自 江西
感谢分享
回复

使用道具 举报

17

主题

1317

回帖

3403

积分

大学生

Rank: 5Rank: 5

金币
632
好评
3
信誉
101

考神MT论坛新人MT论坛帅哥

发表于 2021-8-19 03:13:33 来自手机  | 显示全部楼层  来自 山东
路过再看看
回复

使用道具 举报

1

主题

1730

回帖

4836

积分

大学生

Rank: 5Rank: 5

金币
5134
好评
0
信誉
100
发表于 2021-8-19 00:49:04 | 显示全部楼层  来自 山东
学习使人进步
回复

使用道具 举报

12

主题

362

回帖

1066

积分

高中生

Rank: 4

金币
318
好评
0
信誉
95
发表于 2021-7-15 14:24:39 | 显示全部楼层  来自 贵州
懂得自然懂
回复

使用道具 举报

9

主题

257

回帖

798

积分

禁止发言

金币
146
好评
0
信誉
100
发表于 2021-7-11 22:20:00 来自手机  | 显示全部楼层  来自 广东
顶礼膜拜
回复

使用道具 举报

0

主题

970

回帖

3051

积分

大学生

Rank: 5Rank: 5

金币
1655
好评
0
信誉
100
发表于 2021-6-23 19:58:03 | 显示全部楼层  来自 新疆
非常不错,试试看,非常感谢
回复

使用道具 举报

6

主题

311

回帖

1184

积分

高中生

Rank: 4

金币
895
好评
0
信誉
100
发表于 2021-6-23 16:42:17 | 显示全部楼层  来自 广东
路过看看,
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表