
去年,我们对移动开发人员进行了在线搜索-Droid Mission。 一个月之内,参与者必须在三个方向上解决尽可能多的问题:修复它! (错误搜索和代码研究),将其砍死! (逆向工程)并加以挖掘! (了解Android的功能)。 该任务总共有23个任务-与Android专家在实际工作中遇到的任务非常相似。 在帖子中,我们将展示所有条件和正确的解决方案。
搜索错误和代码研究
修复它! #1
作者:Anastasia Laushkina在一个安静的星期五晚上,两名特工熬夜并引发了一场无法解决的纠纷。 指示他们使用整数键将大量机密数据存储在Sqlite数据库中。
代理A创建具有查询形式的表:CREATE TABLE t(键INT PRIMARY KEY,secret_value_1,secret_value_2),代理B创建具有查询形式的CREATE TABLE t(键INTEGER PRIMARY KEY ASC,secret_value_1,secret_value_2)。
代理程序A绝对确定其请求中没有错误,但是,代理程序B对此提出了反对意见:他声称在代理程序A的处理期间可能会破坏数据的完整性。
用逗号列出:负责代理B完整性但不负责代理A的一列或多列; 以及俄语中的数据结构,因此代理B的请求会更快。
注意事项答案格式:[列名称1],[列名称N],[结构名称](不带括号,带有小写字母)
响应示例:x,y,数组
解决方案它与rowId及其与主键的关系有关。
在“创建表t(键INTEGER PRIMARY KEY ASC,secret_value_1,secret_value_2)”的情况下,键是rowId的别名。 在“创建表t(键INT PRIMARY KEY,secret_value_1,secret_value_2)”的情况下-否。 原因可以
在文档中找到。
因此,在缺少与rowId的键关系的情况下,将成功执行“ INSERT INTO t VALUES(“ some”,“ y”,“ z”)形式的请求,这可能会破坏数据完整性。 如果存在这样的连接,将发生数据类型不匹配错误。
答案被分别视为一对key,rowId和key / rowId(因为存在连接,它们引用同一列)。
通过以搜索树的形式存储来实现速度。
修复它! #2
作者:Anastasia Laushkina二进制文件为了快速发现敌人,邱特工正在开发一种应用程序,其中包含一系列已知罪犯。 他轻松地设法在应用程序中显示此列表,但是他决定将这样的重要信息显示在主屏幕上。
为此,他需要一个小部件。 一个问题:由于某种原因,小部件不显示列表。 一种本能告诉代理,问题只在一行上。
为了获得最佳的解决方案,请用逗号以及需要替换的行名称来命名。
注意事项答案格式:[第1行],[第2行](不带括号)
响应示例:FrameLayout,ScrollView
解决方案1.我们正在寻找小部件的布局文件,我们在其中看到一个无效的ConstraintLayout元素(请参阅
developer.android.com/guide/topics/appwidgets#CreatingLayout )。
2.将其更改为可接受且最佳的-LinearLayout / FrameLayout。
尽管使用FrameLayout破坏了小部件中元素的布局,但仍被接受为答案。
修复它! #3
作者:Artyom Viter二进制文件测试人员注意到RAM消耗异常增加。 有人怀疑某处有泄漏。
您必须调查并定位该问题。 在响应中,指示应用程序类的方法名称及其调用位置,这将导致内存泄漏。 如果发现几个问题,请全部列出。
答案格式:[调用该方法的类的名称]:[此文件中的行号]:[方法名称](不带方括号)
按字典顺序指示几个答案,以#分隔。
一个例子:
如果您认为ExampleFileName.java文件中存在内存泄漏,
class ExampleFileName { public problemMethodName(Sting arg) {} public test(Sting arg) { problemMethodName(arg); // 8 ExampleFileName.java } }
响应示例:ExampleFileName:8:问题方法名称
解决方案第一种方法是代码分析。
- 我们研究应用程序代码。
- 第一次泄漏很明显。 这是SecondActivity类中的BroadCastReceiver注册。 如果您运行该应用程序,则可以在logcat中看到有关此问题的消息。
- 如果您注意startTimer()方法,您会注意到CountDownTimer类向主线程处理程序添加了一个闭包,并带有指向textView的链接。 并且在销毁活动之前,实例(CountDownTimer)类不会调用cancel()方法。 这是第二次内存泄漏。
第二种方法是工具。 您可以在应用程序中实现
Leakcanary库,通过adb或android Profiler获取堆转储,并分析收到的报告。 Eclipse Memory Analyzer(MAT)可以帮助进行分析。 为了在MAT中打开heapdump,您需要使用hprof-convd实用程序将其转换为MAT可以理解的格式。
修复它! #4
发言者:瓦利·伊布拉吉莫夫二进制文件伊万(Ivan)在网上电影院度过了第一天的生活。 由于他是一位出色的开发人员,因此他在观看电影时立即被要求修复崩溃。 他决定同时进行重构。 代码变得更加简洁明了,该错误已不再重现。
但是,后来发现电影的当前位置没有发送到后端。 帮助Ivan了解他混了哪一堂课。
响应是它应该使用的类的名称。
解决方案为了每五秒钟发送一次观看位置并连接到网络,使用了ExecutorService。 Ivan认为,为定期任务创建ScheduledThreadPoolExecutor-为什么不使用它来访问网络? 毕竟,ScheduledThreadPoolExecutor是ThreadPoolExecutor的后代。 Ivan甚至都不怀疑ScheduledThreadPoolExecutor仅使用一个线程,并且在进入网络时,您将需要等待周期性任务的完成。
scheduleFuture = scheduledExecutorService.scheduleAtFixedRate(() -> { try { timingsApi.sendTiming(filmId, player.getContentPosition()).get(); } catch (Exception e) { e.printStackTrace(); } }, 0, PERIOD_SECONDS, TimeUnit.SECONDS);
由于我们不在UI线程和新版本2.9中访问ExoPlayer,因此使该问题的解决方案变得更加复杂。 但这只是警告,而不是伊万的错误。
修复它! #5
作者:亚历山大·齐宾二进制文件一家公司正在开发《农民模拟器》游戏。
在此游戏中,只有玩家可以自由移动。 所有其他物体要么静止不动,要么具有明确定义的运动轨迹。 在某个时候,他们决定将敌人添加到游戏中,这将成为玩家的障碍。
指示开发商Vasya意识到敌人。 在实施过程中,Vasya遇到性能问题。 由于多线程,他决定修复它。 由于Vasya对多线程不熟悉,因此他利用了stackoverflow提供的解决方案。 因此他摆脱了性能问题。
但是,在测试阶段,发现游戏有时会冻结。 帮助Vasya查找原因。
作为响应,请输入有问题的代码行的编号-以逗号分隔,以升序排列且没有空格。 像这样:1,17,42
解决方案装箱基本类型(例如Integer中的int)是昂贵的操作。 为了优化,Java机器有时会重用此类对象。 行也不例外。
ENEMY_LOCK和DECISIONS_LOCK行在代码中用于同步。 因此,如果项目中的某些位置在同一行上具有同步,则可能发生死锁。
通常,您不能使用String / Integer等进行同步。
答案:7.8
修复它! #6
作者:伊万·普霍夫(Ivan Pukhov)二进制文件图像处理团队发布了一个新的本机库,您需要对其进行尝试并指出存在的问题。
解决方案这是一个容易完成的任务。 资源中包含库本身及其头文件。 在标头中,唯一的函数返回一个jobject。
除了创建一个测试项目(通过安装NDK)并进行调用之外,别无他法! 主线程上的函数调用将引发异常,要求不要以这种方式调用它。 我们以任何方式将执行转移到另一个线程,并且一切正常。
现在值得检查返回的对象-它真的是位图吗? 最简单的方法是立即将其显示在ImageView上。 我们将看到带有文本字符串的图像,该任务还需要以字符串形式的答案。 这可能不仅仅是巧合。 :)
修复它! #7
作者:德米特里·扎伊采夫(Dmitry Zaitsev)二进制文件有一个带有代码的应用程序,其中包含所需的代码。 不幸的是,布局已损坏,需要还原才能找到代码。
注意事项更正代码后,框架将从框架的左上角开始准确通过22个需要逆时针列出的字符。
响应示例:ScQG1jxbazmjbcARefbRMo
解决方案现在,Dagger已成为Android开发中的事实上的标准。 在配置完DI之后,将Inject写入构造函数的前面并把所有其余工作交给Dagger非常简单。 如此简单,您以后就可以停止考虑如何提供依赖项。 同样在Android中,您通常必须使用Context类及其后代:Application和Activity。 应用程序与应用程序的生命周期相关,活动与屏幕的生命周期相关。 因此,在使用这些类时,观察许多功能很重要。 该任务正是基于错误上下文的使用,而所有这些都使用DI掩盖了。
可以采取以下步骤来解决:
- 检查项目文件,并注意将ItemStyle应用于所有TextView。
- 看到样式没有真正应用。 这应该导致这样一个想法,即问题与主题有关,因此与活动的背景有关。
- 了解依赖关系图,并了解适配器中提供了Application上下文。
- 在ContestAdapter中,将上下文类型更改为Activity。
修复它! #8
发言者:瓦利·伊布拉吉莫夫二进制文件Ivan被赋予了通过使用DI通过ContentProvider重构应用程序以获取任务列表的任务。 他使用Dagger重构了应用程序,当它启动时,该应用程序开始崩溃。
帮助伊凡了解原因:
- 用您要转移到另一个班级的班级名称指定行号。
- 提供指向文档的链接,并带有指向某个地点的定位点,以帮助您了解应用程序崩溃的原因。
注意事项响应格式:[类别名称]:[行号]-> [类别名称]:[行号],[链接]
答案示例:
FirstCLass:12-> SecondClass:45,https://developer.android.com/reference/android/app/Activity#activity-lifecycle
解决方案该任务包括两个部分:
1.访问DI组件时,在ContentProvider中了解并提供崩溃解决方案。 正确的解决方案是将DI组件的初始化转移到ContentProvider。

ContentProvider上的onCreate方法比Application上的调用早。 如果您检查ActivityThread类和handleBindApplication方法中的应用程序启动代码,这很容易验证。

2.提供一个链接,该链接描述早于Application调用onContentProvider的onCreate。 Google在ContentProvider文档页面上没有提及这一点。 但这是onCreate Aplication方法的描述的摘录:
在应用程序启动时,创建任何活动,服务或接收者对象(不包括内容提供者)之前调用。
修复它! #9
作者:亚历山大·齐宾二进制文件开发人员被赋予执行某些项目列表的视图的任务。 在TK中,有一个重要的要求-将列表滚动到第一个和最后一个元素,以便这些元素出现在屏幕中央。 开发人员实现了该功能,但是在测试时发现有时居中不起作用。 帮助开发人员在代码中找到有问题的行。
注意事项答案格式:
<不带扩展名的文件名>:<用逗号分隔的行号>
<不带扩展名的文件名>:<用逗号分隔的行号>
行必须按字典顺序排序。 行号应按升序排序。
答案示例:
BarFoo:1
FooBar:12、13、14、99
解决方案代码中有两个问题:
1.从代码来看,假定对onViewDetachedFromWindow的调用将始终在onBindViewHolder之后进行,但事实并非如此。 onViewDetachedFromWindow的孪生方法是onViewAttachedToWindow。 如此一来,将调用holder.view.reset(),并且如果滚动缓慢,则居中将消失。
2.在版本7之前的Android上,addView(rootView,params)无法正常工作。
添加视图时,将生成一个LayoutParam,如下所示:
棉花糖
-github.com/aosp-mirror/platform_frameworks_base/blob/marshmallow-release/core/java/android/widget/FrameLayout.java#L403牛
轧糖-github.com/aosp-mirror/platform_frameworks_base/blob/nougat-release/core/java/android/widget/FrameLayout.java#L384FrameLayout.LayoutParams具有两个构造函数,其中一个接受ViewGroup.MarginLayoutParams并将边距复制到自身:
github.com/aosp-mirror/platform_frameworks_base/blob/nougat-release/core/java/android/view/ViewGroup.java#L14第二个构造函数接受ViewGroup.LayoutParams并忽略空白:
github.com/aosp-mirror/platform_frameworks_base/blob/nougat-release/core/java/android/view/ViewGroup.java#L7328因此,在版本7之前的Android上,居中功能不起作用。
答案是:
物品检视:35
主要活动:53
逆向工程
砍死它! #1
作者:Valentin Baryshev二进制文件一名开发者赶紧提交竞赛,甚至没有测试最终的.apk。
原来,他在activity_main.xml中混合了一些视图组。 此外,他将密钥的每个字母放在单独的文本视图中。 结果,布局上的所有视图都分开了,并且屏幕上出现了完全的愤怒。
尝试确定哪个视图组应该代替当前的视图组。
问题的答案是替换正确的视图组后屏幕上显示的代码。
解决方案必须以任何可用的方式(例如,通过在线反编译器)反编译.apk并通过代码重新创建项目。
如果分析main_layout,则可以从属性中猜测根视图组应为ConstraintLayout。
答案:tniartsnoc
砍死它! #2
作者:亚历山大·齐宾二进制文件有经验的用户使该应用程序崩溃。 他没有感到吃惊,打开日志,找到了导致错误的行:N72XbphDx5NnFl6CKMNl8w ==并将其发送给技术支持。
原来,他正在使用该应用程序的过时版本,该版本是在使用代码存储库之前开发的,因此解密代码丢失了。 但是,可以找到该应用程序旧版本的.apk。
帮助解密消息。
解决方案必须以任何可用的方式(例如,通过在线反编译器)反编译.apk并通过代码重新创建项目。
该代码本身包含加密功能和解密功能,您可以向其提供加密的字符串并获得答案。
答案是:
TF:干得好!
砍死它! #3
作者:Valentin Baryshev二进制文件您对Android应用程序生命周期的了解程度如何?
您是否知道在某些情况下系统会终止应用程序进程?
在应用程序上模拟这种情况。
还原应用程序后,您将看到答案-以代码形式。
解决方案只需在电话上安装.apk,运行并在系统卸载应用程序时获得保护套就足够了,但同时它仍处于幕后。
一种选择是安装应用程序,将其启动并最小化(或仅切换到另一个应用程序)。 然后,我们通过adb连接到计算机。 在Android Studio中,选择Logcat标签,选择该应用程序的进程,然后单击红色的停止图标。 然后从窗帘上打开以前最小化的应用程序。 屏幕将显示答案:sss2384gxcxxX。
砍死它! #4
作者:伊万·普霍夫(Ivan Pukhov)二进制文件一位前端同事发布了新模块的演示,但忘了发送规格。 现在他睡在另一个时区,必须在几分钟内完成演示。 在与同事的对话中,您还记得他的话:“是的,在那里很容易,您只需要编写存根就可以了。”
下载档案,并尝试在没有同事的情况下应对。
解决方案这是一个简单的任务,但需要一定的好奇心和毅力。
下载档案,将其内容上载到本地测试服务器,使用WebView创建一个项目并运行它。 短暂的超时后,我们会看到消息“校准方向”。 就是这样。 但是消息提示您也许应该用手拧一下手机? 此后,将显示有关引发的异常的可疑消息-带有调用以检查控制台输出。
我们打开控制台,并看到对API对象上不存在的方法的调用。 我们向WebView添加一个对象以实现JS API,然后再次尝试测试该应用程序。
显然,值得添加一个返回值。 我们选择添加-它起作用。 但是,任何地方都没有答案。 再次打开电话-称为新方法。 也添加它。 经过几次迭代,我们得到finalizeCalibrating调用,然后是storeResult,我们的行将到达该行。
下载源代码时,您可能什么都不愿解决,但请打开它们并找出答案。 此方法有效,但是过程中有几个小型耙子在等着您。
砍死它! #5
作者:伊万·普霍夫(Ivan Pukhov)新版android的开发程序集泄漏到网络上,新的应用程序签名机制直接在设备上调试。 在此程序集中,用于签名的私钥被缝入系统的源代码中,并且开发人员将UI的一部分留作了接收。 找到钥匙。
解决方案1.运行模拟器时,您会注意到SecretActivity应用程序。 如果打开它,则会出现一个吐司,并提供查看日志的信息。 由于这是一个调试程序集,因此有很多日志-需要按应用程序名称过滤它们。
adb logcat | grep 'SecretActivity'
1898 1898 E SecretActivity: Try broadcast ZggwxXsaQ9SapPKyHpnwnYmALHazVFWX
2.我们尝试通过日志中的操作抛出广播:
adb shell am broadcast -a ZggwxXsaQ9SapPKyHpnwnYmALHazVFWX
3.屏幕上将出现一个祝酒词,并带有一个建议,以通过SecretReceiver标签查看日志。
adb logcat | grep 'SecretReceiver'
1898 1898 E SecretReceiver: ISecretService.aidl
1898 1898 E SecretReceiver: package android.app;
1898 1898 E SecretReceiver: interface ISecretService {
1898 1898 E SecretReceiver: String getSecret();
1898 1898 E SecretReceiver: }
1898 1898 E SecretReceiver: See global settings to bind SecretService
4.我们查看全局设置以查找连接到SecretService的操作。
adb shell settings list global
...
bind_secret_service_action=g8mNyGQZR8aHLTXNWcjdwJJYZ85Ewx83
...
5.我们正在使用此aidl接口编写ISecretService服务的客户端。
public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = new Intent(); intent.setAction("g8mNyGQZR8aHLTXNWcjdwJJYZ85Ewx83"); PackageManager pm = getPackageManager(); List<ResolveInfo> resolveInfoList = pm.queryIntentServices(intent, 0); if (resolveInfoList == null || resolveInfoList.size() != 1) { return; } ResolveInfo serviceInfo = resolveInfoList.get(0); ComponentName component = new ComponentName(serviceInfo.serviceInfo.packageName, serviceInfo.serviceInfo.name); Intent explicitIntent = new Intent(intent); explicitIntent.setComponent(component); bindService(explicitIntent, new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { ISecretService secretService = ISecretService.Stub.asInterface(service); try { Log.d("MainActivity", secretService.getSecret()); } catch (Exception e) { Log.e("MainActivity", "RemoteException", e); } } @Override public void onServiceDisconnected(ComponentName name) { } }, Context.BIND_AUTO_CREATE); } }
6.运行,在日志中我们得到答案:
adb logcat | grep 'MainActivity'
1898 1898 E MainActivity: VENHz=qWr7y!t3ZhP!8Skw!!kcTkt7V%
砍死它! #6
作者:Pavel Vorobkalov二进制文件特珀先生写了一个Android应用来发送消息。
解密消息并继续。
答案格式:字符串作为链接。
解决方案在设备上安装并运行该应用程序。 我们看到两个按钮:发送消息和发送公式。 当您单击它们时,什么都看不见。 您需要弄清楚应用程序如何以及在何处发送内容。

