1. 程式人生 > >Vitamio的踩坑+填坑

Vitamio的踩坑+填坑

vitamio這個視訊框架已經有快兩年沒有沒有使用過了,今天想寫個demo再複習下。

  • 首先肯定是下載官方的demo跑一下了,他們的demo放在github上,所以我就直接上github搜尋並下載VitamioBundle,我個人比較喜歡用新的api,所以手動將targetSdkVersion改為了27,重新編譯執行,app打開了,沒問題,點選VideoView條目跳轉播放頁面播放,居然崩潰了。
07-19 07:44:09.764 5133-5133/io.vov.vitamio.demo E/linker: "/data/data/io.vov.vitamio.demo/libs/libffmpeg.so" has text relocations (https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#Text-Relocations-Enforced-for-API-level-23)
07-19 07:44:09.765 5133-5133/io.vov.vitamio.demo E/Vitamio[4.2.1
][Player]: LOAD FFMPEG ERROR: dlopen failed: "/data/data/io.vov.vitamio.demo/libs/libffmpeg.so" has text relocations (https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#Text-Relocations-Enforced-for-API-level-23) 07-19 07:44:09.766 5133-5133/io.vov.vitamio.demo E/Vitamio[4.2.1
][Player]: FIND_NAME_SYM vvo, render_yuv 07-19 07:44:09.768 5133-5133/io.vov.vitamio.demo A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 5133 (ov.vitamio.demo), pid 5133 (ov.vitamio.demo) 07-19 07:44:09.792 5170-5170/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** Build fingerprint: 'google/sdk_gphone_x86/generic_x86:8.1.0/OSM1.180201.021/4741582:userdebug/dev-keys'
Revision: '0' ABI: 'x86' pid: 5133, tid: 5133, name: ov.vitamio.demo >>> io.vov.vitamio.demo <<< signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 Cause: null pointer dereference eax ca5b5ac0 ebx ca5aded8 ecx e49a91e4 edx e3b10860 07-19 07:44:09.792 5170-5170/? A/DEBUG: esi e3b10860 edi 00000075 xcs 00000023 xds 0000002b xes 0000002b xfs 0000006b xss 0000002b eip 00000000 ebp ca598f98 esp ff832c2c flags 00010246 backtrace: #00 pc 00000000 <unknown> #01 pc 5b6f696c <unknown>

說實話這日誌看的我一臉懵逼,但還是得找解決方案google一下。

在這裡我找到了暫時解決得方案,把targetSdkVersion改到22,再次執行,視訊正常播放。

  • demo跑起來了,接下來當然是自己寫個demo玩玩了。(具體的匯入過程我就不多說了,百度一下有很多)編譯執行,what?黑屏~。檢視日誌:

07-19 08:03:08.076 5822-5822/io.vov.vitamio.demo E/Vitamio[Player]: Native libs libffmpeg.so not exists!

開啟Vitamio.java,找到輸出錯誤日誌的地方:

 /**
   * Check if Vitamio is initialized at this device
   *
   * @param ctx Android Context
   * @return true if the Vitamio has been initialized.
   */
  public static boolean isInitialized(Context ctx) {
    vitamioPackage = ctx.getPackageName();
    vitamioLibraryPath = ContextUtils.getDataDir(ctx) + "libs/";
    File dir = new File(getLibraryPath());
    if (dir.exists() && dir.isDirectory()) {
      String[] libs = dir.list();
      if (libs != null) {
        Arrays.sort(libs);
        for (String L : getRequiredLibs()) {
          if (Arrays.binarySearch(libs, L) < 0) {
            Log.e("Native libs %s not exists!", L);
            return false;
          }
        }
        File lock = new File(getLibraryPath() + LIBS_LOCK);
        BufferedReader buffer = null; 
        try {
          buffer = new BufferedReader(new FileReader(lock));  
          int appVersion = ContextUtils.getVersionCode(ctx);
          int libVersion = Integer.valueOf(buffer.readLine());  
          Log.i("isNativeLibsInited, APP VERSION: %d, Vitamio Library version: %d", appVersion, libVersion);
          if (libVersion == appVersion)
            return true;
        } catch (IOException e) {
          Log.e("isNativeLibsInited", e);
        } catch (NumberFormatException e) {
            Log.e("isNativeLibsInited", e);
        } finally {
          IOUtils.closeSilently(buffer);
        }
      }
    }
    return false;
  }
原來是沒有初始化成功,先看看我們載入佈局之前做了什麼。
//檢查初始化
if (!LibsChecker.checkVitamioLibs(this))
    return;
那我們再看一下LibsChecker這個類做了什麼
public final class LibsChecker {
  public static final String FROM_ME = "fromVitamioInitActivity";

  public static final boolean checkVitamioLibs(Activity ctx) {
    if (!Vitamio.isInitialized(ctx) && !ctx.getIntent().getBooleanExtra(FROM_ME, false)) {
      Intent i = new Intent();
      i.setClassName(Vitamio.getVitamioPackage(), "io.vov.vitamio.activity.InitActivity");
      i.putExtras(ctx.getIntent());
      i.setData(ctx.getIntent().getData());
      i.putExtra("package", ctx.getPackageName());
      i.putExtra("className", ctx.getClass().getName());
      ctx.startActivity(i);
      ctx.finish();
      return false;
    }
    return true;
  }
}
由此我們發現,它的作用主要是將當前Activity的資訊傳遞給InitActivity,並把當前頁面finish掉。再看一下InitActivity
public class InitActivity extends Activity {
  public static final String FROM_ME = "fromVitamioInitActivity";
  private ProgressDialog mPD;
  private UIHandler uiHandler;

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    uiHandler = new UIHandler(this);

    new AsyncTask<Object, Object, Boolean>() {
      @Override
      protected void onPreExecute() {
        mPD = new ProgressDialog(InitActivity.this);
        mPD.setCancelable(false);
        mPD.setMessage(InitActivity.this.getString(getResources().getIdentifier("vitamio_init_decoders", "string", getPackageName())));
        mPD.show();
      }

      @Override
      protected Boolean doInBackground(Object... params) {
      //進行初始化操作
        return Vitamio.initialize(InitActivity.this, getResources().getIdentifier("libarm", "raw", getPackageName()));
      }

      @Override
      protected void onPostExecute(Boolean inited) {
        if (inited) {
          uiHandler.sendEmptyMessage(0);
        }
      }

    }.execute();
  }

  private static class UIHandler extends Handler {
    private WeakReference<Context> mContext;

    public UIHandler(Context c) {
      mContext = new WeakReference<Context>(c);
    }

    public void handleMessage(Message msg) {
      InitActivity ctx = (InitActivity) mContext.get();
      switch (msg.what) {
        case 0:
          ctx.mPD.dismiss();
          Intent src = ctx.getIntent();
          Intent i = new Intent();
          i.setClassName(src.getStringExtra("package"), src.getStringExtra("className"));
          i.setData(src.getData());
          i.putExtras(src);
          i.putExtra(FROM_ME, true);
          ctx.startActivity(i);
          ctx.finish();
          break;
      }
    }
  }
}

