Soul Android app 懸浮view以及帖子中view的聯動刷新逆向分析

来源:https://www.cnblogs.com/xing-star/archive/2020/04/24/12768379.html
-Advertisement-
Play Games

Soul app是我司的競品,對它的語音音樂播放同步聯動的邏輯很感興趣,於是就開啟了一波逆向分析。 下麵看代碼,以及技術分析,直接步入正軌,哈哈。 我們根據https://github.com/xingstarx/ActivityTracker 這個工具,找到某一個頁面,比如cn.soulapp.a ...


Soul app是我司的競品,對它的語音音樂播放同步聯動的邏輯很感興趣,於是就開啟了一波逆向分析。

下麵看代碼,以及技術分析,直接步入正軌,哈哈。

我們根據https://github.com/xingstarx/ActivityTracker 這個工具,找到某一個頁面,比如cn.soulapp.android/.ui.post.detail.PostDetailActivity 這個頁面,然後我們用反編譯工具AndroidToolPlus反編譯soul 的Android apk, 然後搜索下PostDetailActivity這個類。然後找到這個類之後,我們在根據代碼經驗猜測,這個語音音樂封裝的控制項可能在哪,肯定是在PostDetailActivity裡面或者是他內容的某個成員變數裡面,一不小心,我們就找到了PostDetailHeaderProvider。在這個類裡面找到了MusicStoryPlayView, AudioPostView這兩個view類,他們就是封裝好的音頻view,音樂view。(就不截圖了。有人感興趣可以按照我說的實踐一番就能得到結論了)

關鍵代碼找到了。那就看看他們內部實現吧。

public class MusicStoryPlayView
  extends FrameLayout
  implements SoulMusicPlayer.MusicPlayListener

類結構上,實現了核心播放器的listener邏輯,那就說明,他的刷新邏輯,都是通過播放器自身的播放狀態回調到view自身上,然後view自身實現了對應的刷新機制就可以更改view的狀態了

 

我們選取幾個回調的邏輯看看。不做仔細分析。

 public void onPause(cn.soulapp.android.lib.common.c.i parami)
  {
    d();
  }
  
  public void onPlay(cn.soulapp.android.lib.common.c.i parami)
  {
    LoveBellingManager.e().d();
  }
  
  public void onPrepare(cn.soulapp.android.lib.common.c.i parami)
  {
    if (this.e == null) {
      return;
    }
    if (parami.b().equals(this.e.songMId)) {
      e();
    }
  }

 

那麼我們還得思考一個問題,這個listener是什麼時候被添加進來的呢。關鍵點在於view自身的兩個方法

  protected void onAttachedToWindow()
  {
    super.onAttachedToWindow();
    SoulMusicPlayer.k().a(this);
  }
  
  protected void onDetachedFromWindow()
  {
    super.onDetachedFromWindow();
    SoulMusicPlayer.k().b(this);
  }
  

所以很明顯,在view被添加到window上(也就是在頁面上顯示出來)的時候,添加入listener裡面,從頁面消失,就移除出去。

接著我們在看看核心播放器的邏輯裡面,是怎麼調度的?

根據代碼相關聯的邏輯,我們很容易找到核心播放器類SoulMusicPlayer

 public void a(cn.soulapp.android.lib.common.c.i parami)
  {
    y0.d().a();
    LoveBellingManager.e().d();
    MusicPlayer.i().f();
    if (TextUtils.isEmpty(parami.f())) {
      return;
    }
    Object localObject1 = this.d;
    if (localObject1 != null) {
      if (!((cn.soulapp.android.lib.common.c.i)localObject1).equals(parami))
      {
        i();
      }
      else
      {
        if (!f())
        {
          this.a.setLooping(parami.g());
          h();
        }
        return;
      }
    }
    if (this.a == null)
    {
      this.a = new IjkMediaPlayer();
      this.a.setOnErrorListener(this);
      this.a.setOnCompletionListener(this);
      this.a.setOnPreparedListener(this);
    }
    this.a.setLooping(parami.g());
    try
    {
      if (l0.e(parami.f()))
      {
        SoulApp localSoulApp;
        Object localObject2;
        if (parami.a() != null)
        {
          localObject1 = this.a;
          localSoulApp = SoulApp.e();
          localObject2 = new java/io/File;
          ((File)localObject2).<init>(parami.f());
          ((IjkMediaPlayer)localObject1).setDataSource(localSoulApp, Uri.fromFile((File)localObject2), parami.a());
        }
        else
        {
          localObject2 = this.a;
          localSoulApp = SoulApp.e();
          localObject1 = new java/io/File;
          ((File)localObject1).<init>(parami.f());
          ((IjkMediaPlayer)localObject2).setDataSource(localSoulApp, Uri.fromFile((File)localObject1));
        }
      }
      else
      {
        localObject1 = parami.a();
        if (localObject1 != null) {
          this.a.setDataSource(SoulApp.e(), Uri.parse(parami.f().replace("https", "http")), parami.a());
        } else {
          this.a.setDataSource(SoulApp.e(), Uri.parse(parami.f().replace("https", "http")));
        }
      }
      this.a.prepareAsync();
      this.d = parami;
      this.b = true;
    }
    catch (IOException parami)
    {
      parami.printStackTrace();
    }
  }
  

 

 public void g()
  {
    if (f())
    {
      Object localObject = this.a;
      if (localObject != null)
      {
        this.b = false;
        ((IjkMediaPlayer)localObject).pause();
        localObject = this.e.iterator();
        while (((Iterator)localObject).hasNext()) {
          ((MusicPlayListener)((Iterator)localObject).next()).onPause(this.d);
        }
        this.c.removeCallbacksAndMessages(null);
      }
    }
  }

