忘了他吧!我偷別人APP的代碼養你

一個開發仔的平常離不開和產品經理的Speak,但大多數時候嗶嗶一堆,不如一句「直接說抄哪一個APP」。借(chao)鑑(xi)是門手藝活,簡單的瞄一下,點幾下,可能就知道大概的實現邏輯了,可是「知道 != 寫得出來」,一看就會,一作就廢是常事。既然本身寫不出來,那就去「」!是的,你沒聽錯,去偷別人的代碼。「盜亦有道」:掌握適當的技巧能夠幫咱們更快,更順利得偷到別人的APP代碼,本節以偷「掘金的消息卡片代碼」爲例,講解一波。java


0x一、緣起

發完《Kotlin刨根問底——你真的瞭解Kotlin中的空安全嗎?》這篇文章後,習慣性地把文章連接往個人小破羣裏一丟,接着套用模(mú)板剛擼的文章,講xxx的,取需。簡短的一句,如無病呻吟,換來幾句「看不懂,可是,羣主牛逼的商業互吹」以及「十位數的閱讀量android

羣分享完了,接着小號分享朋友圈,分享時,看到「消息卡片」的這個選項:git

點擊生成後的卡片還挺精美,嘖嘖嘖,正所謂:愛漂亮之心,人皆有之~github

這種分享生成卡片圖的操做很常見,經常使用於各類導流,好比抖音的抖音碼:面試

感受能夠給「摳腚早報速讀」也搞一個,畢竟 花裏胡哨的圖片沒有靈魂的文字和連接 有趣得多。行吧,偷一波「掘金消息卡片的代碼」:shell

其實吧,實現原理還挺簡單的(噗嗤~):canvas

寫一個卡片頁面的佈局,而後調用 View.draw() 實現View截圖Bitmap,把Bitmap保存到相冊。緩存

接着的內容,你們配合下個人演出,開啓裝傻模式吧!安全


0x二、圖由誰來生成?客戶端 VS 服務端


僞裝客戶端和服務端在那裏激烈甩鍋:bash

爭論信息圖片「由服務端生成的」仍是「由客戶端生成的」狀:

  • 客戶端:我丟,寫個接口,我調用的時候給我生成卡片,直接顯示,美滋滋啊!
  • 服務端:美毛線,吃太飽的一直點生成,接口一直調?並且生成要時間啊!
  • 客戶端:緩存啊,生成過的緩存起來,生成過的直接返回,還要我教,菜虛鯤?
  • 服務端:你這樣浪費資源啊,還要找個服務器放這些圖,請求生成卡片的併發量 太大後臺會炸的,一個簡單的生成頁面,搞那麼複雜?高內聚低耦合,你懂不懂?

此時我化身一個 和事佬 出現:

嗶嗶那麼多,驗證下,看別人是怎麼作的不就行了,最簡單的方法,手機依次點擊:

設置 -> 開發者選項 -> 勾選顯示佈局邊界

接着:

回到掘金點擊消息卡片 -> 截圖 -> 點擊保存 -> 打開圖庫

能夠看到下面這兩個圖片:

是的,右側生成的卡片有「佈局邊界」,就是客戶端生成的!除此以外,還能夠經過抓包來驗證。

抓包區間是「打開信息卡片前」和「點擊保存後」,看下是否有拉取卡片圖的請求。

熟練的打開Fidder,安裝證書,打開WIFI手動設置下代理:主機ip,8888,卻發現抓不了HTTPS包。即便換成Charles,Wireshark等其餘抓包工具也抓不到,緣由是:

Android 7.0(Nougat,牛軋糖)開始,Android更改了對用戶安裝證書的默認信任行爲,應用程序「只信任系統級別的CA」。

對此,若是是本身寫的APP想抓HTTPS的包,能夠在 res/xml目錄下新建一個network_security_config.xml 文件,複製粘貼以下內容:

<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" overridePins="true" /> <!--信任系統證書-->
            <certificates src="user" overridePins="true" /> <!--信任用戶證書-->
        </trust-anchors>
    </base-config>
</network-security-config>
複製代碼

接着**AndroidManifest.xml文件中新增networkSecurityConfig**屬性引用xml文件,以下:

<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config"
    ... >
    ...
    </application>
</manifest>
複製代碼

調試期爲了方便本身抓包能夠加上,發版本的時候,記得刪掉哦!!!固然,大部分時候都是想抓別人APP的HTTPS包,最簡單的兩種處理方法:

  • 一、「系統降級」即採用Android 7.0如下手機,好比筆者的抓包雞魅藍E2就是Android 6.0。
  • 二、「抓越獄蘋果雞

稍微複雜點,也是比較常見的方法「手機Root,把證書安裝到系統證書中」,具體操做步驟以下:

# 打開終端,輸入下述命令把 cer或者der 證書轉換爲pem格式
openssl x509 -inform der -in xxx.cer -out xxx.pem

# 證書重命名,命名規則爲:<Certificate_Hash>.<Number>
# Certificate_Hash:表示證書文件的hash值
# Number:爲了防止證書文件的hash值一致而增長的後綴

# 經過下述命令計算出hash值 
openssl x509 -subject_hash_old -in Fiddler.pem |head -1

# 重命名,hash值是上面命令執行輸出的hash值,好比269953fb
mv Fiddler.pem <hash>.0

# adb push命令把文件複製到內部存儲
adb push <hash>.0 /sdcard/

adb shell   # 啓動命令行交互
su          # 得到超級用戶權限
mount -o remount,rw /system   # 將/system目錄從新掛載爲可讀寫
cp /sdcard/<hash>.0 /system/etc/security/cacerts/  # 把文件複製過去
cd /system/etc/security/cacerts/    # 來到目錄下
chmod 644 <hash>.0    # 修改文件權限

# 重啓設備
adb reboot
複製代碼