我们使用一些用于逆向工程的方法打开.apk应用程序,并查看主要活动。 在这里,您可以看到onSendMessageClick和onSendFormulaClick方法。 他们使用操作“ com.yandex.tupper.action.NEW_MESSAGE”创建一个Intent,并使用键“ com.yandex.tupper.message”创建一个Intent,然后将其从列表中发送到应用程序(使用常规方法)。 他们通过调用PackageManager.queryBroadcastReceivers获得列表。
您可以通过从应用程序代码中提取消息来继续使用逆向工程工具来研究消息。 但是,通过附加的RSA加密有意(尽管有一点)使此路径变得复杂。
还有另一种方法可以更好地匹配Android编程。 您需要编写一个处理此广播Intent的应用程序。 我们在清单寄存器接收器中创建我们的应用程序:
<receiver android:name=".TupperMessageReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="com.yandex.tupper.action.NEW_MESSAGE" /> </intent-filter> </receiver>
在处理程序中,我们从额外消息中获取消息并运行我们自己的Activity,在其中我们将显示处理结果:
public class TupperMessageReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String message = intent.getStringExtra(MainActivity.EXTRA_MESSAGE); if (message == null) { return; } Intent activityIntent = new Intent(context, MainActivity.class); activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); activityIntent.putExtra(MainActivity.EXTRA_MESSAGE, message); context.startActivity(activityIntent); } }
现在让我们处理消息本身的解密。 如果您在Yandex中搜索“ Tupper的消息”,则可以找到有关Wupper公式
的文章 。 它具有数字形式的编码公式的示例:
48584 ...4858450636189713423582095962494202044581400587983244549483093085061934704708809928450644769865524364849997247024915119110411605739177407856919754326571855442057210445735883681829823754139634338225199452191651284348332905131193199953502413758765239264874613394906870130562295813219481113685339535565290850023875092856892694555974281546386510730049106723058933586052544096664351265349363643957125565695936815184334857605266940161251266951421550539554519153785457525756590740540157929001765967965480064427829131488548259914721248506352686630476300
通过单击“发送公式”按钮,该号码将作为消息发送。 对该数字进行解码的结果也在Wikipedia文章中。 它可以用来调试其解码算法的实现。
我们使用Java实现解码算法,为了美观起见,将结果二进制数转换为单色Bitmap:
public class TupperCodec { static final int TUPPER_R = 17; private TupperCodec() {} public static BigInteger decodeFromDecString(String decString) throws NumberFormatException { BigInteger data = new BigInteger(decString, 10); return data.divide(BigInteger.valueOf(TUPPER_R)); } public static String getNumberAsBinImage(BigInteger number) { String binString = number.toString(2); StringBuffer result = new StringBuffer(binString.length()); int i; for (i = 0; i < binString.length(); i++) { if ((i % TUPPER_R) == 0 && i > 0) { result.append('\n'); } result.append(binString.charAt(binString.length() - 1 - i)); } int numberOfRows = (binString.length() + TUPPER_R - 1) / TUPPER_R; for (; i < numberOfRows * TUPPER_R; i++) { result.append(~_~quot�quot~_~); } return result.toString(); } public static Bitmap getBinImageAsBitmap(String binImage) { String[] lines = binImage.split("\n"); int[] colors = new int[lines.length * TUPPER_R]; for (int i = 0; i < lines.length; i++) { String line = lines[i]; assert line.length() == TUPPER_R; for (int j = 0; j < line.length(); j++) { colors[i * TUPPER_R + j] = line.charAt(j) == '0' ? Color.WHITE : Color.BLACK; } } return Bitmap.createBitmap(colors, TUPPER_R, lines.length, Bitmap.Config.ARGB_8888); } }
在“活动”中,调用解码并将结果显示为ImageView中的位图:
public class MainActivity extends AppCompatActivity { static final String EXTRA_MESSAGE = "com.yandex.tupper.message"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = getIntent(); if (intent != null && intent.hasExtra(MainActivity.EXTRA_MESSAGE)) { String message = intent.getStringExtra(MainActivity.EXTRA_MESSAGE); try { BigInteger data = TupperCodec.decodeFromDecString(message); String binImage = TupperCodec.getNumberAsBinImage(data); Bitmap bitmap = TupperCodec.getBinImageAsBitmap(binImage); ImageView imageView = findViewById(R.id.message_image); imageView.setImageBitmap(bitmap); CharSequence toastText = TextUtils.ellipsize(message, new TextPaint(), 400, TextUtils.TruncateAt.END); Toast.makeText(this, toastText, Toast.LENGTH_SHORT).show(); } catch (NumberFormatException ex) { Toast.makeText(this, getString(R.string.invalid_message), Toast.LENGTH_LONG).show(); } } } }