原來初始化的操作放在了這裡,開啟了一個非同步任務進行初始化操作,初始化成功後通過handler傳送一個空訊息,然後執行操作,跳轉到之前的那個頁面(資訊都傳過來了),並關閉InitActivity。這裡有兩個地方比較疑惑

  • 為什麼要加一個handler呢,onPostExecute裡面的就是執行在UI執行緒的吧
  • Vitamio.initialize(Context ctx, int rawId)方法裡面,兩個條件問什麼是或的關係,明明其中一個失敗就能導致初始化失敗
 /**
   * Same as {@link #initialize(Context)}
   *
   * @param ctx   Android Context
   * @param rawId R.raw.libarm
   * @return true if the Vitamio initialized successfully.
   */
  public static boolean initialize(Context ctx, int rawId) {
    return isInitialized(ctx) || extractLibs(ctx, rawId);
  }
補充:如果不是通過定義靜態內部類的方法使用handler,是有可能造成記憶體洩漏的。

迴歸正題,通過以上我們發現了一個引數rawId,預設值是R.raw.libarm。但是我建立專案的時候res下並沒有建立raw資料夾。對比了一下demo,發現別人的確實有這麼個資料夾,裡面放了libarm.so檔案。(vitamio庫裡面也有這個,不太清楚為什麼自己module裡面也要加上) 重新編譯執行,可以正常播放視訊。

重新回到最初的問題,我總不能為了使用vitamio而放棄使用高版本的api吧。搜尋一番無果後,我打開了vitamio的官網,看到了新版本5.2.3,並且已經說明支援Android6.0。(這時我想拍死自己),既然有新的了,那麼重新整合,這次我把targetSdkVersion設為了27,編譯執行,成功。

補充:我將兩個版本的demo做了下對比。新的demo中不在需要res/raw檔案夾了,並且刪除了LibsChecker,同時InitActivity裡面不再執行初始化操作了。(感覺這個類也失去了存在的意義,不過沒刪除而已)

我們做初始化操作,由

if (!LibsChecker.checkVitamioLibs(this))
    return;

變成了

Vitamio.isInitialized(this);

activity由以前的啟動2次變成了1次。

4.2.1版本:VideoViewActivity(start)->LibsChecker.checkVitamioLibs(this)->InitActivity(start),VideoViewActivity(finish)->Vitamio.initialize(this)->VideoViewActivity(start),InitActivity(finish)->setContentView()

5.2.3版本:VideoViewActivity(start)->Vitamio.isInitialized(this)->setContentView()