重啓後,看下可否抓到HTTPS包就知道是否安裝成功,也能夠到設置 -> 安全性和位置信息 -> 加密與憑據 -> 信任的憑據 -> 系統 裏找找本身剛安裝的證書(不一樣手機路徑可能不同)。

其餘抓包工具也是如法炮製,除此還有一種成本更高的方法:「二次打包APK」,不過如今反編譯愈來愈難,不必定能打包成功,固然也說說流程:

  • ① 經過apktool反編譯apk;
  • ② 在res/xml目錄中建立文件network_security_config.xml;
  • ③ AndroidManifest.xml添加android:networkSecurityConfig屬性;
  • ④ 從新打包並自簽名APK

說回正題,抓包,證實消息卡片不是請求後臺獲取到的:

在點擊消息卡片以及到保存這一步,只下載了頭圖,而非卡片圖,大概能猜想到:

打開頁面時傳入標題,內容簡介,文章頭圖,連接等,而後生成消息卡片。

而view生成截圖的方式有兩種:分別是調用View的 getDrawingCache()draw() 方法,不過前者已經Deprecated(過期),點開源碼能夠看到這樣的註釋:

繼續僞裝不知道原理,接着把佈局給摳出來。


0x三、掘金色的漸變圓角背景圖——Apktool得到資源素材


僞裝不會實現這個背景圖,使用Apktool反編譯一波apk,直接拿資源文件,工具包可本身百度或 公號回覆001 獲取,若是反編譯出現以下錯誤信息:

可嘗試更新一波apktool.jar的版本,到:bitbucket.org/iBotPeaches… 下載最新版的Jar包替換便可。接着鍵入下述命令反編譯apk:

apktool.bat d -f xxx.apk
複製代碼

坐等編譯成功:

接着打開編譯後的項目的drawable目錄,搜索:bg_,能夠看到:

盲猜bg_message_card.xml,打開看看:

複製到工程中,稍微調整下,看下預覽效果:

能夠的,接着把用到的圖片素材找出來,複製到工程中,接着咱們來堆砌佈局。


0x四、佈局怎麼堆——開發者助手 + UETool

先來了解佈局的層次,最簡單的方法:經過adb命令來查看:

adb shell dumpsys activity top > info.txt
複製代碼

上述命令會導出activity的堆棧信息到info.txt文件中,搜索應用包名,可定位到當前顯示的Activity:

往下一點,能夠看到View的層次結構:

從這裏就能夠看到當前這個頁面都是由哪些控件堆砌而成的, 不過可能不是很直觀,接着安利一個Android調試工具:開發者助手(可到酷安搜索或 公號回覆002獲取),須要Root權限!!!直接能夠看到包名,版本,當前Activity,Fragment,界面資源分析等信息。

點下界面資源分析,頁面組成一清二楚。

知道佈局是由哪些控件堆砌而成的,是遠遠不夠的,咱們還須要知道控件具體是怎麼堆的。即:寬高多少,margin和padding多少,字體多大,顏色值,是否加粗等等這些信息。一種比較低效的方法是:

手機截圖,發送到電腦,用PxCook之類的工具打開,而後用尺子去量尺寸,取色工具取色。

能夠是不能夠,不過有點撈啊,有沒有更便捷的方法呢?答案確定是有,再安利一個調試工具:UETool,一個移動端頁面調試工具:

不過這個工具,只能 在本身的項目中集成,並不能用來調教別人的APP,須要上擴展版:VirtualUETool

TipsVirtual App 是著名的黑產神器,App虛擬化引擎,能夠在其中建立虛擬空間,而後在虛擬空間裏安裝運行卸載APP,最多見的使用場景就是應用分身,以前是開源的,不過看README.md貌似開始商業化了...

VirtualUETool 用法比較簡單,「捕捉控件」能夠看到控件的一些屬性信息,「相對位置」能夠看控件寬高和與其餘控件的間距,「網格柵欄」能夠用來看控件是否對齊,「佈局層級」以3D模式查看層級。使用示例以下:

邊框,參數這些都有了,堆佈局就不是什麼大問題了~


0x五、用邊角料——堆砌一個不完整的頁面

這裏沒有直接用它的佈局文件,而是參照着本身另外寫了一個,利用前面獲取的一些邊角料。