仔細觀察分析這兩個方法體,大致可以猜測出,他們是start邏輯,以及暫停播放的邏輯。可以分析出,核心播放器執行完播放,暫停,停止等邏輯後,都會調用List裡面的listener,遍歷listener,然後觸發對應的回調邏輯。

恩,大體的思路有了,就是這麼搞,哈哈。

 

那麼我用於我自己項目中,是這麼用的麽,還是有一些細微差異的,整體方案是參考的soul。細微不同之處在於我是將MusicStoryPlayView放在xml裡面,不是像soul那樣,直接new的。所以MusicStoryPlayView會被添加很多次,比如在列表中有很多個的話,後面需要判斷播放的媒體資源,跟MusicStoryPlayView存放的媒體資源的主鍵是否一致。

此外出了view類,我對於一些特殊的邏輯,比如Activity或者是懸浮view等等,都實現了PlayListener。通過他們可以實現一些棘手的問題。

好了,本篇到此結束,如果大家有疑問,歡迎留言交流。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Vim文本編輯器的使用 Vim的工作模式 1.命令模式 2.輸入模式 3.編輯模式 進入Vim 1.使用Vim打開文件 2.直接進入指定位置 Vim基本命令 1.插入命令 2.游標移動命令 3.使用Vim進行編輯 保存退出命令 Vim的工作模式 命令模式 輸入模式 編輯模式 命令模式 在使用Vim編 ...
  • 這是微軟的內核工程師 Axel Rietschin在Quora的一個回答。 Windows 10 的code base 和Windows 8.x , 7 , Vista , XP , 2000 和Windows NT的code base 是相同的,當然是演化過來的,其中的每一代都進行了重大的重構,增 ...
  • MySQL的帳號操作 一 查看所有用戶 在mysql的user表中存儲了MySQL的用戶信息 主要欄位: Host表示允許訪問的主機 User表示用戶名 authentication_string表示加密後的密碼 二 創建用戶和授權 三 修改許可權 四 修改密碼 五 刪除賬戶 六 遠程登錄(危險慎用) ...
  • SQL語言概況(4.1) [toc] 參考資料: 資料庫原理及設計(第3版) 配套資料庫為:microsoft sql server 參照ANSI SQL 92標準 4.1 SQL語言概況 4.1.1 歷史及標準簡介 一切都源於 關係型資料庫之父 —— Edgar Frank Codd 於1970年 ...
  • 一般我們對緩存讀操作的時候有這麼一個固定的套路: 如果我們的數據在緩存裡邊有,那麼就直接取緩存的。 如果緩存里沒有我們想要的數據,我們會先去查詢資料庫,然後將資料庫查出來的數據寫到緩存中。 最後將數據返回給請求 代碼例子: 1 @Override 2 public R selectOrderById ...
  • MySQL字元串截取函數substring_index()的使用 ...
  • 1.mysql登陸 完整登陸命令: mysql -u root -p xxxxx -h 127.0.0.1 -P 23306 語法:mysql -u 用戶名 -p 密碼 -h mysql伺服器的IP地址 -P 使用的埠號 非完整登陸命令: mysql -u root -p 回車(回車後再輸入密碼) ...
  • 目錄:andorid jar/庫源碼解析 Butterknife: 作用: 用於初始化界面控制項,控制項方法,通過註釋進行綁定控制項和控制項方法 慄子: public class MainActivity extends AppCompatActivity { @BindView(R.id.btnTest1 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...