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