佈局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#FF333333"
        tools:context=".MainActivity">

    <android.support.constraint.ConstraintLayout
            android:id="@+id/cly_share_bar"
            android:layout_width="0dp"
            android:layout_height="98dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:background="#FFEEEEEE">

        <android.support.v7.widget.AppCompatImageView
                android:id="@+id/iv_share_wx"
                android:layout_width="0dp"
                android:layout_height="44dp"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toLeftOf="@id/iv_share_pyq"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toTopOf="@id/tv_share_wx"
                android:scaleType="fitCenter"
                android:src="@drawable/share_wechat"/>

        <android.support.v7.widget.AppCompatTextView
                android:id="@+id/tv_share_wx"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toLeftOf="@id/iv_share_wx"
                app:layout_constraintRight_toRightOf="@id/iv_share_wx"
                app:layout_constraintTop_toBottomOf="@id/iv_share_wx"
                app:layout_constraintBottom_toBottomOf="parent"
                android:textSize="12sp"
                android:textColor="#8A000000"
                android:text="微信"/>

        <android.support.v7.widget.AppCompatImageView
                android:id="@+id/iv_share_pyq"
                android:layout_width="0dp"
                android:layout_height="44dp"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toRightOf="@id/iv_share_wx"
                app:layout_constraintRight_toLeftOf="@id/iv_share_qq"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toTopOf="@id/tv_share_pyq"
                android:scaleType="fitCenter"
                android:src="@drawable/share_circle"/>

        <android.support.v7.widget.AppCompatTextView
                android:id="@+id/tv_share_pyq"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toLeftOf="@id/iv_share_pyq"
                app:layout_constraintRight_toRightOf="@id/iv_share_pyq"
                app:layout_constraintTop_toBottomOf="@id/iv_share_pyq"
                app:layout_constraintBottom_toBottomOf="parent"
                android:textSize="12sp"
                android:textColor="#8A000000"
                android:text="朋友圈"/>

        <android.support.v7.widget.AppCompatImageView
                android:id="@+id/iv_share_qq"
                android:layout_width="0dp"
                android:layout_height="44dp"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toRightOf="@id/iv_share_pyq"
                app:layout_constraintRight_toLeftOf="@id/iv_share_wb"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toTopOf="@id/tv_share_qq"
                android:scaleType="fitCenter"
                android:src="@drawable/share_qq"/>

        <android.support.v7.widget.AppCompatTextView
                android:id="@+id/tv_share_qq"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toLeftOf="@id/iv_share_qq"
                app:layout_constraintRight_toRightOf="@id/iv_share_qq"
                app:layout_constraintTop_toBottomOf="@id/iv_share_qq"
                app:layout_constraintBottom_toBottomOf="parent"
                android:textSize="12sp"

                android:textColor="#8A000000"
                android:text="QQ"/>

        <android.support.v7.widget.AppCompatImageView
                android:id="@+id/iv_share_wb"
                android:layout_width="0dp"
                android:layout_height="44dp"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toRightOf="@id/iv_share_qq"
                app:layout_constraintRight_toLeftOf="@id/iv_share_save"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toTopOf="@id/tv_share_wb"
                android:scaleType="fitCenter"
                android:src="@drawable/share_weibo"/>

        <android.support.v7.widget.AppCompatTextView
                android:id="@+id/tv_share_wb"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toLeftOf="@id/iv_share_wb"
                app:layout_constraintRight_toRightOf="@id/iv_share_wb"
                app:layout_constraintTop_toBottomOf="@id/iv_share_wb"
                app:layout_constraintBottom_toBottomOf="parent"
                android:textSize="12sp"
                android:textColor="#8A000000"
                android:text="微博"/>


        <android.support.v7.widget.AppCompatImageView
                android:id="@+id/iv_share_save"
                android:layout_width="0dp"
                android:layout_height="44dp"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toRightOf="@id/iv_share_wb"
                app:layout_constraintRight_toLeftOf="@id/iv_share_other"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toTopOf="@id/tv_share_save"
                android:scaleType="fitCenter"
                android:src="@drawable/share_save"/>

        <android.support.v7.widget.AppCompatTextView
                android:id="@+id/tv_share_save"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toLeftOf="@id/iv_share_save"
                app:layout_constraintRight_toRightOf="@id/iv_share_save"
                app:layout_constraintTop_toBottomOf="@id/iv_share_save"
                app:layout_constraintBottom_toBottomOf="parent"
                android:textSize="12sp"
                android:textColor="#8A000000"
                android:text="保存"/>

        <android.support.v7.widget.AppCompatImageView
                android:id="@+id/iv_share_other"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toRightOf="@id/iv_share_save"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toTopOf="@id/tv_share_other"
                android:scaleType="fitCenter"
                android:src="@drawable/share_others"/>

        <android.support.v7.widget.AppCompatTextView
                android:id="@+id/tv_share_other"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                app:layout_constraintVertical_chainStyle="packed"
                app:layout_constraintLeft_toLeftOf="@id/iv_share_other"
                app:layout_constraintRight_toRightOf="@id/iv_share_other"
                app:layout_constraintTop_toBottomOf="@id/iv_share_other"
                app:layout_constraintBottom_toBottomOf="parent"
                android:textSize="12sp"
                android:textColor="#8A000000"
                android:text="保存"/>

    </android.support.constraint.ConstraintLayout>


    <android.support.v4.widget.NestedScrollView
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginBottom="2dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@id/cly_share_bar"
            android:background="@drawable/bg_message_card">

        <android.support.constraint.ConstraintLayout
                android:id="@+id/cly_content"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

            <android.support.constraint.ConstraintLayout
                    android:id="@+id/cly_card"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="14dp"
                    android:layout_marginEnd="14dp"
                    android:layout_marginTop="26dp"
                    app:layout_constraintHorizontal_bias="1.0"
                    app:layout_constraintLeft_toLeftOf="parent"
                    app:layout_constraintRight_toRightOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    android:background="@drawable/shape_bg_content">

                <android.support.v7.widget.AppCompatImageView
                        android:id="@+id/iv_avatar"
                        android:layout_width="40dp"
                        android:layout_height="40dp"
                        android:layout_marginLeft="16dp"
                        android:layout_marginTop="22dp"
                        app:layout_constraintLeft_toLeftOf="parent"
                        app:layout_constraintTop_toTopOf="parent"/>

                <TextView
                        android:id="@+id/tv_level"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginStart="10dp"
                        app:layout_constraintLeft_toRightOf="@id/iv_avatar"
                        app:layout_constraintTop_toTopOf="@id/iv_avatar"
                        android:textSize="16sp"
                        android:drawablePadding="5dp"
                        android:textColor="#FF1C1C1E"/>

                <android.support.v7.widget.AppCompatTextView
                        android:id="@+id/tv_desc"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        app:layout_constraintLeft_toLeftOf="@id/tv_level"
                        app:layout_constraintBottom_toBottomOf="@id/iv_avatar"
                        android:textSize="12sp"
                        android:textColor="#FF8A9AA9"/>

                <android.support.v7.widget.AppCompatImageView
                        android:id="@+id/iv_article_hover"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_marginEnd="16dp"
                        android:layout_marginTop="22dp"
                        app:layout_constraintLeft_toLeftOf="@id/iv_avatar"
                        app:layout_constraintRight_toRightOf="parent"
                        app:layout_constraintTop_toBottomOf="@id/iv_avatar"
                        android:adjustViewBounds="true"/>

                <android.support.v7.widget.AppCompatTextView
                        android:id="@+id/tv_article_title"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="10dp"
                        app:layout_constraintLeft_toLeftOf="@id/iv_article_hover"
                        app:layout_constraintRight_toRightOf="@id/iv_article_hover"
                        app:layout_constraintTop_toBottomOf="@id/iv_article_hover"
                        android:textSize="20sp"
                        android:textStyle="bold"
                        android:lineSpacingExtra="4dp"
                        android:textColor="#FF1C1C1E"
                        android:text=""/>

                <android.support.v7.widget.AppCompatTextView
                        android:id="@+id/tv_article_summary"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="14dp"
                        android:layout_marginRight="14dp"
                        android:layout_marginTop="9dp"
                        app:layout_constraintLeft_toLeftOf="parent"
                        app:layout_constraintRight_toRightOf="parent"
                        app:layout_constraintTop_toBottomOf="@id/tv_article_title"
                        android:textSize="16sp"
                        android:lineSpacingExtra="4dp"
                        android:textColor="#FF1C1C1E"
                        android:text=""/>

                <android.support.v7.widget.AppCompatImageView
                        android:id="@+id/iv_article_qrcode"
                        android:layout_width="80dp"
                        android:layout_height="80dp"
                        android:layout_marginTop="18dp"
                        app:layout_constraintLeft_toLeftOf="parent"
                        app:layout_constraintRight_toRightOf="parent"
                        app:layout_constraintTop_toBottomOf="@id/tv_article_summary"
                        android:scaleType="fitCenter"
                        android:src="@drawable/ic_qr_code"/>

                <android.support.v7.widget.AppCompatTextView
                        android:id="@+id/tv_article_tips"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="5dp"
                        android:layout_marginBottom="12dp"
                        android:paddingBottom="12dp"
                        app:layout_constraintLeft_toLeftOf="@id/iv_article_qrcode"
                        app:layout_constraintRight_toRightOf="@id/iv_article_qrcode"
                        app:layout_constraintTop_toBottomOf="@id/iv_article_qrcode"
                        app:layout_constraintBottom_toBottomOf="parent"
                        android:textSize="11sp"
                        android:textColor="#FF1C1C1E"
                        android:text="長按識別二維碼"/>

            </android.support.constraint.ConstraintLayout>

            <android.support.v7.widget.AppCompatImageView
                    android:id="@+id/iv_adaptive_logo"
                    android:layout_width="30dp"
                    android:layout_height="30dp"
                    android:layout_marginTop="14dp"
                    app:layout_constraintHorizontal_chainStyle="packed"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toStartOf="@+id/tv_slogan"
                    app:layout_constraintTop_toBottomOf="@id/cly_card"
                    android:scaleType="fitCenter"
                    android:src="@drawable/adaptive_logo"/>

            <android.support.v7.widget.AppCompatTextView
                    android:id="@+id/tv_slogan"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    app:layout_constraintStart_toEndOf="@+id/iv_adaptive_logo"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toTopOf="@id/iv_adaptive_logo"
                    app:layout_constraintBottom_toBottomOf="@id/iv_adaptive_logo"
                    android:textSize="12dp"
                    android:textColor="#FFFCFCFC"
                    android:text="掘金 · 一個幫助開發者成長的技術社區"/>

            <android.support.v7.widget.AppCompatTextView
                    android:id="@+id/tv_host"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="1dp"
                    android:layout_marginBottom="24dp"
                    app:layout_constraintLeft_toLeftOf="parent"
                    app:layout_constraintRight_toRightOf="parent"
                    app:layout_constraintTop_toBottomOf="@id/tv_slogan"
                    app:layout_constraintBottom_toBottomOf="parent"
                    android:textSize="12dp"
                    android:textColor="#FFFCFCFC"
                    android:text="juejin.im"/>

        </android.support.constraint.ConstraintLayout>
        
    </android.support.v4.widget.NestedScrollView>
    