—
clck.ru/FevR5 .
hack it! #7
:.
. , , .
: - .
.apk zip- classes.dex — Java- . , , dex2jar Android Studio. dex2jar Java-, Android Studio class-. , .
, proguard ( android.support.* androidx.*, ), — com.yandex.contest.keygenme.MainActivity, Activity . , : MainActivity.a#doInBackground bbaab b , .

c d. N- π ( --), . , π
hexed [c[i] + key[i]] = d[i], i = 0..11. , . 50 hex- π. : 537306089144. key_2056BE33E064.
, , — . doInBackground:

, , if (var12[0] == var8) {… }. var7 — i- . , var7 0 9 . . , .apk ( JVM) .
hack it! #8
:N , . : Android 4.4, 1200 × 600, FM-. , , FM- . FM- , .
, FM-. .
, :
1. 106.00
2. (seek)
3. ,
, .
: . .
: , a(106.00) 106.00 , b(true) , c() , a(106.00);b(true);c()
: , FM- . .apk dex-. Java. , , — jadx.
.apk, . MainActivity. : fragment_holder RadioFullFragment . . RadioFullPresenter, ChangeRadioFrequencyUsecase, RadioRepo. .
«implements RadioRepo». : RadioRepoImpl. , b. ServiceConnection — . , b lambda$new$0$RadioRepoImpl. , $$Lambda$RadioRepoImpl$FYTR9gAgZEGvrLjkbkpAIup7OKw, serviceConnection IRadioService radioService. , RadioService.
: onServiceConnected «b radioManager = Stub.asInterface(service)». , . «bindService». bindService «com.some_company.help_service». , com.some_company.carradio.FmUtils. , , . , : j , : 8880 88.00 . 8800. a. , .
. : k z. RadioRepoImpl, enableRadio() disableRadio() k. z(boolean isForwardDirection), « (seek) ».
: a(10600);z(true);j().
hack it! #9
:flatbuffers . GPX . . , .
FlatBuffers :
namespace ru.yandex.android.task; struct GeoPoint { latitude:double; longitude:double; } table Points { items:[GeoPoint]; } root_type Points;
: , sha256. .
, FlatBuffers.
1. fbs- .
2. Java-. JVM- , Gradle flatbuffers.
3. fbs-.
4. fbs-, - — . , FlatBuffers.
5. , . structs , struct table. .
6. Kotlin. FlatBuffers.
fbs , . FlatBufferBuilder input. input :
val pointsList = Points.getRootAsPoints(buffer) val restoredPoints = (0 until pointsList.itemsLength()) .mapNotNull(pointsList::items)
c , (lat, long). as-is GPX-.
GPX- ( ) . GPX XML. , XML- . XML- GPX-track-. XML- MAP- . «yandroid».
, . CLI-:
echo "yandroid" | shasum -a 256
.
Android
dig it! #1
:QA- .
:
— ( 'Screen 1')
— GO NEXT ( 'Screen 2')
— GO BACK
— GO NEXT,
— 5–10
: 'Screen 2'.
: , « ».
.apk .
C stack trace, .
stack trace …
at android.os.MessageQueue.nativePollOnce(Native method) at android.os.MessageQueue.next(MessageQueue.java:326) at android.os.Looper.loop(Looper.java:160) at com.android.server.SystemServer.run(SystemServer.java:454) at com.android.server.SystemServer.main(SystemServer.java:294) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:838)
… , RuntimeInit.java, 493, :
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
, , « ».
/data/anr/traces.txt /data/anr/anr_* (https://developer.android.com/topic/performance/vitals/anr#pull_a_traces_file). adb-:
adb root adb shell ls /data/anr adb pull /data/anr/<filename>
( adb root , Google API.)
stack trace . — stack trace, :
com.droid.mission.anrapk.MainActivity$2.onClick(MainActivity.java:43)

dig it! #2
:Android Studio, , MultiDex. -, apk- .dex- (65536). / apk Andorid Studio .dex-.
— dex- , dex- ( , ) . dex- .
: multidex , android gradle plugin 3.0.0 – 3.1.4
.
?
, dex-. Gradle — , , .apk.
Java- dex- ART/Dalvik: transformClassesWithDexBuilderForDebug. dex-, . Android Gradle plugin (AGP) Gradle . , — , Gradle . , . AGP, — DSL- Gradle build.gradle.
3.1.* minimal-main-dex. , .
dig it! #3
:— - .
?
: — .
, . Android WindowManager.
. ,
WindowManager.LayoutParams . , ,
TYPE_APPLICATION .
, , . , , - .
, Android SDK, , . ,
WindowManager AOSP. , , , .
, ,
TYPE_BOOT_PROGRESS . getWindowLayerFromTypeLw WindowManagerPolicy. Javadoc :
Returns the layer assignment for the window type. Allows you to control how different kinds of windows are ordered on-screen
, , . switch-case , layer — TYPE_POINTER, 2018. .
dig it! #4
:, - , .
. , , , .
Android , ?
, , Android SDK, .
startActivity Context . Context Activity — ,
startActivity . startActivityForResult. , Activity
mInstrumentation.execStartActivity .
? startActivity ActivityManagerService, ServiceManager.getService(Context.ACTIVITY_SERVICE).
ActivityManagerService . startActivityAsUser, startActivity, ActivityStarter execute. ,
.
C
startActivity . ,
abort , true START_ABORTED Activity.
, , mService.mIntentFirewall.checkStartActivity. ,
. :
This is called from ActivityManager to check if a start activity intent should be allowed.
, IntentFirewall .
RULES_DIR . : ifw.
dig it! #5
:. , (, ) — 8800. , , (+8800). , 8800 +8800 , / , .
. . , +8800 8800 .
, (java/c/c++). , .
:
source.android.com— :
androidxref.comDialer , , URI
authorities = "content://com.android.contacts"
.
ContentProvider. URI ContactsPoviider. : , SQL- . :
sb.append("PHONE_NUMBERS_EQUAL(" + Tables.DATA + "." + Phone.NUMBER + ", ");
PHONE_NUMBERS_EQUAL — SQL-. sqlite .
sqlite, , . ContactsProvider , config_use_strict_phone_number_comparation, . :
<bool name="config_use_strict_phone_number_comparation">true</bool>