Inverse analysis of floating VIEW of SOUL ANDROID APP and linkage refresh of VIEW in post

Keywords: Android Java github xml

Inverse analysis of floating VIEW of SOUL ANDROID APP and linkage refresh of VIEW in post

Soul app is a competitive product of our company. We are very interested in the logic of synchronous linkage of voice music playback, so we started a wave of reverse analysis.

Let's look at the code and technical analysis, and go straight to the right track, ha ha.

According to https://github.com/xingstarx/ActivityTracker This tool finds a page, such as cn.soulapp.android/.ui.post.detail.PostDetailActivity. Then we use the decompilation tool Android toolplus to decompile Saul's Android apk, and search for the PostDetailActivity class. After finding this class, we are guessing from our code experience where the voice music encapsulated control may be. It must be in PostDetailActivity or a member variable of its content. If we are not careful, we will find PostDetailHeaderProvider. Two view classes, musicstoryplayview and audiopostview, are found in this class. They are encapsulated audio view and music view. (no screenshots. If you are interested, you can follow my practice and get the conclusion.)

Key code found. Let's see their internal implementation.

public class MusicStoryPlayView
extends FrameLayout
implements SoulMusicPlayer.MusicPlayListener
In the class structure, the listener logic of the core player is implemented, which means that its refresh logic is called back to the view itself through the player's own playback state, and then the view itself implements the corresponding refresh mechanism to change the view state

Let's take a look at the logic of several callbacks. No careful analysis.

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();
}

}

So we have to think about when the listener was added. The key point lies in two methods of view itself

protected void onAttachedToWindow()
{

super.onAttachedToWindow();
SoulMusicPlayer.k().a(this);

}

protected void onDetachedFromWindow()
{

super.onDetachedFromWindow();
SoulMusicPlayer.k().b(this);

}

So obviously, when the view is added to the window (that is, displayed on the page), it is added to the listener, disappears from the page, and then removed.

Then we are looking at the logic of the core player. How is it scheduled?

According to the logic associated with the code, we can easily find the core player class 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);
  }
}

}

After careful observation and analysis of these two methods, we can roughly guess that they are start logic and pause logic. It can be analyzed that after the core player finishes playing, pausing, stopping and other logic, it will call the listener in the List, traverse the listener, and then trigger the corresponding callback logic.

Well, we have a general idea. That's how we do it. Ha ha.

So I use it in my own projects. Is it so, or there are some subtle differences. The overall scheme is a reference soul. The subtle difference is that I put MusicStoryPlayView in xml, not directly new like soul. Therefore, MusicStoryPlayView will be added many times. For example, if there are many media resources in the list, you need to judge whether the primary key of the media resources played is consistent with that of MusicStoryPlayView.

This is out of view class. I have implemented PlayListener for some special logic, such as Activity or suspended view. Through them, some tricky problems can be realized.

Original address https://www.cnblogs.com/xing-star/p/12768379.html

Posted by lamia on Sat, 25 Apr 2020 02:55:29 -0700