</android.support.constraint.ConstraintLayout>
複製代碼

界面文件:MainActivity.kt

package com.coderpig.kttest
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CircleCrop
import com.bumptech.glide.request.RequestOptions
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private val authorAvatarUrl = "https://user-gold-cdn.xitu.io/2019/5/10/16a9fc6bbb83e12e?imageView2/0/h/110/q/110"
    private val authorNickName = "coder-pig"
    private val authorDesc = "網管@摳腚網咖"
    private val authorLevel = 1
    private val articleCoverUrl =
        "https://user-gold-cdn.xitu.io/2019/7/24/16c22e09ebcc9819?w=1918&h=1067&f=jpeg&s=601259"
    private val articleTitle = "Kotlin刨根問底——你真的瞭解Kotlin中的空安全嗎?"
    private val articleSummary =
        "每一個人的時間都是有限的,一旦作出學習某塊知識的選擇,意味着付出了暫時沒法學習其餘知識的機會成本,須要取捨。不可能等什麼都學會了再去面試,學完得猴年馬月,並且技術,是學不完的… 初次接觸Kotlin已經是三年前,在上家公司用Kotlin重構了平板的應用市場和電臺APP。說來慚愧,至…"
    private val articleUrl = "https://juejin.im/entry/5d38086f6fb9a07f00531d24"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Glide.with(this).load(authorAvatarUrl).apply(RequestOptions.bitmapTransform(CircleCrop())).into(iv_avatar)
        tv_level.text = authorNickName
        val rightDrawable = when (authorLevel) {
            1 -> getDrawable((R.drawable.ic_user_lv1))
            2 -> getDrawable((R.drawable.ic_user_lv2))
            3 -> getDrawable((R.drawable.ic_user_lv3))
            4 -> getDrawable((R.drawable.ic_user_lv4))
            5 -> getDrawable((R.drawable.ic_user_lv5))
            6 -> getDrawable((R.drawable.ic_user_lv6))
            7 -> getDrawable((R.drawable.ic_user_lv7))
            8 -> getDrawable((R.drawable.ic_user_lv8))
            else -> null
        }
        rightDrawable?.setBounds(0, 0, rightDrawable.minimumWidth, rightDrawable.minimumHeight)
        tv_level.setCompoundDrawables(null, null, rightDrawable, null)
        tv_desc.text = authorDesc
        tv_article_title.text = articleTitle
        tv_article_summary.text = articleSummary
        Glide.with(this).load(articleCoverUrl).into(iv_article_hover)
    }
}
複製代碼

