該篇博文之前標題比較大,引起不少朋友反感,本該刪除,重新整理再發,但是考慮到,編輯後重新發,平臺會再次推薦給大家,更會耽誤老鐵們的時間,遂決定在原文簡單編輯。另外給大家道歉,之前有些標題黨,希望大家原諒。
前言
閒來無事,正看著百度新聞,突然靈感一閃,何不研究下一款新聞app,那就從今日頭條開始吧。
第一步
1.1、輪廓分析
這裡使用壓縮軟體開啟,主要看lib目錄及assets目錄。
1.2、lib目錄
lib目錄主要是放一些庫或jar包,開啟後發現,只有一個armeabi目錄,我們知道x86/x86_64/armeabi-v7a/arm64-v8a裝置都支援armeabi架構的.so檔案,所以一個目錄也是可以的,只是不能保證100%不crash。
- libandfix.so 阿里的一個熱更新框架
- libcocklogic-1.1.3.so 阿里的一個推送
- libdaemon.so 記憶體洩漏檢測
- libgif.so、libgifimage.so字面意思可知是圖片顯示庫
- libimagepipeline.so facebook出品的fresco框架,這個框架使用率正在被Glide追趕,畢竟Glide是谷歌出的。
- libOcrEncryption.so 文字識別庫
- libstatic-webp.so 支援主流webp格式圖片,這個格式圖片比jpg圖片壓縮率超高,幾兆的圖片都可能被壓縮到幾十k。
- libweibosdkcore.so 微博sdk
……
其他的不分析了,感興趣可以自行百度。
1.3、assets目錄
assets目錄主要放置靜態檔案,且為只讀許可權,不能修改。
開啟之後,發現大量的html、js、css檔案,不愧是混合開發啊,一些靜態檔案都直接放assets裡,載入的時候都不需要去網路讀取。這裡包含著jquery.js、rangy-core.js、TBAppLinkJsBridge.js(連線淘寶)、wpload.js、wpsave.js、android.js等。
第二步:抓包
由圖可知,使用了httpdns服務,這種服務具有域名防劫持、精準排程的特性。尤其是阿里推出的httpdns號稱0ms解析延遲
。事實上,剛查了阿里的httpdns服務,ip就是203.107.1.1,確認其使用阿里的httpdns服務。
隨便點選一條新聞試試,得到的地址是
m.haiwainet.cn/ttc/345621/…
可以看到這個網站並不帶頭部及底部的,看來只是為給今日頭條或者其他第三方準備的,不信我在在該網站內隨便點其他新聞
網址格式為m.haiwainet.cn/middle/3541…
ttc和moddile不一樣。這也說明頭條是採用和第三方合作的方式解決文章的版權問題,自己管理著評論介面,另外我app上看到的新聞頁面有些關鍵詞被加了熱點連結,可以知道今日頭條通過js對網頁處理過,或者是加工過。
其他地方很多都是security.snssdk.com通訊,也沒什麼抓的。
第三步:反編譯
有圖可知共使用的服務有andfix、百度推送、極光推送、gson(我覺得fastjson更好用)、小米推送、華為推送、個信、autolay自動佈局、友盟等等。
先分析下啟用頁SplashActivity
圖中有個地方調了finish,看下條件:
isSplashCreateAbOn 沒找到,不知道是幹嘛的,估計寫在so檔案裡了。
isTaskRoot判斷是否在任務棧中的根Activity,即啟動頁面的第一個activity。
上面q、r方法程式碼如下(註釋是猜測的意思):
private void q()
{
if (!com.ss.android.article.base.app.a.Q().cj()) {
return;
}
ArticleBaseExtendManager.a().registDeviceManager(this);
//註冊服務
}
private void r()
{
if (!this.D)
{
this.D = true;
this.E = ArticleBaseExtendManager.a().hasDetailInfo(this);
//得到詳細資訊
}
}複製程式碼
下面去看這個Manager方法:
package com.ss.android.article.base.feature.shrink.extend;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import com.bytedance.common.utility.Logger;
import com.bytedance.common.utility.k;
import com.ss.android.common.shrink.extend.CoreExtendAdapter;
import com.ss.android.common.shrink.extend.CoreExtendSdkManager;
import java.util.HashMap;
import java.util.Map;
public class ArticleBaseExtendManager
extends CoreExtendSdkManager
{
private static CoreExtendSdkManager b = null;
public Map<String, CoreExtendAdapter> a = new HashMap();
private CoreExtendAdapter a(String paramString)
{
CoreExtendAdapter localCoreExtendAdapter2;
if (k.a(paramString))
{
localCoreExtendAdapter2 = null;
return localCoreExtendAdapter2;
}
for (;;)
{
try
{
localCoreExtendAdapter2 = (CoreExtendAdapter)this.a.get(paramString);
if (localCoreExtendAdapter2 != null) {
break;
}
Object localObject2 = Class.forName(paramString).newInstance();
if (!(localObject2 instanceof CoreExtendAdapter)) {
continue;
}
localCoreExtendAdapter1 = (CoreExtendAdapter)localObject2;
}
catch (Throwable localThrowable1)
{
Object localObject1 = localThrowable1;
CoreExtendAdapter localCoreExtendAdapter1 = null;
continue;
localCoreExtendAdapter1 = null;
continue;
}
try
{
this.a.put(paramString, localCoreExtendAdapter1);
//載入完成。
Logger.d("CoreExtendAdapter", "load ThirdExtendLibAdapter done: " + paramString);
return localCoreExtendAdapter1;
}
catch (Throwable localThrowable2) {}
}
//載入異常時
Logger.w("CoreExtendAdapter", "load " + paramString + " has exception: " + localThrowable2);
return localCoreExtendAdapter1;
}
public static CoreExtendSdkManager a()
{
if (b == null) {}
try
{
if (b == null) {
b = new ArticleBaseExtendManager();
}
return b;
}
finally {}
}
public void baiduStatisticsEvent(Context paramContext, String paramString1, String paramString2)
{
CoreExtendAdapter localCoreExtendAdapter = a("com.ss.android.article.base.feature.shrink.extend.BaiduStatisticsAdapter");
if (localCoreExtendAdapter == null) {
return;
}
localCoreExtendAdapter.baiduStatisticsEvent(paramContext, paramString1, paramString2);
}
public Intent getJumpIntent(Context paramContext)
{
CoreExtendAdapter localCoreExtendAdapter = a("com.ss.android.article.base.feature.shrink.extend.UgrReadApkAdapter");
if (localCoreExtendAdapter == null) {
return super.getJumpIntent(paramContext);
}
return localCoreExtendAdapter.getJumpIntent(paramContext);
}
public boolean hasDetailInfo(Context paramContext)
{
CoreExtendAdapter localCoreExtendAdapter = a("com.ss.android.article.base.feature.shrink.extend.UgrReadApkAdapter");
if (localCoreExtendAdapter == null) {
return super.hasDetailInfo(paramContext);
}
return localCoreExtendAdapter.hasDetailInfo(paramContext);
}
public void initBaiduStatistics(Context paramContext)
{
CoreExtendAdapter localCoreExtendAdapter = a("com.ss.android.article.base.feature.shrink.extend.BaiduStatisticsAdapter");
if (localCoreExtendAdapter == null) {
return;
}
localCoreExtendAdapter.initBaiduStatistics(paramContext);
}
public void initGoogleAdSdk(Context paramContext)
{
CoreExtendAdapter localCoreExtendAdapter = a("com.ss.android.article.base.feature.shrink.extend.GoogleAdAdapter");
if (localCoreExtendAdapter == null) {
return;
}
localCoreExtendAdapter.initGoogleAdSdk(paramContext);
}
public void registDeviceManager(Activity paramActivity)
{
CoreExtendAdapter localCoreExtendAdapter = a("com.ss.android.article.base.feature.shrink.extend.RegistDeviceManagerAda");
if (localCoreExtendAdapter == null) {
return;
}
localCoreExtendAdapter.registDeviceManager(paramActivity);
}
public void startAppseStatistics(Context paramContext, String paramString)
{
CoreExtendAdapter localCoreExtendAdapter = a("com.ss.android.article.base.feature.shrink.extend.AppseeStatisticsAdapter");
if (localCoreExtendAdapter == null) {
return;
}
localCoreExtendAdapter.startAppseStatistics(paramContext, paramString);
}
}複製程式碼
由上段程式碼可知:定義一個hash陣列 public Map
如果載入成功,執行a.put(paramString, localCoreExtendAdapter1)方法。該manage內還包含一些初始化方法,如initBaidu、initGoogleAdSdk、registDeviceManager,入參都是字串的包名。
開啟是的b方法,相容性開啟,一般由編譯器生成。
protected Intent b()
{
Intent localIntent;
if (this.F != null) {
localIntent = this.F;
}
do
{
return localIntent;
localIntent = new Intent(this, MainActivity.class);
if (Build.VERSION.SDK_INT >= 11) {
localIntent.addFlags(32768);
}
} while (Build.VERSION.SDK_INT < 21);
localIntent.addFlags(67108864);
localIntent.addFlags(536870912);
return localIntent;
}複製程式碼
再看看Main_Activity程式碼
public class MainActivity
extends com.ss.android.article.base.feature.main.a
{
private static Set<WeakReference<MainActivity>> V = new HashSet();
private WeakReference<MainActivity> W = new WeakReference(this);
public void onCreate(Bundle paramBundle)
{
com.bytedance.c.a.m(this);
k.a.a("MainActivity#onCreateStart");
k.a.b();
try
{
Iterator localIterator = V.iterator();
while (localIterator.hasNext())
{
WeakReference localWeakReference = (WeakReference)localIterator.next();
if (localWeakReference != null)
{
Activity localActivity = (Activity)localWeakReference.get();
if ((localActivity != null) && (localActivity != this))
{
localActivity.finish();
continue;
super.onCreate(paramBundle);
}
}
}
}
catch (Throwable localThrowable) {}
for (;;)
{
k.a.a("MainActivity#onCreateEnd");
com.bytedance.c.a.n(this);
return;
V.clear();
V.add(this.W);
}
}
protected void onDestroy()
{
super.onDestroy();
try
{
V.remove(this.W);
return;
}
catch (Throwable localThrowable) {}
}
protected void onResume()
{
com.bytedance.c.a.o(this);
k.a.a("MainActivity#onResumeStart");
super.onResume();
k.a.a("MainActivity#onResumeEnd");
com.bytedance.c.a.p(this);
}
}複製程式碼
使用了弱引用WeakReference,其主要特點是:一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。這句話意思如果有activity頁面沒有被釋放,在進入Main_Activity時都要執行finish的方法,解決後面多頁面衝突的bug,其中,我們注意到rivate static Set
總結
瞎折騰一通,沒多少技術含量,也許是瞎折騰。但是如果你需要去一家公司面試,先熟悉他所用的技術,提前做點功課、想好應對的策略,可以大大增加面試通過的勝算。
補充:本文較為基礎,更深層次文章,下次帶到。對之前所為深感抱歉!