[{"content":"I recently read Chris Banes\u0026rsquo; post \u0026ldquo;Should you use Kotlin Sequences for Performance?\u0026rdquo; and found his findings intriguing, especially when compared with Max Sidorov\u0026rsquo;s results in \u0026ldquo;Measuring sequences\u0026rdquo; and \u0026ldquo;Kotlin under the hood: how to get rid of recursion\u0026rdquo;. The differences in environments, methodologies, pipelines, and data produced varying benchmark results, leading to potential misunderstandings. In this post, I\u0026rsquo;ll show how data distribution can significantly impact the performance of processing pipelines under different parameters.\nBefore moving on, there\u0026rsquo;s a crucial reminder from JMH (Java Microbenchmark Harness):\nREMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. Do not assume the numbers tell you what you want them to tell.\nWhat Are We Really Comparing? When comparing Kotlin Sequences and Collection Operators, it\u0026rsquo;s important to understand that we\u0026rsquo;re not merely comparing abstract concepts like pull/push models or lazy/eager evaluation. Instead, we\u0026rsquo;re examining how these approaches perform in concrete scenarios and pipelines.\nEager evaluation on bare loops is a common pattern, but lazy calculations are not. If you\u0026rsquo;re not familiar with the differences between these approaches, I recommend checking the official Kotlin documentation for diagrams illustrating how Sequence operators differ in execution, as well as Max Sidorov\u0026rsquo;s \u0026ldquo;Sequences: Theory\u0026rdquo; for additional explanation.\nThe Pipeline Structure I liked Chris\u0026rsquo;s approach of using a simple repository example for benchmarking, so I\u0026rsquo;ve reused that pattern:\nclass SimplePipelineRepository(private val db: Db) : DataRepository { override fun getItemsListCollectionOperators(): List\u0026lt;UiModel\u0026gt; { return db.getItems() .filter { it.isEnabled } .map { UiModel(it.id) } } override fun getItemsListSequence(): List\u0026lt;UiModel\u0026gt; { return db.getItems() .asSequence() .filter { it.isEnabled } .map { UiModel(it.id) } .toList() } } These pipelines have identical named operations and lambda code. But if the \u0026ldquo;work\u0026rdquo; is the same, what exactly are we comparing? The benchmark results for these pipelines will include:\nMethod calling overhead (hasNext/next on filter and map) during one cycle versus two cycles with \u0026ldquo;flat\u0026rdquo; calculations Memory management differences between the approaches How the JIT compiler optimizes each pipeline Hardware performance characteristics For those interested in benchmarking JVM apps, I highly recommend the JMH Samples as the best resource for learn how to write benchmarks . Some samples demonstrate how to avoid or minimize the impact of JIT optimizations.\nBut for this post I don\u0026rsquo;t use them. :)\nSetting Up Benchmark Parameters To explore how Sequences and Collection Operators behave across different scenarios, I\u0026rsquo;ve built a matrix of benchmarks varying three key parameters:\nCollection size (batch size): The number of items in the input list Filtering ratio (percent): The percentage of items that will pass the filter logic - 0%, 10%, 25%, 50%, 75%, 90%, and 100% (from 0.0 to 1.0) Data distribution: How filtered items are arranged in the input list Ordered: Most passing items are at the beginning of the list Distributed: Passing items are distributed with fixed steps throughout the list Shuffled: Items are randomly arranged using Kotlin\u0026rsquo;s default shuffle algorithm Environment For all tests, I used the following environment:\nHardware: MacBook Pro M1 Pro (8-core) with 32GB RAM OS: macOS Sequoia 15.3.2 JDK: Temurin-21.0.6+7 JMH: version 1.37 Kotlin: 2.0.20 Benchmarking Medium and Large Collections In this section, I\u0026rsquo;ll present results for collection sizes of 100, 1,000, 10,000, and 100,000 elements. For the medium and large batch sizes, I used this configuration:\nWarm-ups: 10 Measurements: 25 Forks: 2 Mode: Throughput Duration: 1 second GC: G1 Fixed Heap size: 2GB Enabled +AlwaysPreTouch option to ensure memory is fully allocated at startup to avoid performance spikes If you want to measure time, be careful—especially for very fast computations. The ancient manuscript \u0026ldquo;Nanotrusting the Nanotime\u0026rdquo; by Aleksey Shipilёv explains it in detail.\nIn the tables below, the \u0026ldquo;Diff %\u0026rdquo; column shows the percentage difference between Collection Operators (ColOps) and Sequences implementations. Positive values (+%) indicate Sequences are faster, while negative values (-%) indicate Collection Operators are faster.\nOrdered data The table is based on data from [CSV ]. 100 1,000 10,000 100,000 Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 18,856,930 (±0.25%) 18,371,690 (±0.77%) -2.6% 10% 5,664,906 (±0.56%) 10,341,531 (±0.17%) +82.6% 25% 3,981,816 (±0.26%) 3,992,161 (±5.56%) +0.3% 50% 2,756,168 (±0.15%) 2,165,411 (±0.36%) -21.4% 75% 2,091,353 (±1.03%) 2,177,218 (±1.78%) +4.1% 90% 1,874,621 (±0.25%) 1,959,500 (±0.47%) +4.5% 100% 1,783,201 (±0.16%) 2,105,502 (±0.26%) +18.1% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 2,043,831 (±1.01%) 1,985,882 (±0.32%) -2.8% 10% 597,364 (±0.25%) 793,354 (±0.24%) +32.8% 25% 411,388 (±2.45%) 439,794 (±3.12%) +6.9% 50% 259,954 (±0.70%) 228,300 (±0.31%) -12.2% 75% 193,585 (±0.29%) 164,271 (±0.26%) -15.1% 90% 165,430 (±1.00%) 140,505 (±0.48%) -15.1% 100% 154,720 (±0.23%) 151,463 (±0.52%) -2.1% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 174,631 (±0.63%) 199,958 (±0.56%) +14.5% 10% 56,166 (±0.19%) 69,180 (±0.79%) +23.2% 25% 37,806 (±0.21%) 42,571 (±0.35%) +12.6% 50% 25,220 (±0.16%) 23,809 (±0.24%) -5.6% 75% 18,769 (±0.46%) 16,590 (±0.96%) -11.6% 90% 16,255 (±0.55%) 14,730 (±0.45%) -9.4% 100% 14,951 (±0.49%) 14,318 (±0.27%) -4.2% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 20,271 (±0.75%) 20,799 (±0.53%) +2.6% 10% 5,546 (±0.44%) 7,669 (±1.18%) +38.3% 25% 3,827 (±0.37%) 4,219 (±0.27%) +10.2% 50% 2,467 (±0.34%) 2,477 (±1.00%) +0.4% 75% 1,836 (±0.40%) 1,673 (±3.58%) -8.8% 90% 1,611 (±0.40%) 1,428 (±2.12%) -11.4% 100% 1,490 (±0.34%) 1,457 (±0.33%) -2.2% Key Observations for Ordered Data:\nWith ordered data, Sequences show a dramatic advantage (+82.6%) for small collections with low filter percentages (10%) As the filter percentage increases, the advantage diminishes and sometimes reverses For medium-to-large collections (10,000+), Sequences perform better at very low (0-10%) and very high (90-100%) filter percentages Shuffled data Yes, it\u0026rsquo;s strange to use shuffled data, but it\u0026rsquo;s the easiest way to demonstrate behavior in an \u0026ldquo;unstable\u0026rdquo; state.\nThe table is based on data from [CSV ]. 100 1000 10000 100000 Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 18,351,457 (±0.21%) 18,488,046 (±0.18%) +0.7% 10% 5,261,433 (±0.33%) 3,018,650 (±4.93%) -42.6% 25% 3,903,873 (±0.31%) 4,143,732 (±0.21%) +6.1% 50% 2,666,945 (±0.17%) 2,471,655 (±0.61%) -7.3% 75% 2,037,681 (±0.25%) 2,182,115 (±0.26%) +7.1% 90% 1,836,880 (±0.34%) 1,958,603 (±0.58%) +6.6% 100% 1,770,229 (±0.39%) 2,105,915 (±0.23%) +19.0% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 2,002,420 (±0.31%) 1,983,975 (±0.53%) -0.9% 10% 653,513 (±0.51%) 757,662 (±0.78%) +15.9% 25% 386,755 (±0.28%) 440,960 (±0.29%) +14.0% 50% 257,941 (±0.28%) 224,932 (±0.33%) -12.8% 75% 191,069 (±0.25%) 164,345 (±0.26%) -14.0% 90% 163,977 (±0.37%) 140,887 (±0.37%) -14.1% 100% 151,641 (±0.33%) 151,823 (±0.26%) +0.1% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 171,580 (±0.75%) 152,665 (±1.08%) -11.0% 10% 59,221 (±0.50%) 64,486 (±0.23%) +8.9% 25% 37,434 (±0.15%) 39,228 (±0.16%) +4.8% 50% 23,604 (±0.24%) 16,776 (±0.31%) -28.9% 75% 18,626 (±0.42%) 13,201 (±0.52%) -29.1% 90% 16,292 (±0.33%) 11,974 (±0.39%) -26.5% 100% 14,958 (±0.30%) 14,656 (±0.20%) -2.0% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 12,491 (±0.84%) 12,303 (±0.70%) -1.5% 10% 4,033 (±1.05%) 4,826 (±2.09%) +19.7% 25% 2,092 (±2.50%) 1,991 (±4.67%) -4.8% 50% 1,200 (±2.32%) 1,021 (±4.02%) -14.9% 75% 1,294 (±4.23%) 777 (±2.80%) -40.0% 90% 1,430 (±2.74%) 925 (±3.25%) -35.3% 100% 1,481 (±0.48%) 1,479 (±0.17%) -0.1% Key Observations for Shuffled Data:\nHigh filter percentages (90-100%) show more consistent results regardless of distribution Distributed data The table is based on data from [CSV ]. 100 1000 10000 100000 Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 18,871,658 (±0.19%) 18,451,270 (±0.63%) -2.2% 10% 5,749,032 (±0.48%) 2,853,098 (±0.33%) -50.4% 25% 3,980,196 (±0.18%) 4,076,371 (±0.52%) +2.4% 50% 2,672,543 (±0.19%) 2,444,005 (±0.21%) -8.6% 75% 2,059,558 (±0.25%) 2,116,703 (±0.34%) +2.8% 90% 1,872,262 (±0.32%) 1,956,287 (±0.52%) +4.5% 100% 1,723,854 (±1.07%) 2,102,347 (±0.89%) +22.0% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 2,055,754 (±0.37%) 1,977,181 (±0.51%) -3.8% 10% 642,339 (±0.66%) 731,905 (±0.40%) +13.9% 25% 394,202 (±0.27%) 425,367 (±0.38%) +7.9% 50% 261,218 (±0.30%) 223,243 (±0.40%) -14.5% 75% 190,938 (±0.30%) 163,153 (±0.38%) -14.6% 90% 165,775 (±0.47%) 140,938 (±0.42%) -15.0% 100% 154,954 (±0.24%) 152,512 (±0.51%) -1.6% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 175,417 (±0.64%) 194,157 (±0.98%) +10.7% 10% 56,803 (±0.23%) 60,288 (±0.60%) +6.1% 25% 38,284 (±0.13%) 38,877 (±0.23%) +1.5% 50% 24,950 (±0.25%) 18,808 (±0.38%) -24.6% 75% 19,091 (±0.26%) 15,133 (±0.33%) -20.7% 90% 16,488 (±0.47%) 14,027 (±0.47%) -14.9% 100% 15,174 (±0.13%) 14,431 (±0.31%) -4.9% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 20,056 (±0.28%) 19,859 (±0.37%) -1.0% 10% 5,647 (±0.23%) 7,057 (±0.26%) +25.0% 25% 3,869 (±0.13%) 2,747 (±0.18%) -29.0% 50% 2,485 (±0.23%) 1,533 (±0.32%) -38.3% 75% 1,826 (±0.31%) 1,528 (±0.31%) -16.3% 90% 1,618 (±0.27%) 1,466 (±0.51%) -9.4% 100% 1,508 (±0.25%) 1,462 (±0.37%) -3.1% Key Observations for Distributed Data:\nDistributed data produces results similar to shuffled data for many cases For small collections with low filter percentages (10%), Sequences perform even worse As collection size increases, the distribution pattern impacts performance more significantly Comparative Analysis of Distribution Impact To better understand how data distribution affects each approach, I\u0026rsquo;ve compared the same implementation across different distribution patterns.\nImpact on Collection Operators 100 1000 10000 100000 Filter % Ordered CollOps (ops/s) Distributed CollOps (ops/s) Diff Ordered vs Distributed Shuffled CollOps (ops/s) Distribution Impact (min-max) 0% 18,856,929 ±0.25% 18,871,657 ±0.50% +0.08% 18,351,457 ±0.17% 2.76% 10% 5,664,906 ±0.56% 5,749,031 ±0.33% +1.49% 5,261,433 ±0.89% 8.61% 25% 3,981,815 ±0.26% 3,980,196 ±0.29% -0.04% 3,903,873 ±0.56% 1.96% 50% 2,756,167 ±0.15% 2,672,543 ±0.58% -3.03% 2,666,945 ±1.01% 3.24% 75% 2,091,353 ±1.03% 2,059,557 ±0.69% -1.52% 2,037,681 ±0.24% 2.57% 90% 1,874,621 ±0.25% 1,872,261 ±0.26% -0.13% 1,836,880 ±0.57% 2.01% 100% 1,783,200 ±0.16% 1,723,854 ±1.07% -3.33% 1,770,228 ±0.39% 3.33% Filter % Ordered CollOps (ops/s) Distributed CollOps (ops/s) Diff Ordered vs Distributed Shuffled CollOps (ops/s) Distribution Impact (min-max) 0% 2,043,831 ±1.01% 2,055,753 ±0.31% +0.58% 2,002,420 ±0.30% 2.61% 10% 597,364 ±0.25% 642,339 ±0.16% +7.53% 653,512 ±0.28% 9.40% 25% 411,388 ±2.45% 394,201 ±0.59% -4.18% 386,755 ±0.24% 5.99% 50% 259,954 ±0.70% 261,218 ±1.50% +0.49% 257,940 ±0.24% 1.26% 75% 193,584 ±0.29% 190,938 ±1.47% -1.37% 191,069 ±0.18% 1.37% 90% 165,429 ±1.00% 165,775 ±0.51% +0.21% 163,977 ±0.18% 1.09% 100% 154,719 ±0.23% 154,954 ±0.88% +0.15% 151,640 ±0.52% 2.14% Filter % Ordered CollOps (ops/s) Distributed CollOps (ops/s) Diff Ordered vs Distributed Shuffled CollOps (ops/s) Distribution Impact (min-max) 0% 174,630 ±0.63% 175,417 ±1.15% +0.45% 171,580 ±1.82% 2.20% 10% 56,165 ±0.19% 56,803 ±0.11% +1.14% 59,221 ±3.79% 5.44% 25% 37,805 ±0.21% 38,284 ±0.18% +1.26% 37,434 ±0.55% 2.24% 50% 25,219 ±0.16% 24,949 ±0.45% -1.07% 23,603 ±0.83% 6.41% 75% 18,768 ±0.46% 19,090 ±0.15% +1.72% 18,626 ±0.44% 2.48% 90% 16,254 ±0.55% 16,487 ±0.22% +1.43% 16,292 ±0.29% 1.43% 100% 14,950 ±0.49% 15,174 ±0.24% +1.50% 14,957 ±0.23% 1.50% Filter % Ordered CollOps (ops/s) Distributed CollOps (ops/s) Diff Ordered vs Distributed Shuffled CollOps (ops/s) Distribution Impact (min-max) 0% 20,271 ±0.75% 20,055 ±0.20% -1.06% 12,491 ±0.57% 38.38% 10% 5,545 ±0.44% 5,646 ±0.49% +1.83% 4,033 ±1.53% 29.10% 25% 3,827 ±0.37% 3,868 ±0.52% +1.09% 2,091 ±0.84% 46.44% 50% 2,467 ±0.34% 2,484 ±0.24% +0.72% 1,199 ±0.26% 52.09% 75% 1,835 ±0.40% 1,825 ±0.49% -0.53% 1,294 ±0.25% 29.48% 90% 1,611 ±0.40% 1,617 ±0.23% +0.41% 1,430 ±0.18% 11.66% 100% 1,490 ±0.34% 1,508 ±0.31% +1.21% 1,480 ±0.62% 1.84% Key Observations: Collection Operators show relatively low sensitivity to data distribution for small and medium collections. For the largest collections (100,000 items), however, shuffled data can cause dramatic performance drops (up to 52% slower compared to ordered data).\nImpact on Sequences 100 1000 10000 100000 Filter % Ordered Sequence (ops/s) Distributed Sequence (ops/s) Diff Ordered vs Distributed Shuffled Sequence (ops/s) Distribution Impact (min-max) 0% 18,371,690 ±0.77% 18,451,269 ±0.21% +0.43% 18,488,045 ±0.13% 0.63% 10% 10,341,531 ±0.17% 2,853,097 ±3.25% -72.41% 3,018,649 ±3.64% 72.41% 25% 3,992,161 ±5.56% 4,076,371 ±0.21% +2.11% 4,143,732 ±0.21% 3.80% 50% 2,165,410 ±0.36% 2,444,005 ±6.88% +12.87% 2,471,655 ±6.55% 14.14% 75% 2,177,218 ±1.78% 2,116,703 ±0.19% -2.78% 2,182,114 ±1.36% 3.00% 90% 1,959,499 ±0.47% 1,956,287 ±0.60% -0.16% 1,958,603 ±0.19% 0.16% 100% 2,105,501 ±0.26% 2,102,346 ±0.30% -0.15% 2,105,915 ±0.26% 0.17% Filter % Ordered Sequence (ops/s) Distributed Sequence (ops/s) Diff Ordered vs Distributed Shuffled Sequence (ops/s) Distribution Impact (min-max) 0% 1,985,881 ±0.32% 1,977,180 ±0.18% -0.44% 1,983,974 ±0.59% 0.44% 10% 793,353 ±0.24% 731,905 ±0.47% -7.75% 757,661 ±0.29% 7.75% 25% 439,794 ±3.12% 425,366 ±0.29% -3.28% 440,960 ±0.20% 3.55% 50% 228,299 ±0.31% 223,243 ±0.35% -2.21% 224,932 ±0.28% 2.21% 75% 164,270 ±0.26% 163,153 ±0.33% -0.68% 164,344 ±0.29% 0.73% 90% 140,504 ±0.48% 140,938 ±0.37% +0.31% 140,886 ±0.30% 0.31% 100% 151,462 ±0.52% 152,512 ±0.23% +0.69% 151,822 ±0.30% 0.69% Filter % Ordered Sequence (ops/s) Distributed Sequence (ops/s) Diff Ordered vs Distributed Shuffled Sequence (ops/s) Distribution Impact (min-max) 0% 199,957 ±0.56% 194,157 ±0.53% -2.90% 152,665 ±0.82% 23.65% 10% 69,180 ±0.79% 60,288 ±0.50% -12.85% 64,486 ±0.93% 12.85% 25% 42,571 ±0.35% 38,876 ±0.15% -8.68% 39,227 ±1.71% 8.68% 50% 23,809 ±0.24% 18,807 ±0.38% -21.01% 16,775 ±1.86% 29.54% 75% 16,589 ±0.96% 15,133 ±0.80% -8.78% 13,201 ±1.49% 20.43% 90% 14,730 ±0.45% 14,027 ±0.27% -4.77% 11,973 ±0.55% 18.71% 100% 14,318 ±0.27% 14,430 ±0.47% +0.79% 14,656 ±0.50% 2.36% Filter % Ordered Sequence (ops/s) Distributed Sequence (ops/s) Diff Ordered vs Distributed Shuffled Sequence (ops/s) Distribution Impact (min-max) 0% 20,799 ±0.53% 19,859 ±0.28% -4.52% 12,303 ±1.03% 40.85% 10% 7,669 ±1.18% 7,056 ±0.26% -7.98% 4,825 ±0.58% 37.07% 25% 4,218 ±0.27% 2,747 ±0.23% -34.88% 1,990 ±0.94% 52.81% 50% 2,476 ±1.00% 1,532 ±0.28% -38.12% 1,021 ±0.70% 58.76% 75% 1,673 ±3.58% 1,528 ±2.67% -8.68% 776 ±0.74% 53.57% 90% 1,427 ±2.12% 1,465 ±0.75% +2.67% 924 ±1.28% 37.91% 100% 1,457 ±0.33% 1,461 ±0.25% +0.30% 1,478 ±0.96% 1.48% Key Observations: Sequences show significantly higher sensitivity to data distribution:\nFor 10% filter with batch size 100, changing from ordered to distributed data causes a dramatic 72.41% performance drop For larger collections (100,000 items), distribution can impact performance by over 58% Sequences maintain consistent performance regardless of distribution when the filter percentage is very high (90-100%) Summary of Medium and Large Collection Analysis: Collection Operators are generally less affected by data distribution than Sequences. The impact of distribution becomes more pronounced for both approaches as collection size increases, with Sequences showing greater sensitivity to distribution patterns when dealing with low filter percentages.\nThe Case of Small Collections For smaller collection sizes, I ran the benchmark with a different configuration:\nWarm-ups: 10 Measurements: 10 Forks: 2 Mode: Throughput Duration: 500ms GC: G1 Fixed Heap size: 2GB +AlwaysPreTouch: Ensure memory is fully allocated at startup Please note that results for batch size 5 should be interpreted with caution, as the filtering logic may not be ideal for such small collections.\nThe table is based on data from [CSV ].\n5 10 25 50 75 90 100 Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 90,383,823 (±8.14%) 150,940,586 (±0.66%) +67.00% 10% 55,406,888 (±0.71%) 90,949,266 (±0.71%) +64.15% 25% 55,657,083 (±2.39%) 90,665,757 (±1.61%) +62.90% 50% 36,332,025 (±0.52%) 57,613,969 (±0.73%) +58.58% 75% 31,807,259 (±2.12%) 51,554,783 (±2.09%) +62.08% 90% 29,086,237 (±0.41%) 33,116,184 (±0.48%) +13.86% 100% 29,062,972 (±0.73%) 33,067,388 (±0.71%) +13.78% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 34,480,704 (±7.84%) 100,852,381 (±1.09%) +192.49% 10% 24,357,839 (±2.48%) 58,669,965 (±0.38%) +140.87% 25% 16,613,017 (±3.99%) 44,866,760 (±1.31%) +170.07% 50% 16,718,661 (±1.79%) 35,870,028 (±0.34%) +114.55% 75% 12,612,989 (±32.56%) 28,001,904 (±0.53%) +122.01% 90% 16,601,949 (±0.75%) 26,188,253 (±1.30%) +57.74% 100% 15,926,913 (±0.56%) 19,520,430 (±0.34%) +22.56% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 51,657,026 (±4.86%) 58,587,172 (±0.66%) +13.42% 10% 11,049,179 (±10.36%) 21,250,762 (±18.15%) +92.33% 25% 13,373,719 (±1.62%) 8,895,053 (±18.92%) -33.49% 50% 10,458,196 (±0.31%) 12,943,845 (±10.29%) +23.77% 75% 7,869,224 (±0.59%) 8,039,625 (±0.38%) +2.17% 90% 6,548,830 (±3.77%) 7,766,415 (±0.45%) +18.59% 100% 6,434,227 (±0.54%) 5,578,035 (±0.51%) -13.31% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 33,632,347 (±1.92%) 34,421,169 (±0.91%) +2.35% 10% 9,120,782 (±6.20%) 9,094,591 (±11.51%) -0.29% 25% 7,823,834 (±1.54%) 8,458,477 (±25.74%) +8.11% 50% 5,235,795 (±0.80%) 4,877,777 (±13.45%) -6.84% 75% 3,913,007 (±0.62%) 3,123,934 (±0.46%) -20.17% 90% 3,727,317 (±1.12%) 3,348,660 (±15.37%) -10.16% 100% 3,333,589 (±0.60%) 2,898,578 (±0.34%) -13.05% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 24,191,600 (±0.78%) 23,965,514 (±0.57%) -0.93% 10% 7,153,022 (±4.58%) 5,567,417 (±11.44%) -22.17% 25% 5,196,413 (±1.07%) 4,176,509 (±5.52%) -19.63% 50% 3,428,483 (±1.18%) 2,867,721 (±0.96%) -16.36% 75% 2,715,268 (±1.87%) 2,167,624 (±1.75%) -20.17% 90% 2,339,701 (±2.03%) 2,700,372 (±0.76%) +15.42% 100% 2,203,987 (±4.02%) 2,722,582 (±0.49%) +23.53% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 20,671,915 (±1.03%) 20,931,777 (±0.12%) +1.26% 10% 6,217,694 (±1.49%) 4,855,438 (±14.99%) -21.91% 25% 4,304,343 (±1.73%) 4,596,305 (±0.39%) +6.78% 50% 3,126,313 (±1.33%) 2,473,220 (±0.58%) -20.89% 75% 2,354,969 (±1.74%) 2,635,813 (±0.38%) +11.93% 90% 2,093,672 (±0.33%) 2,217,350 (±0.32%) +5.91% 100% 1,958,377 (±0.34%) 2,363,563 (±0.30%) +20.69% Percent collectionsOperators (ops/s) sequence (ops/s) Diff % 0% 18,757,307 (±0.43%) 19,031,045 (±0.54%) +1.46% 10% 5,664,876 (±0.65%) 3,991,975 (±11.15%) -29.53% 25% 3,994,290 (±0.88%) 4,197,072 (±0.85%) +5.08% 50% 2,721,058 (±0.60%) 2,546,152 (±12.91%) -6.43% 75% 2,082,445 (±1.08%) 2,294,545 (±0.50%) +10.19% 90% 1,867,434 (±1.99%) 2,011,525 (±0.40%) +7.72% 100% 1,745,965 (±1.63%) 2,166,105 (±0.29%) +24.06% Key Observations for Small Collections:\nFor very small collections (5-10 items), Sequences consistently outperform Collection Operators across all filter percentages The advantage is most pronounced at low filter percentages (58-67% faster for 0-25% filters) As collection size increases to 50+, the performance pattern starts to resemble that of medium-sized collections For batch size 25, we observe higher variability in results, suggesting this might be a transitional size where JIT optimization patterns change Summary of Small Collection Analysis: Very small collections (under 25 items) show a distinctly different pattern from medium and large collections. Sequences consistently outperform Collection Operators for these tiny collections, regardless of filter percentage or distribution pattern. This advantage diminishes as collection size increases, eventually transitioning to the more complex pattern observed with medium-sized collections.\nConclusion Comparing Sequences and Collection Operators is not straightforward. Data distribution can impact performance, sometimes reversing the performance advantage between the two approaches. JIT and Hardware optimizations do a lot of work. Really. All this results are heavy depend on Branch predictor implementation and how particular JIT and Hardware work with them in case of Sequences and Collection operators.\nUpd from 22 Sept 25: there\u0026rsquo;s a great brief branch predictor overview by Matt Godbolt, I highly recommend to check the whole video.\nBased on all results we can conclude:\nDistribution matters: The arrangement of passing items in your collection can affect performance by up to 72% in some cases. Collection size is crucial: Very small collections behave differently from medium and large ones. Filter percentage affects sensitivity: Low and high filter percentages show different sensitivity to distribution patterns. No universal winner: Neither approach consistently outperforms the other across all scenarios. Rather than making a blanket recommendation, it\u0026rsquo;s more valuable to understand the factors that influence performance in your specific use case. Consider:\nWhat is your typical collection size? What percentage of items typically pass your filter? Is your data likely to have a specific distribution pattern? How critical is performance for this particular operation? Do you need a more stable results? Importand update For Android devs: this results isn\u0026rsquo;t fit for an Android apps. The environemt is different, AOT and hardware different. Don\u0026rsquo;t try to apply it to your apps directly, but pay attention on your benchmarks.\nP.S.: In future posts, I plan to explore:\nApplying JMH samples recommendations to benchmarks and showing how results change, checking how it would impact Android Finding a \u0026ldquo;baseline\u0026rdquo; for comparing Sequences and Collection Operators Comparing memory usage between the two approaches ","permalink":"https://qwexter.xyz/posts/benchmarking/sequences-vs-collection-operators/","summary":"\u003cp\u003eI recently read Chris Banes\u0026rsquo; post \u003ca href=\"https://chrisbanes.me/posts/use-sequence/\"\u003e\u0026ldquo;Should you use Kotlin Sequences for Performance?\u0026rdquo;\u003c/a\u003e and found his findings intriguing, especially when compared with Max Sidorov\u0026rsquo;s results in \u003ca href=\"https://medium.com/kotlin-academy/measuring-sequences-6536db14d576\"\u003e\u0026ldquo;Measuring sequences\u0026rdquo;\u003c/a\u003e and \u003ca href=\"https://medium.com/proandroiddev/kotlin-under-the-hood-how-to-get-rid-of-recursion-5a34162e890b\"\u003e\u0026ldquo;Kotlin under the hood: how to get rid of recursion\u0026rdquo;\u003c/a\u003e. The differences in environments, methodologies, pipelines, and data produced varying benchmark results, leading to potential misunderstandings.\nIn this post, I\u0026rsquo;ll show how data distribution can significantly impact the performance of processing pipelines under different parameters.\u003c/p\u003e","title":"Kotlin Sequences vs Collection Operators: Impact Of Data Distribution"},{"content":"Yep, I\u0026rsquo;d like to say \u0026ldquo;Thank you!\u0026rdquo; to people behind the Advent of Code 2024. It was my first time when I tried solve everything and I wasn\u0026rsquo;t complete it in time, but I still want to complete it and search \u0026ldquo;the right solution\u0026rdquo;.\nDeveloping an Android app isn\u0026rsquo;t required to work with complex CS-like problems. AAt least that\u0026rsquo;s what my experience tells me. The main problems are communication, finding PM and designer needs, and implementing smooth UI and comfortable UX for the shortest time.\nThe maintainability and ability to extend and work with different AB-tests and analytics aligned with SOLID, Clean Architecture, etc is difficult too. But it requires different approaches to solving different problems vs CS-like problems that usually people ask at the interview stage.\nIt might sounds a little bit strange, but solving the advent of code was the easiest TDD practice that I have in my life. Step by step, aligned with example inputs and outputs I created tests ahead of implementation, built a simple solution, and refactor. When I started part II of the problems, I had such a simple and naive implementation that I can easily extend it to the second part solution without rewriting layers or dependencies.\nI wrote benchmarks to check the ways, even I knew the result I found a place where it could be used and prove that sometimes advanced implementations don\u0026rsquo;t worse it.\nI found applications for studies I did more than 10 years ago. I found insights into some techniques that I didn\u0026rsquo;t understand. I found great tasks to solve and bring light to my blind spots. And these memes at reddit (maybe AoC is the only reason why I read this platform), they\u0026rsquo;re funny, and I understand them now.\nIt was simple and hard, but it was\u0026hellip;joyful?\nYes, maybe it\u0026rsquo;s the right word.\nJoyful.\nThank you, Advent of Code 2024 team!\n","permalink":"https://qwexter.xyz/posts/thx_aoc_2024/","summary":"\u003cp\u003eYep, I\u0026rsquo;d like to say \u0026ldquo;Thank you!\u0026rdquo; to people behind the \u003ca href=\"https://adventofcode.com/2024/about\"\u003eAdvent of Code 2024\u003c/a\u003e. It was my first time when I tried solve everything and I wasn\u0026rsquo;t complete it in time, but I still want to complete it and search \u0026ldquo;the right solution\u0026rdquo;.\u003c/p\u003e\n\u003cp\u003eDeveloping an Android app isn\u0026rsquo;t required to work with complex CS-like problems. AAt least that\u0026rsquo;s what my experience tells me. The main problems are communication, finding PM and designer needs, and implementing smooth UI and comfortable UX for the shortest time.\u003c/p\u003e","title":"Thank you, Advent of Code 2024."},{"content":"This post shares a brief overview and links to helpful open-source code examples related to the installation process of an Android application using PackageInstaller - the Android team\u0026rsquo;s recommended method for installation. The post is the second of a series related to the installation process:\nHow an Android app installation works: Permissions and install by Intent Installation of an Android app by PackageInstaller (this) What\u0026rsquo;s going on inside the OS (coming soon) Brief overview of the installation process The API was introduced in Android 5 (API 21) and allows users to streamline installation, updating, and deletion processes. Another important feature is the ability to work with split APKs. This affects developers as well, because many developer-related features, like \u0026ldquo;Apply changes,\u0026rdquo; rely on the logic within.\nTo use PackageInstaller, you need the same permissions for installing, deleting, or updating apps as well as storage permissions. I found it interesting that to get access to already installed applications, you need the REQUEST_PACKAGE_DELETE permission. You can check more details on this in this comment.\nIn simple cases, the installation process is:\nObtain a session Copy APK data to the session stream Commit session changes One of the parameters to commit a session is a PendingIntent that allows you to get a result or request the user to approve the installation. Here\u0026rsquo;s a simple example based on the official one:\nprivate fun installAppByActionView() { val session = obtainSession() pushApkToSession(session) session.commit(intentSender()) } private fun pushApkToSession( session: PackageInstaller.Session, path: String = Environment.getExternalStorageDirectory().path + APP_PATH, ) { try { val file = File(path) val fileInputStream = FileInputStream(file) val sessionOutputStream = session.openWrite(\u0026#34;package\u0026#34;, 0, file.length()) val buffer = ByteArray(DEFAULT_BUFFER_SIZE) var bytesRead: Int while (fileInputStream.read(buffer).also { bytesRead = it } != -1) { sessionOutputStream.write(buffer, 0, bytesRead) } session.fsync(sessionOutputStream) fileInputStream.close() sessionOutputStream.close() } catch (e: IOException) { Log.e(TAG, \u0026#34;Error adding APK to session: ${e.message}\u0026#34;, e) } } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) if (PACKAGE_INSTALLED_ACTION != intent.action) return val status: Int = intent.extras?.getInt(PackageInstaller.EXTRA_STATUS) ?: return when (status) { // system requires approval from the user PackageInstaller.STATUS_PENDING_USER_ACTION -\u0026gt; { val confirmationIntent = intent.getParcelableExtra\u0026lt;Intent\u0026gt;(Intent.EXTRA_INTENT) confirmationIntent?.let { startActivity(confirmationIntent) } } PackageInstaller.STATUS_SUCCESS -\u0026gt; { Log.d( TAG, \u0026#34;Failed: $status. Message:${intent.extras?.getString(PackageInstaller.EXTRA_STATUS_MESSAGE)}\u0026#34; ) } PackageInstaller.STATUS_FAILURE, PackageInstaller.STATUS_FAILURE_ABORTED, PackageInstaller.STATUS_FAILURE_BLOCKED, PackageInstaller.STATUS_FAILURE_CONFLICT, PackageInstaller.STATUS_FAILURE_INCOMPATIBLE, PackageInstaller.STATUS_FAILURE_INVALID, PackageInstaller.STATUS_FAILURE_STORAGE -\u0026gt; { Log.d( TAG, \u0026#34;Failed: $status. Message:${intent.extras?.getString(PackageInstaller.EXTRA_STATUS_MESSAGE)}\u0026#34; ) } else -\u0026gt; { Log.d( TAG, \u0026#34;Unknown status: $status. Message:${intent.extras?.getString(PackageInstaller.EXTRA_STATUS_MESSAGE)}\u0026#34;, ) } } } In Android 34, you can request user approval before committing by calling:\nsession.requestUserPreapproval( PreapprovalDetails.Builder() .setPackageName(\u0026#34;xyz.qwexter.installation_demo\u0026#34;) .setLabel(\u0026#34;Approve me\u0026#34;) //.setIcon() //.setLocale() .build(), intentSender() ) There is an interesting difference in the user approval process. When the system requests approval from the user and they deny it, you don\u0026rsquo;t get any result back. You can only check if the session was abandoned manually or use SessionCallback. But, if you force user approval and they deny it, you\u0026rsquo;ll receive a STATUS_FAILURE_BLOCKED intent.\nIf you want to show some progress to the user or react to changes in the installation state, you should use the SessionCallback.\nIt may seem strange, but I think the best place to get more information is this merge request. I highly recommend checking the logic, which is more advanced than in the examples above. It also covers more cases, like silently installing or updating apps and how to delete them.\nAnd of course F-Droid. I think it\u0026rsquo;s the best project to get to know installation APIs, check how they manage permissions and UI updates.\nConclusion This way might seem a bit more complicated compared to using the system installer via Intent. But, it allows you\nCustomize UI\\UX of installation, updating, or deleting process in your app Silent management of apps in specific situations (check MR for explanation) Split APKs support So if you need more advanced tools to install applications that\u0026rsquo;s your choice.\n","permalink":"https://qwexter.xyz/posts/install_android_app_package_installer/","summary":"\u003cp\u003eThis post shares a brief overview and links to helpful open-source code examples related to the installation process of an Android application using \u003ca href=\"https://developer.android.com/reference/android/content/pm/PackageInstaller\"\u003ePackageInstaller\u003c/a\u003e - the Android team\u0026rsquo;s recommended method for installation. The post is the second of a series related to the installation process:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"/posts/install_android_app/\"\u003eHow an Android app installation works: Permissions and install by Intent\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eInstallation of an Android app by PackageInstaller (this)\u003c/li\u003e\n\u003cli\u003eWhat\u0026rsquo;s going on inside the OS (coming soon)\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"brief-overview-of-the-installation-process\"\u003eBrief overview of the installation process\u003c/h3\u003e\n\u003cp\u003eThe API was introduced in Android 5 (API 21) and allows users to streamline installation, updating, and deletion processes. Another important feature is the ability to work with split APKs. This affects developers as well, because many developer-related features, like \u0026ldquo;Apply changes,\u0026rdquo; rely on the logic within.\u003c/p\u003e","title":"Installation of an Android app by PackageInstaller"},{"content":"While researching how Google Play Integrity API can potentially check the installation source, I found the installation process quite interesting. So I decided to dive into details of how we can install apps and how the operating system (OS) installs them inside. There will be a few parts:\nHow an Android app installation works: Permissions and install by Intent (this article) Installation of an Android app by PackageInstaller What\u0026rsquo;s going on inside the OS (coming soon) To simplify, I suggest imagining a situation: we are working on \u0026ldquo;the App\u0026rdquo;, and one of the requested features is to install an app. I\u0026rsquo;d like to skip the download part and assume the APK is already on the device. We have several ways to solve it:\nFully delegate to the system installation process (non-session install) Manage the installation process in \u0026ldquo;the App\u0026rdquo; ourselves (session install) But before we start the installation, we need to talk about permissions.\nPermissions We have two main permissions related to the installation process (other related to deletion and updates):\nINSTALL_PACKAGES - Allow install apps without user interaction, completely silent process. Used by the system-level apps or the device manager that needs to install packages without user interaction (e.g., for enterprise solutions or OEMs) and it\u0026rsquo;s nearly impossible to launch the app with this permission in Google Play. Even more, Google Play protects can delete apps with this permission even if you download and install them from other sources. REQUEST_INSTALL_PACKAGES - Allow request an installation process to the user. Requires user interaction and can install an app silently in some cases. We must use this one to install something, and be ready to explain why you need this permission to the Google Play team or your publication can be rejected. Note: It\u0026rsquo;s interesting but my demo app with INSTALL_PACKAGES permission was scanned during installation by Play Protect and by manual trigger, and the demo app survived both checks on my personal device. It seems you need something else to be deleted, not only the permission.\nStarting from Android 8 you also need the granted by user permission for installation from unknown sources even if you declare REQUEST_INSTALL_PACKAGES in your manifest.\nBy the way, we need some permissions to access storage too. It depends on the APK location and I think it\u0026rsquo;s better to check official documentation regardless of all API changes.\nNote: Some of the official examples don\u0026rsquo;t work as expected due to outdated work with storage permissions. You can check some API Demos on the AOSP emulator.\nAdd to AndroidManifest.xml:\n\u0026lt;uses-permission android:name=\u0026#34;android.permission.REQUEST_INSTALL_PACKAGES\u0026#34; /\u0026gt; When we get all the necessary permissions we allow installation from an Unknown source. What\u0026rsquo;s next? As I mentioned earlier, we have two ways, but now we describe the simplest one - delegate it to OS.\nDelegate it to Android OS - launch intent. Sounds easy, right? And in general, it is. But you know, that\u0026rsquo;s Android SDK with a relatively long history. Android allows you to launch 2 intents to start installation:\nIntent.ACTION_VIEW - yes, this one can install apps too. (I\u0026rsquo;ve seen a mention that this way deprecated too, but I didn\u0026rsquo;t find off sources if you have a link, let me know). This way is the simplest and limited from any configuration point of view. Intent.ACTION_INSTALL_PACKAGE - it is marked deprecated but still works (the latest API that I tested was API 34). In this case, we have more ways to control installations, like marking that app installed not from an unknown source, or installing the same app that exists in the system for another user. Also if you setup the installer name as your app, you can receive bug reports extras by Intent.ACTION_APP_ERROR. fun installByActionInstallPackage(uri: Uri) { Intent().apply { action = Intent.ACTION_INSTALL_PACKAGE setData(uri) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) startActivity(this) } } fun installByViewIntent(uri: Uri) { Intent().apply { action = Intent.ACTION_VIEW // or change mime type to \u0026#34;*/*\u0026#34;, for some reason this works better on newer Android versions setDataAndType(uri, \u0026#34;application/vnd.android.package-archive\u0026#34;) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) startActivity(this) } } That\u0026rsquo;s all code and idea.\nI\u0026rsquo;ve found some discussions on StackOverflow like this when developers shared problems with delegation installation, but it looks more like a problem with access to APK file and storage permissions changes either deprecation.\nIf you are looking for some example:\nvery friendly official example (in Java), but without proper storage access File managers like Fossify File Manager or Amaze File Manager) Firebase App Distribution uses the same approach too. And F-Droid too! Conclusion What we can say about this way?\nIt\u0026rsquo;s extremely simple The configuration is very limited It\u0026rsquo;s outdated in some way and the Android team recommends usage session installation But still helpful if you want to install a simple app.\n","permalink":"https://qwexter.xyz/posts/install_android_app/","summary":"\u003cp\u003eWhile researching how Google Play Integrity API can potentially check the installation source, I found the installation process quite interesting. So I decided to dive into details of how we can install apps and how the operating system (OS) installs them inside. There will be a few parts:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eHow an Android app installation works: Permissions and install by Intent (this article)\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/posts/install_android_app_package_installer/\"\u003eInstallation of an Android app by PackageInstaller\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eWhat\u0026rsquo;s going on inside the OS (coming soon)\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eTo simplify, I suggest imagining a situation: we are working on \u0026ldquo;the App\u0026rdquo;, and one of the requested features is to install an app. I\u0026rsquo;d like to skip the download part and assume the APK is already on the device. We have several ways to solve it:\u003c/p\u003e","title":"How an Android app installation works: Permissions and install by Intent"},{"content":"Intro While I was working at a health tech start-up, I had an unusual dialogue with our mobile apps support manager.\nSupport: Why are we randomly showing users an advertisement?\nMe: Sorry? We don\u0026rsquo;t show anything like that.\nSupport: A user who installed the app a few days ago said they saw an advertisement.\nMe: That\u0026rsquo;s strange. Can you ask which version they\u0026rsquo;re using and where they downloaded it? Is it from the official store?\nThis chat worried me. I explained the situation to the manager and asked for some time for investigation. Later, I received message: \u0026ldquo;The user said that app was installed from a store and app has our app id\u0026rdquo;. There was a short video showing how a user was opening our app with our splash screen, followed by an advertisement for some game\u0026hellip;8 seconds at all.\nDemystifying the Problem The problem had transformed into a kind of mystery. The fact that the user installed the app from the store and got an advertisement bothered us. I can\u0026rsquo;t remember who exactly proposed the idea, but someone reminded us about an alternative store. We checked one. We checked another one. And we got an answer from the user, saying he downloaded the app from a third one.\nSomeone had copied our Play Store page. All setups were made with fake accounts and emails that were difficult to trace. When I began reverse engineering it, I discovered it was a web-view wrapper. The fake app worked on our adaptive website, copied our splash screen and app ID.\nSo, I had spent time researching our internals and dependencies and checking forums. Which was somewhat useless. Or not. At least we confirmed that our original app was functioning correctly.\nIf I had clarified which \u0026ldquo;store,\u0026rdquo; we could have saved a lot of time and effort.\nAsking the right question at the right time is an amazing skill.\nWe knew the problem. What\u0026rsquo;s next? We contacted the app store, I found an advertisement provider, and we contacted them too. But the problem was that we didn\u0026rsquo;t get any answers for a long period of time. And when we did, it was \u0026ldquo;Sorry, but we don\u0026rsquo;t see any problem from our side. If you disagree, you should go to court.\u0026rdquo; So you have variants:\nHire a really good lawyer and make actions - costs are high, require a lot of time Try to reach an agreement with the alt store account holder\u0026hellip; But from my point of view, that\u0026rsquo;s not the best way. do nothing with clone and notify users about right place to load and install your app If possible, review your user agreement and highlight official distribution channels for your apps. However, it\u0026rsquo;s better to consult with a lawyer because this area is regulated differently in various countries.\nPlease note that this information is not legal advice, and the actual legal implications can vary depending on jurisdiction and specific circumstances.\nApp store regulations are complex and relatively new in terms of law. And it\u0026rsquo;s not always clear, even when you work with Google Play. In the case of a less popular app store, it will be even more complicated. There are also many different jurisdictions to consider as well.\nWe got frauded then in pretty simple way, there were much more complex way of attack and there relatively fresh with some statistics post by The Cyber Express.\nIs that all we can do? There are a few ideas:\nReserve your app ID in alt stores, so no one can upload a fraudulent app there with the same ID, and it helps to guide your users. You never know how your business might grow or which app stores might become prominent. This is especially important now, given the various sanctions, bans, and other restrictions in place. Monitoring. At least it helps you react as soon as you find out about a problem and minimize potential damage. A kind of script that checks different stores for your app ID, or parses the HTML page with search results; it can be run on CI/CD and notify you. Or maybe some kind of security service would be a great idea. In that case, if some one distribute (idk why) same app in different way you can use Google play integrity API and it can help you in some cases. Especially if you work on only Google play. If you know of an alternative solution or have had a similar experience, please let me know.\n","permalink":"https://qwexter.xyz/posts/clone_app/","summary":"\u003ch3 id=\"intro\"\u003eIntro\u003c/h3\u003e\n\u003cp\u003eWhile I was working at a health tech start-up, I had an unusual dialogue with our mobile apps support manager.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eSupport\u003c/strong\u003e: Why are we randomly showing users an advertisement?\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMe\u003c/strong\u003e: Sorry? We don\u0026rsquo;t show anything like that.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSupport\u003c/strong\u003e: A user who installed the app a few days ago said they saw an advertisement.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMe\u003c/strong\u003e: That\u0026rsquo;s strange. Can you ask which version they\u0026rsquo;re using and where they downloaded it? Is it from the official store?\u003c/p\u003e","title":"Clone wars (android app edition)"},{"content":"One of the last updates of Google Play Integrity API brought a new feature that allows you to check the installation source and \u0026ldquo;block\u0026rdquo; app navigation and suggest installation from Google Play. The feature forced some discussions in the community and misunderstanding. So let\u0026rsquo;s check what\u0026rsquo;s going on.\nThe new feature allows you to prevent app usage and navigate to Google Play to reinstall the app from the \u0026ldquo;right\u0026rdquo; store. I got two main points from comments and discussions in the community that intrigue me:\nNow we can protect all our apps and check that it\u0026rsquo;s official apps; What if the app was installed from an alt store and we break a user experience? These statements sparked my curiosity, and I started my research. In short: most of the misunderstanding lies in part reading from a lot of sources. We already have the oportunity to check some installation soures and react to the results. The whole blog post shared some parts of my journey, nothing more.\nAbout Google Play Integrity API Google Play Integrity API (docs, client sources) allows you to verify the authenticity of app interactions and the device they\u0026rsquo;re running on. It prevents unauthorized access, abuse, and attacks by checking if requests are coming from the genuine app and device.\nGoogle Play Integrity API acts as a third-party service that provides an authoritative verification mechanism.\nI highly recommend checking one of the latest videos by Android Developers and the official documentation and diagram of work documentation.\nIf we think about this API as a third-party service we can get these intresting and important notes:\nif the Google Play services are outdated or not installed (like some Chinese phones or custom ROMs) your verification will be unsuccessful; you need an internet connection to get proper results too, if your app works with the offline mode you need to solve it by yourself. you and only you have to decide how to react to verdicts (results of checking) in your app, it\u0026rsquo;s your part of the job Let\u0026rsquo;s check the apps check! Let\u0026rsquo;s check the apps check! I focus on the two verdicts (result of checks):\nCheck the appIntegrity field to verify that the user installed it from Google Play - this version exists in the store and is signed with the right certificate Check that the account has the record of the installation of the app and maybe \u0026ldquo;bought\u0026rdquo; it via Google Play payments Both checks provide some data and you as a developer should react to them. So if you decide to show the Google Play screen it\u0026rsquo;s your logic. Consider how this aligns with your offline workflow.\nThese checks help you prevent some stupid and easy-to-do clone apps with repackaging or simple mods. But it requires some work from your side and an understanding of the possible risks. If you think that users don\u0026rsquo;t install strange apps - check one of the latest news about malware.\nThe account verdict\u0026rsquo;s state \u0026ldquo;LICENSED\u0026rdquo; is pretty tricky. You should be careful especially if your system allows subscriptions or buying from different sources (for example iOS or WEB).\nScratch the surface of installation sources In the first drafts this section header was named \u0026ldquo;Deep dive into installation sources\u0026rdquo;. However the whole installation process is pretty complex, so I changed the header.\nThe installation process is complex and contains different steps. The main work does PackageManager (yep, pm from ADB request too) and PackageManagerService (links to source code related to Android 14, but as I remember the general approach was same and for Android 6 too). In general, the whole install process depends on the app that launched this process and some data recorded from the \u0026ldquo;parent\u0026rdquo; into InstallSource class. For example, if you install the app from Google Play there must be com.android.vending, for F-Droid org.fdroid.fdroid. In case when you install the app via ADB or from the android shell it can be null or com.android.shell (AOSP source check)\nBased on this we can check if an app was installed from Google Play. If you search or ask LLM bots you can get a similar code snippet:\nfun Context.getInstallationSource(): String? = runCatching { // For API levels above or equal to 30 (Android 11 and later) if (Build.VERSION.SDK_INT \u0026gt;= Build.VERSION_CODES.R) { packageManager.getInstallSourceInfo(packageName).installingPackageName } else { // For API levels below 30 packageManager.getInstallerPackageName(packageName) } }.getOrNull() It is not so complex, right? But there is a problem. To get this information you need permission: INSTALL_PACKAGES. That is one of the most suspicious permissions for regular apps. Google Play has it.\nBy the way, if you restore some apps from the backup app the install source will be that app too.\nOK, we can\u0026rsquo;t check the installation source like this, maybe we can check signatures on our side and rely on it with our service API calls for example? Technically yes, it\u0026rsquo;s not hard to collect the version, version code, and signing of our apps. But what\u0026rsquo;s the next step? Send it to our API service for checking, right? Can we use some hashing to generate helpful API headers and keys? Yes, we can. But in that case, if this algorithm is exposed (it\u0026rsquo;s not so hard to do) the whole work will be useless. But it\u0026rsquo;s better than nothing and it helps you with simple attacks but requires more complex logic on the back-end.\nConclusion Google Play Integrity API is a helpful tool that can save your company time, money, and reputation. Can we break user experience with this integration? Yep, if we don\u0026rsquo;t think about all cases, especially devices without Google Play services or offline scenarios. Is it a perfect protection? Not really, but it\u0026rsquo;s one of the easiest and universal ways to protect your service from stupid attacks.\nRemember, it\u0026rsquo;s just one layer of security. You still need to implement server-side checks and be prepared for cases where the API might not be available or reliable. And always consider the user experience - don\u0026rsquo;t lock out legitimate users just because they\u0026rsquo;re using a device or method you didn\u0026rsquo;t expect.\nIn the end, it\u0026rsquo;s about finding the right balance between security and usability. The Google Play Integrity API gives you some great tools, but how you use them is up to you. Always think about your specific app, your users, and your security needs when implementing these checks.\n","permalink":"https://qwexter.xyz/posts/app_installation_info/","summary":"\u003cp\u003eOne of the last updates of Google Play Integrity API brought a new feature that allows you to check the installation source and \u0026ldquo;block\u0026rdquo; app navigation and suggest installation from Google Play. The feature forced some discussions in the community and misunderstanding.\nSo let\u0026rsquo;s check what\u0026rsquo;s going on.\u003c/p\u003e\n\u003cp\u003eThe new feature allows you to prevent app usage and navigate to Google Play to reinstall the app from the \u0026ldquo;right\u0026rdquo; store.\nI got two main points from comments and discussions in the community that intrigue me:\u003c/p\u003e","title":"Google Play Integrity API and installation sources checks"},{"content":"Hi! In the last few weeks, I went through several technical interviews for an Android developer position, and one of the recurring questions was, \u0026ldquo;What are memory leaks? How can we create them? How can we find them?\u0026rdquo;. I found that some developers don\u0026rsquo;t know about the other side of lateinit var usage with Fragments and how easily it can cause leaks.\nIn short: the lifecycle of fragments and views is different. When our fragments go to the back stack, the view should release memory, but if we use fragment-level fields with lateinit var, we keep a reference, and GC can\u0026rsquo;t clear outdated objects.\nThis issue occurs with old-school findViewById and ViewBinding cases. For example, let\u0026rsquo;s create a simple fragment:\nclass IncrementFragment : Fragment(R.layout.fragment_increment) { private lateinit var infoTextView: TextView private lateinit var buttonView: Button override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val layerNumber = arguments?.getInt(NUMBER_KEY) ?: 0 infoTextView = view.findViewById(R.id.info_text_view) buttonView = view.findViewById(R.id.add_fragment_button) bindViews(layerNumber) } // here we bind data and setup views and click listeners private fun bindViews(layerNumber: Int) { infoTextView.text = \u0026#34;Current fragment is $layerNumber\u0026#34; val newLayerNumber = layerNumber + 1 buttonView.text = \u0026#34;Add new layer ($newLayerNumber?)\u0026#34; buttonView.setOnClickListener { navigate(newLayerNumber) } } // here we just replace one fragment to new with parentFragmentManager private fun navigate(newLayerNumber: Int) { parentFragmentManager.beginTransaction() .replace( R.id.main_container, IncrementFragment::class.java, createArgs(newLayerNumber), \u0026#34;ADD_NUMBER_$newLayerNumber\u0026#34; ) .addToBackStack(\u0026#34;ADD_NUMBER_$newLayerNumber\u0026#34;) .commit() } } If we run this example and add some fragments to the back stack (yes, I added a lot of fragments to the stack), then capture a heap dump, we can see something like this:\nHere we can find records about our fragments in the stack, and if we look deeper, we can see that most back stacked instances have a similar size and contain active links to fragment-level view fields referencing old views:\nIf we look at the visible fragment, we can see an actual reference to mView:\nIf we go back through the fragment stack, we recreate a new instance of the view, find new views, and store references to fragment-level fields. It seems like, right now, GC can release old memories, right? In my example, yes. But sometimes we can\u0026rsquo;t release memories from views because we have callbacks (like OnClickListener, LiftOnScrollListener). We probably can find some troubles and should pay attention. Kotlin has some strategies on how to build classes and lambdas better to prevent memory leaks. You can start with Yangwei\u0026rsquo;s notes about lambdas and don\u0026rsquo;t forget to occasionally check the decompiled code (Tools→Kotlin→Show Kotlin Bytecode and look there or press Decompile).\nA few final points:\nIf we look at some of the screenshots above, we can see that they indicate \u0026ldquo;0 Leaks\u0026rdquo;. However, this is not true. If we run LeakCanary, we get notifications about memory leaks. I think using both methods is better. Don\u0026rsquo;t use lateinit var with fragment UI - if you can\u0026rsquo;t nullify references to fragment-level fields, you get a memory leak (Google mentioned this). If you use viewBinding, check some delegates or write your own (you can work with plain views in a similar way). Some times I hear something like “if we use lateinit var we can avoid a lot of null-checks and save time and app size” — 50/50, you don’t write some checks by yourself, but compiler add checks for every scope where variable will be used. For example, decompiled code of bindData is what you can see early: private final void bindData(int layerNumber) { AppCompatTextView var10000 = this.infoTextView; if (var10000 == null) { Intrinsics.throwUninitializedPropertyAccessException(\u0026#34;infoTextView\u0026#34;); } var10000.setText((CharSequence)(\u0026#34;Current fragment is \u0026#34; + layerNumber)); final int newLayerNumber = layerNumber + 1; AppCompatButton var3 = this.buttonView; if (var3 == null) { Intrinsics.throwUninitializedPropertyAccessException(\u0026#34;buttonView\u0026#34;); } // ... } Here few helpful articles about memory leaks:\nDetecting memory leaks in Android applications by Lily Chen — popular leaks. Stressing memory on Android by Amanjeet Singh Let your delegates auto-nullify references**☠️ - by Shreyas Patil ","permalink":"https://qwexter.xyz/posts/lateinit_var/","summary":"\u003cp\u003eHi! In the last few weeks, I went through several technical interviews for an Android developer position, and one of the recurring questions was, \u0026ldquo;What are memory leaks? How can we create them? How can we find them?\u0026rdquo;. I found that some developers don\u0026rsquo;t know about the other side of \u003ccode\u003elateinit var\u003c/code\u003e usage with Fragments and how easily it can cause leaks.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIn short: the lifecycle of fragments and views is different. When our fragments go to the back stack, the view should release memory, but if we use fragment-level fields with \u003ccode\u003elateinit var\u003c/code\u003e, we keep a reference, and GC can\u0026rsquo;t clear outdated objects.\u003c/p\u003e","title":"How lateinit steal app memory."}]