運行效果圖以下

哈哈,像不像,真的不是截掘金哈,替換一波文章相關的信息,運行下看看:

細心的你應該能發現這個模糊的二維碼(直接用的截圖),以及右上角缺失了的文章標籤,僞裝不知道二維碼能夠用zxing實現,看看掘金是怎麼作的?


0x六、君子愛碼,盜之有道——Jadx反編譯


一、偷生成二維碼圖片的代碼

在開發者助手那裏能夠看到「未知加固」,通常就是沒有加固,不用脫殼美滋滋,Jadx反編譯一波源碼(jadx直接反編譯apk很容易直接卡死,筆者寫了個Python的批處理腳本,取需:github.com/coder-pig/C…),反編譯後用Android Studio打開反編譯後的項目,記得順帶把前面apktool反編譯出來的res資源文件夾也丟進去!

接着全局搜索文件CommonActivity.java,代碼裏搜下setContentView,能夠看到:

這裏和沉浸式狀態欄有關,兼容Android 5.0如下,佈局以下大同小異:

自定義了一個StatusBarView狀態欄和一個幀佈局容器FixInsetsFrameLayout,中間塞個toolbar,這裏主要關注這個容器,佈局id:fragment_container,八九不離十是用來塞Fragment的,搜下:

getSupportFragmentManager().beginTransaction().replace
複製代碼

能夠看到:

噢,兩種建立方法耶

  • 一、直接Intent傳FRAGMENT_NAME建立
  • 二、經過ARouter建立

第一種見得多了,第二種用的是阿里的ARouter路由,點進去**ServiceFactory.getInstance().getFragment()**方法:

就是try裏面包着的這一句,去ARouter的Github倉庫就能夠翻到混淆前的樣子是:

Fragment fragment = (Fragment) ARouter.getInstance().build("/xxx/xxxfragment").navigation();
複製代碼

嘖嘖嘖,怪不得開發者助手那裏沒法檢測當前Fragment,行吧,咱們須要找到這個fragment的路徑,在CommonActivity.java這個文件中顯然是很難繼續下去的:

解固然也是有解的,動態調試smali或者xposed寫個簡單插件打印日誌,不過有點繁瑣,換種姿式吧。先明確下如今的目標:

找到消息卡片的佈局!!!

em...發現卡片底部有一句掘金的slogan:一個幫助開發者成長的技術社區,全局搜下?

23333,直指 fragment_entry_pin_card.xml佈局,打開看看:

圈住的部分分別是二維碼對應的控件右上角標籤,接着全局搜R.layout.fragment_entry_pin_card

定位到了PreviewEntryPinCardFragment這個類,就是消息卡片對應的Fragment,接着搜顯示二維碼控件的id:iv_qrcode

定位到BitmapUtils類的**create2DCode()**方法:

導包處能夠看到用到了google的zxing庫

複製粘貼,轉一波Kotlin,這裏Hashtable須要明確傳入類型:

接着顯示二維碼的控件調用 setImageBitmap() 設置一波,結果以下:

能夠,很舒服,你可能對這裏的**-16777216**有疑問,其實就是一個十進制的顏色值,代碼轉下十六進制:

print(String.format("%08x",-16777216))
複製代碼

打印:ff000000,即不透明黑色,對應屬性Color.Black,偷二維碼生成代碼任務完成


二、偷自定義標籤控件的代碼

接着到右上角的文章標籤了,在佈局文件裏看到這個自定義控件im.juejin.android.base.views.labelview.LabelView,搜下LabelView.java文件,開偷,若是你有必定的Kotlin語法基礎和自定義View基礎,偷起來仍是非常不難的,留意下這個東西:

就是自定義屬性,反編譯後的attrs.xml裏是找不到這個LabelView的,須要本身自定義一個,定義declare-styleable標籤,把相關屬性從反編譯後的attrs.xml中選擇性複製,好比這裏:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="LabelView">
        <attr name="font" format="reference"/>
        <attr name="fontWeight" format="integer"/>
        <attr name="foregroundInsidePadding" format="boolean"/>
        <attr name="il_max_length" format="integer"/>
        <attr name="il_hint" format="string"/>
        <attr name="lv_text" format="string"/>
        <attr name="lv_text_color" format="color"/>
        <attr name="lv_text_size" format="dimension"/>
        <attr name="lv_text_bold" format="boolean"/>
        <attr name="lv_text_all_caps" format="boolean"/>
        <attr name="lv_background_color" format="color"/>
        <attr name="lv_min_size" format="dimension"/>
        <attr name="lv_padding" format="dimension"/>
        <attr name="lv_gravity">
            <enum name="BOTTOM_LEFT" value="83"/>
            <enum name="BOTTOM_RIGHT" value="85"/>
            <enum name="TOP_LEFT" value="51"/>
            <enum name="TOP_RIGHT" value="53"/>
        </attr>
        <attr name="lv_fill_triangle" format="boolean" />
    </declare-styleable>
</resources>
複製代碼

接着甚至連實現原理都不用去看,無腦複製代碼進項目中,自動Java轉Kotlin,處理一波語法問題和刪除無關代碼,調整後的LabelView代碼以下:

