{"id":334,"date":"2022-03-09T07:31:16","date_gmt":"2022-03-09T07:31:16","guid":{"rendered":"https:\/\/boochlin.com\/?p=334"},"modified":"2026-09-19T18:04:21","modified_gmt":"2026-09-19T18:04:21","slug":"funny-vote-app-mvp-kotlin-%e6%94%b9%e7%89%88%e9%9b%9c%e8%a8%98","status":"publish","type":"post","link":"https:\/\/boochlin.com\/?p=334","title":{"rendered":"Funny vote APP -MVP Kotlin \u6539\u7248\u96dc\u8a18"},"content":{"rendered":"<p><img decoding=\"async\" src=\"..\/..\/assets\/images\/post320_funny_vote_banner.png\" alt=\"Funny Vote \u8da3\u6295\u7968 Kotlin \u6539\u7248\u65d7\u8266\u5c01\u9762\"><\/p>\n<p>\u8ddd\u96e2\u4e0a\u4e00\u7bc7\u804a MVP \u89e3\u8026\u8207\u55ae\u5143\u6e2c\u8a66\uff0c\u4e0d\u77e5\u4e0d\u89ba\u53c8\u904e\u4e86\u5e7e\u500b\u6708\u3002\u672c\u4f86\u5728\u7b2c\u4e8c\u7bc7\u6700\u5f8c\u4fe1\u8a93\u65e6\u65e6\u8aaa\u8981\u628a Kotlin \u7248\u672c\u653e\u5230\u4e0b\u4e00\u7bc7\uff0c\u7d50\u679c\u4e00\u958b\u5de5\u4f4d\u624d\u767c\u73fe\uff0c\u6b20\u4e0b\u7684\u6280\u8853\u50b5\u679c\u7136\u9023\u672c\u5e36\u5229\u90fd\u8981\u9084\u3002<\/p>\n<p>2018 \u5e74\u7684\u79cb\u5929\uff0cGoogle I\/O \u5b98\u65b9\u628a Kotlin \u6276\u6b63\u70ba Android \u4e00\u7d1a\u516c\u6c11\u7684\u72c2\u71b1\u525b\u5e2d\u6372\u6574\u500b\u5708\u5b50\u3002\u7576\u6642\u770b\u8457\u5c08\u6848\u88e1\u516b\u5341\u5e7e\u500b Java \u985e\u5225\uff0c\u6bcf\u5929\u88ab\u7121\u7aae\u7121\u76e1\u7684 Getter\/Setter\u3001\u7a7a\u6307\u6a19\u6aa2\u67e5\u8207 ButterKnife \u641e\u5f97\u5fc3\u529b\u4ea4\u7601\uff0c\u6211\u5fc3\u88e1\u53ea\u6709\u4e00\u500b\u5ff5\u982d\uff1a\u300c\u65e2\u7136\u5b98\u65b9\u80cc\u66f8\u4e86\uff0c\u90a3\u6211\u5c31\u958b\u500b <code>mvp_kotlin<\/code> \u5206\u652f\uff0c\u4e00\u9375\u628a Java \u8f49\u6210 Kotlin\uff0c\u5c11\u5beb\u5e7e\u5343\u884c\u6a23\u677f\u4ee3\u78bc\u8eba\u5e73\u4e0d\u597d\u55ce\uff1f\u300d<\/p>\n<p>\uff08\u5f8c\u4f86\u7684\u8840\u6dda\u8b49\u660e\uff1a\u4eba\u53ea\u8981\u4e00\u6025\u8457\u60f3\u5077\u61f6\uff0c\u901a\u5e38\u90fd\u6703\u8e29\u9032\u66f4\u6df1\u7684\u6c34\u6ce5\u5751\u88e1\u3002\uff09<\/p>\n<p>\u9019\u7bc7\u6587\u7ae0\u4e0d\u6703\u6559\u4f60\u4ec0\u9ebc\u5ba3\u544a\u8b8a\u6578\u3001\u8ff4\u5708\u8a9e\u6cd5\u7cd6\uff0c\u90a3\u4e9b\u770b\u5b98\u65b9\u6587\u4ef6\u5341\u5206\u9418\u5c31\u61c2\u3002\u9019\u7bc7\u7d14\u7cb9\u662f\u5728 2018 \u5e74 9 \u6708 3 \u65e5\u5230 9 \u6708 5 \u65e5\u51cc\u6668\uff0c\u6211\u89aa\u624b\u628a\u6574\u500b\u5c08\u6848 80+ \u985e\u5225\u5168\u8f49\u6210 Kotlin 1.2 \u6642\uff0c\u649e\u4e0a\u7684\u771f\u5be6\u4fee\u7f85\u5834\u8207\u5e95\u5c64 Bytecode \u8e29\u5751\u56de\u9867\u3002<\/p>\n<p>\u7576\u5e74\u6298\u9a30\u7684\u5148\u92d2\u4ee3\u78bc\uff0c\u4f9d\u7136\u5b8c\u6574\u4fdd\u5b58\u5728 GitHub \u4e0a\u4f9b\u5927\u5bb6\u8003\u53e4\uff1a<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/boochlin06\/FunnyVote\/tree\/mvp_kotlin\">Funny Vote MVP_Kotlin \u6b77\u53f2\u5148\u92d2\u5206\u652f<\/a><\/li>\n<\/ul>\n<hr>\n<h2>1. \u865b\u5047\u7e41\u69ae\uff1aPOJO \u84b8\u767c 80% \u8207 Synthetics \u521d\u9ad4\u9a57<\/h2>\n<p>\u525b\u958b\u59cb\u8f49\u7684\u90a3\u5169\u5929\uff0c\u723d\u5ea6\u78ba\u5be6\u9ad8\u5230\u8b93\u4eba\u98c4\u98c4\u7136\u3002<\/p>\n<p>\u6700\u76f4\u63a5\u7684\u6536\u7a6b\u662f <strong>POJO \u6a23\u677f\u5927\u6e05\u6d17<\/strong>\u3002\u4ee5\u524d\u5beb\u500b <code>VoteData<\/code>\uff0c\u5169\u767e\u884c\u4ee3\u78bc\u88e1\u6709\u516b\u6210\u662f\u5728\u624b\u6572 Getter\u3001Setter\u3001<code>equals()<\/code>\u3001<code>hashCode()<\/code>\uff1a<\/p>\n<pre><code class=\"hljs language-kotlin\"><span class=\"hljs-comment\">\/\/ \u56db\u884c\u641e\u5b9a\u5169\u767e\u884c Java \u6a23\u677f\uff0c\u81ea\u52d5\u8d08\u9001 copy(), toString(), componentN()<\/span>\n<span class=\"hljs-keyword\">data<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title class_\">VoteData<\/span>(\n    <span class=\"hljs-keyword\">val<\/span> voteKey: String,\n    <span class=\"hljs-keyword\">val<\/span> title: String,\n    <span class=\"hljs-keyword\">val<\/span> isMultiChoice: <span class=\"hljs-built_in\">Boolean<\/span> = <span class=\"hljs-literal\">false<\/span>,\n    <span class=\"hljs-keyword\">val<\/span> maxOptions: <span class=\"hljs-built_in\">Int<\/span> = <span class=\"hljs-number\">1<\/span>\n)<\/code><\/pre>\n<p>\u4e7e\u6de8\u4fd0\u843d\uff0c\u5f37\u8feb\u75c7\u7576\u5834\u75ca\u7652\u3002<\/p>\n<p>\u63a5\u8457\u662f\u7576\u5e74\u865f\u7a31\u300cButterKnife \u7d42\u7d50\u8005\u300d\u7684 <strong>Kotlin Android Extensions (Kotlin Synthetics)<\/strong>\u3002\u5728 <code>build.gradle<\/code> \u52a0\u4e0a\u5916\u639b\uff0c\u5728 Activity \u88e1\u76f4\u63a5 import XML \u4f48\u5c40\uff0c\u5143\u4ef6 ID \u7576\u5834\u8b8a\u6210\u8b8a\u6578\u64cd\u4f5c\uff1a<\/p>\n<pre><code class=\"hljs language-kotlin\"><span class=\"hljs-comment\">\/\/ \u544a\u5225 findViewById \u8207 @BindView\uff0c\u76f4\u63a5\u62ff XML ID \u7576\u8b8a\u6578\u64cd\u4f5c<\/span>\n<span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">onCreate<\/span><span class=\"hljs-params\">(savedInstanceState: <span class=\"hljs-type\">Bundle<\/span>?)<\/span><\/span> {\n    <span class=\"hljs-keyword\">super<\/span>.onCreate(savedInstanceState)\n    setContentView(R.layout.activity_main)\n    \n    txtTitle.text = <span class=\"hljs-string\">&quot;Funny Vote \u8da3\u6295\u7968&quot;<\/span>\n    btnSubmit.setOnClickListener { doSubmit() }\n}<\/code><\/pre>\n<p>\u770b\u8457\u6eff\u5751\u6eff\u8c37\u7684 <code>@BindView<\/code> \u88ab\u6574\u6279\u4e1f\u9032\u5783\u573e\u6876\uff0c\u6211\u5750\u5728\u6905\u5b50\u4e0a\u50bb\u7b11\uff0c\u5fc3\u60f3\u9019\u8a9e\u6cd5\u7cd6\u4e5f\u592a\u751c\u4e86\u5427\u3002<\/p>\n<p>\u7136\u800c\uff0c\u751c\u871c\u901a\u5e38\u7dad\u6301\u4e0d\u904e 48 \u5c0f\u6642\u3002<\/p>\n<hr>\n<h2>2. \u7576\u5834\u6253\u81c9\uff1aSynthetics \u7684 include \u5e7d\u9748\u8207\u96b1\u853d NPE<\/h2>\n<p>\u7576\u6211\u628a\u6295\u7968\u8a73\u60c5\u9801 <code>VoteDetailContentActivity<\/code> \u8f49\u6210 Kotlin \u5f8c\uff0c\u771f\u6a5f\u4e0a\u4e00\u8dd1\uff0c\u9ede\u64ca\u8df3\u8f49\u7acb\u523b\u4e1f\u51fa <code>NullPointerException<\/code> \u7576\u5834\u66b4\u6583\uff01<\/p>\n<p>\u7576\u6642\u6211\u6eff\u8166\u5b50\u9ed1\u4eba\u554f\u865f\uff1a<strong>\u300cKotlin \u4e0d\u662f\u5ba3\u7a31\u7a7a\u5b89\u5168\uff08Null Safety\uff09\u55ce\uff1f\u6211\u578b\u5225\u90fd\u5beb\u6210\u975e\u7a7a\u4e86\uff0c\u9019 NPE \u5230\u5e95\u54ea\u4f86\u7684\uff1f\u300d<\/strong><\/p>\n<p>\u7ffb\u958b\u6211\u7576\u5e74\u8a18\u4e8b\u672c\u7684\u7b2c\u4e00\u884c\uff0c\u7d00\u9304\u8457\u9019\u6bb5\u932f\u5b57\u9023\u7bc7\u7684\u5d29\u6f70\u54c0\u568e\uff1a<br \/>\n<code>android kotlin extension include xml with id name with id \u611f\u89ba\u5f88\u50cf\u85e5\u4e0d\u80fd\u6709include id \u624d\u884c<\/code><\/p>\n<pre><code class=\"hljs language-plaintext\">[VoteDetail \u5de2\u72c0\u4f48\u5c40\u968e\u5c64]\nactivity_vote_detail.xml\n \u251c\u2500\u2500 Toolbar (id: toolbar)\n \u251c\u2500\u2500 ViewPager (id: viewPager)\n \u2514\u2500\u2500 &lt;include layout=\"@layout\/include_author\" android:id=\"@+id\/include_author\" \/&gt;\n       \u2514\u2500\u2500 (include_author.xml \u5167\u90e8\u53c8\u6709\u4e00\u500b id \u70ba imgAvatar \u7684\u5143\u4ef6)<\/code><\/pre>\n<h3>\u5e95\u5c64\u662f\u600e\u9ebc\u80cc\u523a\u6211\u7684\uff1f<\/h3>\n<p>\u51b7\u975c\u4e0b\u4f86\u53cd\u7de8\u8b6f Kotlin Synthetics \u7522\u51fa\u7684 Java Bytecode\uff0c\u771f\u76f8\u624d\u6d6e\u51fa\u6c34\u9762\u3002<\/p>\n<p>Synthetics \u5728 Activity \u80cc\u5f8c\u5077\u5077\u585e\u4e86\u4e00\u500b\u5feb\u53d6\u67e5\u8868\u5b57\u5178\uff1a<\/p>\n<pre><code class=\"hljs language-java\"><span class=\"hljs-comment\">\/\/ Kotlin \u7de8\u8b6f\u5668\u80cc\u5f8c\u5077\u5e79\u7684\u9ad2\u6d3b<\/span>\n<span class=\"hljs-keyword\">private<\/span> HashMap _$_findViewCache;\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-type\">View<\/span> <span class=\"hljs-title function_\">_$_findCachedViewById<\/span><span class=\"hljs-params\">(<span class=\"hljs-type\">int<\/span> id)<\/span> {\n    <span class=\"hljs-keyword\">if<\/span> (_$_findViewCache == <span class=\"hljs-literal\">null<\/span>) _$_findViewCache = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-title class_\">HashMap<\/span>();\n    <span class=\"hljs-type\">View<\/span> <span class=\"hljs-variable\">view<\/span> <span class=\"hljs-operator\">=<\/span> (View) _$_findViewCache.get(id);\n    <span class=\"hljs-keyword\">if<\/span> (view == <span class=\"hljs-literal\">null<\/span>) {\n        view = findViewById(id);\n        _$_findViewCache.put(id, view);\n    }\n    <span class=\"hljs-keyword\">return<\/span> view;\n}<\/code><\/pre>\n<p>\u7576\u4f60\u4f7f\u7528 <code>&lt;include&gt;<\/code> \u6a19\u7c64\uff0c\u4e26\u4e14\u540c\u6642\u5728 <code>&lt;include&gt;<\/code> \u8207\u5167\u90e8\u6839\u8996\u5716\u5ba3\u544a\u4e86 ID \u6642\uff0cSynthetics \u751f\u6210\u7684\u975c\u614b\u5c6c\u6027\u89e3\u6790\u51fa\u73fe\u4e86\u56b4\u91cd\u7684\u6b67\u7570\uff08Ambiguity\uff09\u2014\u2014\u5b83\u76f4\u63a5\u5f9e\u5916\u5c64 Activity \u5617\u8a66 <code>findViewById(R.id.imgAvatar)<\/code>\uff0c\u4f46\u8a72\u5143\u4ef6\u88ab\u5305\u5728\u5b50\u4f48\u5c40\u5167\u90e8\uff0c\u5916\u5c64\u67e5\u8868\u76f4\u63a5\u56de\u50b3 <code>null<\/code>\uff01<\/p>\n<p>\u66f4\u6bd2\u7684\u662f\uff0cKotlin \u7684\u975e\u7a7a\u5ba3\u544a\u5728\u7de8\u8b6f\u671f\u76f8\u4fe1\u4e86 Synthetics \u7684\u5916\u639b\u4fdd\u8b49\uff0c\u7d50\u679c\u5728 Runtime \u62ff\u5230 <code>null<\/code> \u547c\u53eb\u65b9\u6cd5\uff0c\u539f\u5730\u62cb\u51fa NPE\uff01<\/p>\n<p>\u9019\u500b\u5751\u72e0\u72e0\u6253\u4e86\u6211\u4e00\u5df4\u638c\uff1a<strong>\u4e0d\u8981\u4ee5\u70ba\u8a9e\u6cd5\u7cd6\u5305\u5f97\u5f88\u751c\uff0c\u5e95\u5c64\u7684 View \u968e\u5c64\u67e5\u627e\u6a5f\u5236\u5c31\u4e0d\u5b58\u5728\u4e86\u3002<\/strong><\/p>\n<hr>\n<h2>3. \u6838\u5fc3\u6230\u5f79\uff1a\u6e2c\u8a66\u5168\u6ec5\u2014\u2014\u7576 Kotlin \u9810\u8a2d final \u649e\u7ffb Mockito<\/h2>\n<p>\u89e3\u6c7a\u4e86 UI \u4f48\u5c40\u7684\u5c0f\u6253\u5c0f\u9b27\uff0c\u771f\u6b63\u81f4\u547d\u7684\u6838\u5f48\u5728\u7b2c\u4e09\u5929\u51cc\u6668\u4e09\u9ede\u5f15\u7206\uff1a<strong>\u55ae\u5143\u6e2c\u8a66\u5168\u6ec5<\/strong>\u3002<\/p>\n<p>\u5728\u7b2c\u4e8c\u7bc7\u88e1\uff0c\u6211\u5927\u8a00\u4e0d\u615a\u5730\u5beb\u4e0b\u4e86\u300c\u67b6\u69cb\u89e3\u8026\u5f8c\u52d9\u5fc5\u88dc\u9f4a\u55ae\u5143\u6e2c\u8a66\u300d\u3002\u7d50\u679c\u7576\u6211\u628a Presenter \u8f49\u6210 Kotlin \u5f8c\uff0c\u5728\u7d42\u7aef\u6a5f\u6572\u4e0b <code>.\/gradlew testDebugUnitTest<\/code> \u7684\u90a3\u4e00\u79d2\uff0c\u63a7\u5236\u53f0\u76f4\u63a5\u70b8\u51fa\u4e00\u7247\u8840\u7d05\uff1a<\/p>\n<p><strong>\u6e2c\u8a66\u6848\u4f8b\u5168\u90e8\u5931\u6557\uff0c\u4e00\u500b\u7da0\u71c8\u90fd\u6c92\u6709\u3002<\/strong><\/p>\n<h3>\u6bd2\u6bba Mockito \u7684\u5169\u628a\u6697\u7bad<\/h3>\n<p>\u8ffd\u9032\u5806\u758a\u65e5\u8a8c\uff0c\u6211\u6293\u51fa\u4e86\u5169\u9846\u81f4\u547d\u7684\u8de8\u8a9e\u8a00\u5730\u96f7\uff1a<\/p>\n<h4>\u6697\u7bad\u4e00\uff1aKotlin \u985e\u5225\u9810\u8a2d\u5168\u70ba final<\/h4>\n<p>\u5728 Java \u7684\u4e16\u754c\u88e1\uff0c\u6240\u6709 class \u9810\u8a2d\u90fd\u662f <code>open<\/code> \u7684\uff08\u9664\u975e\u986f\u5f0f\u52a0\u4e0a <code>final<\/code>\uff09\u3002\u4f46 Kotlin \u70ba\u4e86\u6548\u80fd\u8207\u5b89\u5168\uff0c<strong>\u6240\u6709\u985e\u5225\u8207\u65b9\u6cd5\u9810\u8a2d\u5168\u90fd\u662f <code>final<\/code>\uff01<\/strong><br \/>\n\u800c 2018 \u5e74\u4e3b\u6d41\u7684 Mockito 1.x \/ 2.x \u65e9\u671f\u7248\u672c\u5e95\u5c64\u662f\u9760 CGLIB \u6216 Byte Buddy \u751f\u6210\u5b50\u985e\u5225\u4f86\u505a\u52d5\u614b\u4ee3\u7406\u7684\u3002Java \u898f\u7bc4\u660e\u5b9a\u7121\u6cd5\u7e7c\u627f final \u985e\u5225\uff0cMockito \u76f4\u63a5\u5674\u51fa\uff1a<br \/>\n<code>Cannot mock\/spy class UserPresenter because it is final!<\/code><\/p>\n<h4>\u6697\u7bad\u4e8c\uff1aIntrinsics Bytecode \u975e\u7a7a\u63d2\u6a01<\/h4>\n<p>\u5728\u55ae\u5143\u6e2c\u8a66\u4e2d\uff0c\u6211\u5011\u7fd2\u6163\u9019\u6a23\u5beb\uff1a<\/p>\n<pre><code class=\"hljs language-kotlin\">verify(mockView).showUser(Mockito.any())<\/code><\/pre>\n<p>\u4f46 <code>Mockito.any()<\/code> \u662f Java \u5beb\u7684\u6cdb\u578b\u65b9\u6cd5\uff0c\u5728\u57f7\u884c\u968e\u6bb5\u5b83\u7684\u5e95\u5c64\u5be6\u4f5c\u5c31\u662f\u76f4\u63a5 <code>return null<\/code>\u3002<br \/>\n\u800c\u5728 Kotlin \u7de8\u8b6f\u5668\u773c\u88e1\uff0c\u5982\u679c\u4f60\u7684\u4ecb\u9762\u53c3\u6578\u5ba3\u544a\u70ba\u975e\u7a7a\uff1a<\/p>\n<pre><code class=\"hljs language-kotlin\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">showUser<\/span><span class=\"hljs-params\">(user: <span class=\"hljs-type\">UserData<\/span>)<\/span><\/span> <span class=\"hljs-comment\">\/\/ \u975e\u7a7a\u578b\u5225<\/span><\/code><\/pre>\n<p>Kotlin \u7de8\u8b6f\u5668\u6703\u5728\u8a72\u65b9\u6cd5\u7684<strong>\u7b2c\u4e00\u884c\u6307\u4ee4\u81ea\u52d5\u63d2\u5165\u9019\u6bb5 Bytecode<\/strong>\uff1a<\/p>\n<pre><code class=\"hljs language-java\">Intrinsics.checkParameterIsNotNull(user, <span class=\"hljs-string\">&quot;user&quot;<\/span>);<\/code><\/pre>\n<p>\u7576 <code>Mockito.any()<\/code> \u5410\u51fa <code>null<\/code> \u50b3\u9032\u53bb\u7684\u90a3\u4e00\u77ac\u9593\uff0c\u9019\u884c\u6aa2\u67e5\u7576\u5834\u66b4\u6012\uff0c\u76f4\u63a5\u5674\u51fa <code>IllegalArgumentException: Parameter specified as non-null is null: method showUser, parameter user<\/code>\uff01<\/p>\n<h3>\u51cc\u6668\u4e09\u9ede\u534a\u7684\u6551\u8d16\u4e4b\u9053<\/h3>\n<p>\u5728 2018\/9\/5 \u51cc\u6668 03:34 \u7684\u771f\u5be6 commit <code>0853e4b<\/code> \u4e2d\uff0c\u6211\u7528\u5169\u5957\u7d44\u5408\u62f3\u5fb9\u5e95\u64fa\u5e73\u4e86\u9019\u500b\u6b7b\u5c40\uff1a<\/p>\n<h4>\u7b2c\u4e00\u62f3\uff1a\u555f\u7528\u5be6\u9a57\u6027\u7684 mock-maker-inline<\/h4>\n<p>\u5728 <code>app\/src\/test\/resources\/mockito-extensions\/org.mockito.plugins.MockMaker<\/code> \u5efa\u7acb\u914d\u7f6e\u6a94\uff0c\u5beb\u5165\u4e00\u884c\u5ba3\u544a\uff1a<\/p>\n<pre><code class=\"hljs language-plaintext\">mock-maker-inline<\/code><\/pre>\n<p>\u5f37\u5236 Mockito \u5207\u63db\u5230\u5e95\u5c64 Java Agent Bytecode \u63d2\u6a01\uff08Instrumentation\uff09\u5f15\u64ce\uff0c\u7e5e\u904e\u5b50\u985e\u7e7c\u627f\u9650\u5236\uff0c\u5be6\u73fe\u5c0d final class \u7684\u76f4\u63a5 mock\u3002<\/p>\n<h4>\u7b2c\u4e8c\u62f3\uff1a\u624b\u523b\u6cdb\u578b\u6551\u547d\u5de5\u5177\u6b3a\u9a19\u7de8\u8b6f\u5668<\/h4>\n<p>\u5728 <code>src\/test\/java\/com\/heaton\/funnyvote\/MockitoKotlinHelpers.kt<\/code> \u4e2d\uff0c\u6211\u89aa\u624b\u523b\u4e0b\u4e86\u9019\u7d44\u5229\u7528 Kotlin \u6cdb\u578b\u578b\u5225\u64e6\u9664\uff08Type Erasure\uff09\u7684\u6551\u547d\u5de5\u5177\u51fd\u6578\uff1a<\/p>\n<pre><code class=\"hljs language-kotlin\"><span class=\"hljs-comment\">\/\/ 2018 \u5e74\u5728 MockitoKotlinHelpers.kt \u88e1\u89aa\u624b\u5beb\u4e0b\u7684 Workaround\uff1a<\/span>\n<span class=\"hljs-comment\">\/\/ \u5229\u7528\u96b1\u5f0f\u7e7c\u627f Any? \u7684\u6cdb\u578b\u4e0a\u9650\uff0c\u9a19\u904e\u7de8\u8b6f\u5668\u7684 Intrinsics.checkParameterIsNotNull \u63d2\u6a01<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-type\">&lt;T&gt;<\/span> <span class=\"hljs-title\">any<\/span><span class=\"hljs-params\">()<\/span><\/span>: T = Mockito.any&lt;T&gt;()\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-type\">&lt;T&gt;<\/span> <span class=\"hljs-title\">eq<\/span><span class=\"hljs-params\">(obj: <span class=\"hljs-type\">T<\/span>)<\/span><\/span>: T = Mockito.eq&lt;T&gt;(obj)\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-type\">&lt;T&gt;<\/span> <span class=\"hljs-title\">capture<\/span><span class=\"hljs-params\">(argumentCaptor: <span class=\"hljs-type\">ArgumentCaptor<\/span>&lt;<span class=\"hljs-type\">T<\/span>&gt;)<\/span><\/span>: T = argumentCaptor.capture()<\/code><\/pre>\n<p>\u628a\u6e2c\u8a66\u4ee3\u78bc\u88e1\u7684 <code>Mockito.any()<\/code> \u5168\u90e8\u66ff\u63db\u70ba\u81ea\u5df1\u5beb\u7684 <code>any()<\/code>\uff0c\u7d42\u7aef\u6a5f\u88e1\u7684\u6e2c\u8a66\u7d05\u5b57\u77ac\u9593\u5168\u6578\u6d88\u9000\uff0c\u7da0\u71c8\u5237\u5237\u5237\u4eae\u8d77\u3002<\/p>\n<p>\u770b\u8457\u63a7\u5236\u53f0\u5410\u51fa <code>BUILD SUCCESSFUL<\/code>\uff0c\u5916\u9762\u5929\u525b\u597d\u77c7\u77c7\u4eae\u3002\u90a3\u7a2e\u6478\u9032 JVM \u6c34\u7ba1\u6df1\u8655\u628a Bug \u64f0\u6b7b\u7684\u5feb\u611f\uff0c\u662f\u4efb\u4f55\u9ad8\u968e\u6846\u67b6\u90fd\u7d66\u4e0d\u4e86\u7684\u3002<\/p>\n<hr>\n<h2>4. \u975e\u540c\u6b65\u904e\u6e21\u671f\u7684\u6298\u8877\uff1a\u6c92\u6709\u5354\u7a0b\u7684\u8352\u539f\u6642\u4ee3<\/h2>\n<p>\u641e\u5b9a\u55ae\u5143\u6e2c\u8a66\u5f8c\uff0c\u6700\u5f8c\u4e00\u500b\u5de5\u7a0b\u6289\u64c7\u662f\uff1a<strong>\u975e\u540c\u6b65\u4efb\u52d9\u600e\u9ebc\u8fa6\uff1f<\/strong><\/p>\n<p>\u5728 2018 \u5e74\uff0cAndroid \u525b\u5ee2\u68c4\u4e86\u9673\u820a\u7684 <code>AsyncTask<\/code>\u3002\u800c\u50b3\u8aaa\u4e2d\u7684 Kotlin Coroutines\uff08\u5354\u7a0b\uff09\u5728\u7576\u6642\u7684 1.2 \u7248\u672c\u9084\u9802\u8457\u5927\u5927\u7684 <code>Experimental<\/code> \u8b66\u544a\u6a19\u7c64\uff0c\u9023 <code>viewModelScope<\/code> \u90fd\u9084\u6c92\u51fa\u751f\u3002\u5728\u7dda\u4e0a\u7522\u54c1\u88e1\u76f4\u63a5\u4e0a\u5be6\u9a57\u6027\u67b6\u69cb\uff0c\u9664\u975e\u4f60\u60f3\u534a\u591c\u88ab\u8001\u95c6\u96fb\u8a71\u53eb\u9192\u3002<\/p>\n<p>\u4f60\u8aaa\u70ba\u4ec0\u9ebc\u4e0d\u5168\u9762\u5f15\u9032 RxJava\uff1f<br \/>\n\u6b63\u5982\u6211\u5728\u7b2c\u4e8c\u7bc7\u8aaa\u904e\u7684\uff0cFunny Vote \u53ea\u662f\u500b\u4e2d\u5c0f\u578b App\uff0c\u70ba\u4e86\u5e7e\u500b\u7c21\u55ae\u7684\u672c\u5730\u8cc7\u6599\u5eab\u67e5\u8a62\u8207 API \u8acb\u6c42\uff0c\u628a\u6574\u500b\u5c08\u6848\u7684\u4ecb\u9762\u5168\u90e8\u6539\u5beb\u6210 <code>Observable<\/code> \/ <code>Single<\/code>\uff0c\u9084\u8981\u5718\u968a\u6bcf\u500b\u4eba\u53bb\u641e\u61c2 Schedulers \u548c\u80cc\u58d3\uff0c\u5be6\u5728\u662f\u6709\u9ede\u300c\u96de\u817f\u6bd4\u61f6\u89ba\u300d\u7684\u904e\u5ea6\u8a2d\u8a08\u3002<\/p>\n<p>\u6700\u5f8c\u6211\u63a1\u53d6\u7684\u52d9\u5be6\u59a5\u5354\uff0c\u662f\u5728\u5c08\u6848\u88e1\u523b\u4e86\u4e00\u500b\u6975\u5ea6\u8f15\u91cf\u7684\u96d9\u7dda\u7a0b\u6c60\u8abf\u5ea6\u5668 <code>AppExecutors.kt<\/code>\uff1a<\/p>\n<pre><code class=\"hljs language-kotlin\"><span class=\"hljs-comment\">\/\/ 2018 \u5e74\u7684\u8f15\u91cf\u8abf\u5ea6\u5668\uff1a\u78c1\u789f\/\u7db2\u8def I\/O + \u4e3b\u57f7\u884c\u7dd2<\/span>\n<span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title class_\">AppExecutors<\/span>(\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">val<\/span> diskIO: Executor = Executors.newSingleThreadExecutor(),\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">val<\/span> networkIO: Executor = Executors.newFixedThreadPool(<span class=\"hljs-number\">3<\/span>),\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">val<\/span> mainThread: Executor = MainThreadExecutor()\n) {\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">diskIO<\/span><span class=\"hljs-params\">()<\/span><\/span>: Executor = diskIO\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">networkIO<\/span><span class=\"hljs-params\">()<\/span><\/span>: Executor = networkIO\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">mainThread<\/span><span class=\"hljs-params\">()<\/span><\/span>: Executor = mainThread\n\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title class_\">MainThreadExecutor<\/span> : <span class=\"hljs-type\">Executor<\/span> {\n        <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">val<\/span> mainThreadHandler = Handler(Looper.getMainLooper())\n        <span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">execute<\/span><span class=\"hljs-params\">(command: <span class=\"hljs-type\">Runnable<\/span>)<\/span><\/span> {\n            mainThreadHandler.post(command)\n        }\n    }\n}<\/code><\/pre>\n<p>\u642d\u914d Kotlin \u7684 <strong>Trailing Lambda\uff08\u5c3e\u96a8\u9589\u5305\uff09<\/strong> \u8a9e\u6cd5\u7cd6\uff0cPresenter \u88e1\u539f\u672c\u5197\u9577\u919c\u964b\u7684\u533f\u540d\u985e\u5225\u56de\u547c\uff0c\u77ac\u9593\u8b8a\u6210\u4e86\u4e7e\u6de8\u6d41\u66a2\u7684\u975e\u540c\u6b65\u8abf\u5ea6\uff1a<\/p>\n<pre><code class=\"hljs language-kotlin\"><span class=\"hljs-comment\">\/\/ 2018 \u5e74\u5728\u6c92\u6709\u5354\u7a0b\u7684\u8352\u539f\u4e0a\uff0c\u6700\u52d9\u5be6\u7684\u975e\u540c\u6b65\u5beb\u6cd5<\/span>\n<span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">getUserData<\/span><span class=\"hljs-params\">()<\/span><\/span> {\n    view.showProgress(<span class=\"hljs-literal\">true<\/span>)\n    appExecutors.networkIO().execute {\n        <span class=\"hljs-keyword\">val<\/span> user = userDataRepository.getUser()\n        appExecutors.mainThread().execute {\n            view.showProgress(<span class=\"hljs-literal\">false<\/span>)\n            user?.let { view.showUser(it) } ?: view.showError(<span class=\"hljs-string\">&quot;User not found&quot;<\/span>)\n        }\n    }\n}<\/code><\/pre>\n<p>\u5728\u55ae\u5143\u6e2c\u8a66\u88e1\uff0c\u53ea\u9700\u8981\u6ce8\u5165\u540c\u6b65\u7684 <code>InstantExecutor<\/code> \u5c31\u80fd\u6beb\u79d2\u7d1a\u8dd1\u5b8c\u6e2c\u8a66\u3002\u4eba\u6d3b\u8457\u53ea\u8981\u80fd\u5728\u9031\u4e94\u6e96\u6642\u4e0b\u73ed\uff0c\u7a0b\u5f0f\u78bc\u80fd\u8dd1\u4e14\u597d\u6539\uff0c\u6bd4\u8ffd\u4ec0\u9ebc\u6700\u65b0\u6846\u67b6\u90fd\u5f37\u5f97\u591a\u3002<\/p>\n<hr>\n<h2>5. \u6642\u4ee3\u7684\u773c\u6dda\u8207\u6536\u5de5<\/h2>\n<p>\u6298\u9a30\u4e86\u4e00\u9031\uff0c<code>mvp_kotlin<\/code> \u5206\u652f\u7e3d\u7b97\u5b8c\u6210\u4e86\u5168\u5c08\u6848 80 \u591a\u500b\u6a94\u6848\u7684\u9077\u79fb\u3002<\/p>\n<ul>\n<li><strong>\u7e3d\u884c\u6578\u6e1b\u5c11\u4e86\u5c07\u8fd1 35%<\/strong>\uff0c\u4e3b\u8981\u662f\u5927\u91cf\u7684 POJO \u6a23\u677f\u8207\u8996\u5716\u67e5\u627e\u4ee3\u78bc\u88ab\u6d88\u6ec5\uff1b<\/li>\n<li><strong>\u7dda\u4e0a NPE \u5d29\u6f70\u7387\u5e7e\u4e4e\u6b78\u96f6<\/strong>\uff1b<\/li>\n<li><strong>\u55ae\u5143\u6e2c\u8a66\u8986\u84cb\u7387\u5728\u4e0d\u7834\u58de\u751f\u7522\u4ee3\u78bc\u7684\u524d\u63d0\u4e0b\u7dad\u6301\u5728 80% \u4ee5\u4e0a<\/strong>\u3002<\/li>\n<\/ul>\n<p>\u4f46\u8edf\u9ad4\u5de5\u7a0b\u6700\u8ff7\u4eba\u4e5f\u6700\u8af7\u523a\u7684\u5730\u65b9\uff0c\u5c31\u662f\u6280\u8853\u7684\u5feb\u901f\u8fed\u4ee3\uff1a<\/p>\n<ul>\n<li>\u7576\u5e74\u8cbb\u76e1\u5fc3\u6a5f\u586b\u88dc\u7684 <strong>Synthetics<\/strong>\uff0c\u5f8c\u4f86\u56e0\u70ba\u5feb\u53d6\u7f3a\u9677\u88ab Google \u5fb9\u5e95\u5ee2\u68c4\uff0c\u88ab ViewBinding \u548c Compose \u53d6\u4ee3\uff1b<\/li>\n<li>\u7576\u5e74\u51cc\u6668\u4e09\u9ede\u534a\u641e\u7684 <strong>mock-maker-inline<\/strong> \u8207\u624b\u523b\u7684 <strong>MockitoKotlinHelpers<\/strong>\uff0c\u5f8c\u4f86\u6709\u4e86\u6210\u719f\u7684 MockK \u51fd\u5f0f\u5eab\u539f\u751f\u652f\u63f4 Kotlin\uff1b<\/li>\n<li>\u7576\u5e74\u5c0f\u5fc3\u7ffc\u7ffc\u907f\u958b\u7684 <strong>Coroutines<\/strong>\uff0c\u5728 Kotlin 1.3 \u6b63\u5f0f\u7a69\u5b9a\u5f8c\u8fc5\u901f\u5e2d\u6372 Android\uff0c\u5fb9\u5e95\u6539\u5beb\u4e86\u975e\u540c\u6b65\u4e26\u884c\u7684\u904a\u6232\u898f\u5247\u3002<\/li>\n<\/ul>\n<p>\u6240\u4ee5\u8aaa\u8ffd\u65b0\u6280\u8853\u8ddf\u8cb7\u80a1\u7968\u4e00\u6a23\uff0c\u4f60\u4ee5\u70ba\u6284\u5728\u8d77\u8dd1\u9ede\uff0c\u5176\u5be6\u662f\u9032\u53bb\u7576\u958b\u8def\u5148\u92d2\u517c\u586b\u5751\u7832\u7070\u3002\u4f46\u56de\u982d\u770b commit log\uff0c\u7576\u5e74\u80fd\u628a\u9019\u500b\u5751\u64f0\u6b7b\uff0c\u723d\u5ea6\u9084\u662f\u5be6\u6253\u5be6\u7684\u3002<\/p>\n<p>\u4ee5\u4e0a\u662f\u672c\u6b21\u5c0f\u5f1f\u7684\u771f\u5be6\u611f\u60f3\u3002\u6211\u771f\u7684\u61f6\u5f97\u8cbc\u843d\u843d\u9577\u7684\u5168\u5c08\u6848 CODE\uff0c\u5404\u4f4d\u771f\u7684\u60f3\u770b\u7576\u5e74\u5b8c\u6574\u7684\u63d0\u4ea4\u7d30\u7bc0\uff0c\u53bb GitHub \u7ffb <code>mvp_kotlin<\/code> \u5206\u652f\u5427\uff0c\u901a\u901a\u90fd\u653e\u5728\u90a3\uff0c\u4e0d\u6d6a\u8cbb\u7248\u9762\u3002<\/p>\n<p>\u611f\u8b1d\u89c0\u770b\uff0c\u6536\u5de5\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u8ddd\u96e2\u4e0a\u4e00\u7bc7\u804a MVP \u89e3\u8026\u8207\u55ae\u5143\u6e2c\u8a66\uff0c\u4e0d\u77e5\u4e0d\u89ba\u53c8\u904e\u4e86\u5e7e\u500b\u6708\u3002\u672c\u4f86\u5728\u7b2c\u4e8c\u7bc7\u6700\u5f8c\u4fe1\u8a93\u65e6\u65e6\u8aaa\u8981\u628a Kotlin \u7248\u672c [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1265,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[169],"tags":[200,149,252,280],"_links":{"self":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts\/334"}],"collection":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=334"}],"version-history":[{"count":5,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts\/334\/revisions"}],"predecessor-version":[{"id":1577,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts\/334\/revisions\/1577"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/media\/1265"}],"wp:attachment":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=334"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=334"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=334"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}