package com.coderpig.kttest

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.view.View
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt

class LabelView : View {
    private var mBackgroundColor: Int = 0
    private var mBackgroundPaint: Paint = Paint(1)
    private var mFillTriangle: Boolean = false
    private var mGravity: Int = 0
    private var mMinSize: Float = 0.0f
    private var mPadding: Float = 0.0f
    private var mPath: Path = Path()
    private var mTextAllCaps: Boolean = false
    private var mTextBold: Boolean = false
    private var mTextColor: Int = 0
    private var mTextContent: String = ""
    private var mTextPaint: Paint = Paint(1)
    private var mTextSize: Float = 0.0f

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs, 0) {
        obtainAttributes(context, attrs)
        this.mTextPaint.textAlign = Paint.Align.CENTER
    }

    private fun obtainAttributes(context: Context, attributeSet: AttributeSet?) {
        val obtainStyledAttributes = context.obtainStyledAttributes(attributeSet, R.styleable.LabelView)
        this.mTextContent = obtainStyledAttributes.getString(R.styleable.LabelView_lv_text) as String
        this.mTextColor = obtainStyledAttributes.getColor(
            R.styleable.LabelView_lv_text_color, Color.parseColor("#FFFFFF")
        )
        this.mTextSize = obtainStyledAttributes.getDimension(R.styleable.LabelView_lv_text_size, sp2px(11.0f).toFloat())
        this.mTextBold = obtainStyledAttributes.getBoolean(R.styleable.LabelView_lv_text_bold, true)
        this.mTextAllCaps = obtainStyledAttributes.getBoolean(R.styleable.LabelView_lv_text_all_caps, true)
        this.mFillTriangle = obtainStyledAttributes.getBoolean(R.styleable.LabelView_lv_fill_triangle, false)
        this.mBackgroundColor =
            obtainStyledAttributes.getColor(R.styleable.LabelView_lv_background_color, Color.parseColor("#FF4081"))
        this.mMinSize = obtainStyledAttributes.getDimension(
            R.styleable.LabelView_lv_min_size,
            if (this.mFillTriangle) dp2px(35.0f).toFloat() else dp2px(50.0f).toFloat()
        )
        this.mPadding = obtainStyledAttributes.getDimension(R.styleable.LabelView_lv_padding, dp2px(3.5f).toFloat())
        this.mGravity = obtainStyledAttributes.getInt(R.styleable.LabelView_lv_gravity, 51)
        obtainStyledAttributes.recycle()
    }

    fun setTextColor(i: Int) { this.mTextColor = i; invalidate() }

    fun setText(str: String) { this.mTextContent = str; invalidate() }

    fun setTextSize(f: Float) { this.mTextSize = sp2px(f).toFloat(); invalidate() }

    fun setTextBold(z: Boolean) { this.mTextBold = z; invalidate() }

    fun setFillTriangle(z: Boolean) { this.mFillTriangle = z; invalidate() }

    fun setTextAllCaps(z: Boolean) { this.mTextAllCaps = z; invalidate() }

    fun setBgColor(i: Int) { this.mBackgroundColor = i; invalidate() }

    fun setMinSize(f: Float) { this.mMinSize = dp2px(f).toFloat(); invalidate() }

    fun setPadding(f: Float) { this.mPadding = dp2px(f).toFloat(); invalidate() }

    fun setGravity(i: Int) { this.mGravity = i }

    fun getText(): String = this.mTextContent

    fun getTextColor() = this.mTextColor

    fun getTextSize() = this.mTextSize

    fun isTextBold() = this.mTextBold

    fun isFillTriangle() = this.mFillTriangle

    fun isTextAllCaps() = this.mTextAllCaps

    fun getBgColor() = this.mBackgroundColor

    fun getMinSize() = this.mMinSize

    fun getPadding() = this.mPadding

    fun getGravity() = this.mGravity

    public override fun onDraw(canvas: Canvas) {
        val height = height
        this.mTextPaint.color = this.mTextColor
        this.mTextPaint.textSize = this.mTextSize
        this.mTextPaint.isFakeBoldText = this.mTextBold
        this.mBackgroundPaint.color = this.mBackgroundColor
        val descent = this.mTextPaint.descent() - this.mTextPaint.ascent()
        if (!this.mFillTriangle) {
            val sqrt = (this.mPadding * 2.0f + descent).toDouble() * sqrt(2.0)
            when {
                this.mGravity == 51 -> {
                    this.mPath.reset()
                    this.mPath.moveTo(0.0f, (height.toDouble() - sqrt).toFloat())
                    this.mPath.lineTo(0.0f, height.toFloat())
                    this.mPath.lineTo(height.toFloat(), 0.0f)
                    this.mPath.lineTo((height.toDouble() - sqrt).toFloat(), 0.0f)
                    this.mPath.close()
                    canvas.drawPath(this.mPath, this.mBackgroundPaint)
                    drawText(height, -45.0f, canvas, descent, true)
                }
                this.mGravity == 53 -> {
                    this.mPath.reset()
                    this.mPath.moveTo(0.0f, 0.0f)
                    this.mPath.lineTo(sqrt.toFloat(), 0.0f)
                    this.mPath.lineTo(height.toFloat(), (height.toDouble() - sqrt).toFloat())
                    this.mPath.lineTo(height.toFloat(), height.toFloat())
                    this.mPath.close()
                    canvas.drawPath(this.mPath, this.mBackgroundPaint)
                    drawText(height, 45.0f, canvas, descent, true)
                }
                this.mGravity == 83 -> {
                    this.mPath.reset()
                    this.mPath.moveTo(0.0f, 0.0f)
                    this.mPath.lineTo(0.0f, sqrt.toFloat())
                    this.mPath.lineTo((height.toDouble() - sqrt).toFloat(), height.toFloat())
                    this.mPath.lineTo(height.toFloat(), height.toFloat())
                    this.mPath.close()
                    canvas.drawPath(this.mPath, this.mBackgroundPaint)
                    drawText(height, 45.0f, canvas, descent, false)
                }
                this.mGravity == 85 -> {
                    this.mPath.reset()
                    this.mPath.moveTo(0.0f, height.toFloat())
                    this.mPath.lineTo(sqrt.toFloat(), height.toFloat())
                    this.mPath.lineTo(height.toFloat(), sqrt.toFloat())
                    this.mPath.lineTo(height.toFloat(), 0.0f)
                    this.mPath.close()
                    canvas.drawPath(this.mPath, this.mBackgroundPaint)
                    drawText(height, -45.0f, canvas, descent, false)
                }
            }
        } else if (this.mGravity == 51) {
            this.mPath.reset()
            this.mPath.moveTo(0.0f, 0.0f)
            this.mPath.lineTo(0.0f, height.toFloat())
            this.mPath.lineTo(height.toFloat(), 0.0f)
            this.mPath.close()
            canvas.drawPath(this.mPath, this.mBackgroundPaint)
            drawTextWhenFill(height, -45.0f, canvas, true)
        } else if (this.mGravity == 53) {
            this.mPath.reset()
            this.mPath.moveTo(height.toFloat(), 0.0f)
            this.mPath.lineTo(0.0f, 0.0f)
            this.mPath.lineTo(height.toFloat(), height.toFloat())
            this.mPath.close()
            canvas.drawPath(this.mPath, this.mBackgroundPaint)
            drawTextWhenFill(height, 45.0f, canvas, true)
        } else if (this.mGravity == 83) {
            this.mPath.reset()
            this.mPath.moveTo(0.0f, height.toFloat())
            this.mPath.lineTo(0.0f, 0.0f)
            this.mPath.lineTo(height.toFloat(), height.toFloat())
            this.mPath.close()
            canvas.drawPath(this.mPath, this.mBackgroundPaint)
            drawTextWhenFill(height, 45.0f, canvas, false)
        } else if (this.mGravity == 85) {
            this.mPath.reset()
            this.mPath.moveTo(height.toFloat(), height.toFloat())
            this.mPath.lineTo(0.0f, height.toFloat())
            this.mPath.lineTo(height.toFloat(), 0.0f)
            this.mPath.close()
            canvas.drawPath(this.mPath, this.mBackgroundPaint)
            drawTextWhenFill(height, -45.0f, canvas, false)
        }
    }

    private fun drawText(i: Int, f: Float, canvas: Canvas, f2: Float, z: Boolean) {
        canvas.save()
        canvas.rotate(f, i.toFloat() / 2.0f, i.toFloat() / 2.0f)
        canvas.drawText(
            if (this.mTextAllCaps) this.mTextContent.toUpperCase() else this.mTextContent,
            (paddingLeft + (i - paddingLeft - paddingRight) / 2).toFloat(),
            (i / 2).toFloat() - (this.mTextPaint.descent() + this.mTextPaint.ascent()) / 2.0f + if (z) -(this.mPadding * 2.0f + f2) / 2.0f else (this.mPadding * 2.0f + f2) / 2.0f,
            this.mTextPaint
        )
        canvas.restore()
    }

    private fun drawTextWhenFill(i: Int, f: Float, canvas: Canvas, z: Boolean) {
        canvas.save()
        canvas.rotate(f, i.toFloat() / 2.0f, i.toFloat() / 2.0f)
        canvas.drawText(
            if (this.mTextAllCaps) this.mTextContent.toUpperCase() else this.mTextContent,
            (paddingLeft + (i - paddingLeft - paddingRight) / 2).toFloat(),
            (i / 2).toFloat() - (this.mTextPaint.descent() + this.mTextPaint.ascent()) / 2.0f + if (z) (-i / 4).toFloat() else (i / 4).toFloat(),
            this.mTextPaint
        )
        canvas.restore()
    }

    /* access modifiers changed from: protected */
    public override fun onMeasure(i: Int, i2: Int) {
        val measureWidth = measureWidth(i)
        setMeasuredDimension(measureWidth, measureWidth)
    }

    private fun measureWidth(i: Int): Int {
        val mode = MeasureSpec.getMode(i)
        val size = MeasureSpec.getSize(i)
        if (mode == 1073741824) {
            return size
        }
        val paddingLeft = paddingLeft + paddingRight
        this.mTextPaint.color = this.mTextColor
        this.mTextPaint.textSize = this.mTextSize
        var measureText =
            ((paddingLeft + this.mTextPaint.measureText(this.mTextContent + "").toInt()).toDouble() * sqrt(2.0)).toInt()
        if (mode == Integer.MIN_VALUE) {
            measureText = min(measureText, size)
        }
        return max(this.mMinSize.toInt(), measureText)
    }


    private fun dp2px(f: Float) = (resources.displayMetrics.density * f + 0.5f).toInt()

    private fun sp2px(f: Float) = (resources.displayMetrics.scaledDensity * f + 0.5f).toInt()
}
複製代碼

佈局直接添加這個控件:

<com.coderpig.kttest.LabelView
    xmlns:lv="http://schemas.android.com/apk/res-auto"
    android:layout_width="60dp"
    android:layout_height="60dp"
    android:paddingLeft="10dip"
    android:paddingRight="10dip"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    lv:lv_text=" 文章 "
    lv:lv_text_color="#ffffffff"
    lv:lv_text_size="11.199982sp"
    lv:lv_background_color="#ffdfdfdf"
    lv:lv_gravity="TOP_RIGHT"
    lv:lv_fill_triangle="false"/>
複製代碼

運行下看下效果(對比掘金生成的和我實現的效果):


三、偷生成信息卡片截圖的代碼


行吧,就剩下最後生成信息卡片截圖的代碼咯,在上面的PreviewEntryPinCardFragment.java文件中並無找到底下這個分享欄。分享欄應該是寫到另外一個佈局文件中了,這裏用一個取巧的操做,直接全局搜保存按鈕的文件名:share_save,記得勾選xml類型,能夠更快定位到:

打開fragment_message_card.xml

行吧,就是咱們想要的內容,全局搜:R.layout.fragment_message_card,勾選java文件:

直指 ActivityShareFragment.java,接着搜save,定位到點擊 ,

猜想這裏作了兩個操做:

  • 一、調用BitmapUtils類的saveBitmap2file()方法保存截圖;
  • 二、調用FileUtils類的notifyGallery()通知圖庫更新;

接着打開BitmapUtils類,定位到 saveBitmap2file() 方法:

這段代碼只是:把Bitmap保存爲JPEG圖片而已,而後,前面的Bitmap哪來的?對應參數r1

經過CommonMessageCardFragment類的getBitmap()方法得到,打開類定位到getBitmap()方法:

抽象類和抽象方法???看下前面的PreviewEntryPinCardFragment是否是繼承了這個類:

果真,這裏把佈局視圖做爲參數傳入 ViewExKt.e() 方法,跟:

臥槽,水到渠成啊,圖片的生成過程一清二楚啊!接着把 圖片路徑生成規律通知圖庫 的部分也摳出來把。

SD.getGalleryDir():得到圖庫路徑:

MD5Util.encrypt():MD5加密下連接:

最後FileUtils.notifyGallery():發送廣播通知圖庫更新。

嘖嘖嘖,材料齊全,開始組裝偷來的代碼,整合後的代碼以下:

工具類:Utils.kt

package com.coderpig.kttest

import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Environment
import java.io.FileOutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException

// 把Bitmap保存爲圖片
fun saveBitmap2file(bitmap: Bitmap, str: String): Boolean {
    val compressFormat = Bitmap.CompressFormat.JPEG
    return try {
        val fileOutputStream = FileOutputStream(str)
        val compress = bitmap.compress(compressFormat, 100, fileOutputStream)
        fileOutputStream.close()
        compress
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

// 得到圖庫路徑
fun getGalleryDir(): String {
    val externalStoragePublicDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
    try {
        externalStoragePublicDirectory.mkdirs()
    } catch (e: Exception) {
    }
    return externalStoragePublicDirectory.absolutePath
}

// 得到MD5字符串
fun encrypt(str: String?): String {
    var str = str
    val str2 = ""
    if (str == null) {
        str = ""
    }
    try {
        val instance = MessageDigest.getInstance("MD5")
        instance.update(str.toByteArray())
        val digest = instance.digest()
        val stringBuffer = StringBuffer("")
        for (i in digest.indices) {
            var b = digest[i]
            if (b < 0) {
                b = (b + 256).toByte()
            }
            if (b < 16) {
                stringBuffer.append("0")
            }
            stringBuffer.append(Integer.toHexString(b.toInt()))
        }
        return stringBuffer.toString()
    } catch (e: NoSuchAlgorithmException) {
        return str2
    }
}

// 廣播通知圖庫更新
fun notifyGallery(context: Context, str: String) {
    context.sendBroadcast(Intent("android.intent.action.MEDIA_SCANNER_SCAN_FILE", Uri.parse("file://$str")))
}
複製代碼

頁面MainActivity.java 新增:

iv_share_save.setOnClickListener {
    val sb = StringBuilder()
    sb.apply { append(getGalleryDir()).append("/").append(Test.encrypt(entryUrl)).append(".jpg") }
    val picPath = sb.toString().replace("ffffff", "")
    saveBitmap2file(createViewBitmap(cly_content), picPath)
    notifyGallery(this, picPath)
    Toast.makeText(this, "已經保存到:$picPath" +"", Toast.LENGTH_SHORT).show()
}

private fun createViewBitmap(view: View): Bitmap {
    val createBitmap = Bitmap.createBitmap(view.width, view.height, Config.RGB_565)
    view.draw(Canvas(createBitmap))
    return createBitmap
}
複製代碼

最後的運行效果圖以下

這裏有個小坑我糾結了許久,就是生成的md5字符串一直和掘金的不同,後來發現加密的字符串不是文章的連接,而是:juejin.im/entry/xxx 哈哈。行吧,到此,掘金消息卡片的代碼總算收入囊中,完整的代碼,公號回覆003取需。


0x七、碎碎念

偷代碼只是開開玩笑,畢竟是別人的勞動結晶!尊重他人勞動成果,限於咱們本身的閱歷,或者沒有大神帶,有些功能以本身當前水平沒辦法寫出來, 此時借鑑別人的代碼,也不失爲一個好的方法。並且研究別人寫的代碼挺有趣的,一層一層刨開,揣摩做者的意圖,用到了什麼技巧,怎麼用到本身的項目中,等等,耗時,但獲益良多。最後說一句,僅用於技術研究學習之用,請勿用於商業用途!破壞計算機信息系統罪瞭解下?很是鄙視那種二次打包別人APP,而後塞廣告或者病毒的人。

(PS:公號回覆00x返回對應資源,沒別的意思,只是方便本身和羣友,有些人看了個人文章,反手就問:那個東西去哪裏下?資源失效了?有那個XX嗎?等等這些問題,而我又要去打開文章,而後想一想資源在哪,從新傳一下,而後又回頭把幾個平臺的文章改一下,好煩咯!So,丟公號去了!別吐槽我啊,關鍵字和官網啥的我都有給,能夠本身百度!)

行吧,就說這麼多,若是紕漏或建議,歡迎在評論區指出,謝謝~


參考文獻


若是本文對你有所幫助,歡迎
留言,點贊,轉發
素質三連,謝謝😘~

相關文章
相關標籤/搜索