序言
騰訊瀏覽服務功能強大,穩(wěn)定屏富,集成還算是比較簡單的晴竞,比原生的webview強。最主要的是可以瀏覽PDF狠半,Word文檔噩死,方便不少颤难。此篇文章主要不是在講集成,雖然集成的篇幅多些已维,但是我寫的最重要的目的是我在實際使用過程中碰到的問題行嗤,以及解決方案。如果已經(jīng)成功集成的了垛耳,可直接看其他問題栅屏,可能會有你想要的。
基本上的話照著這個文檔接入是沒有什么問題的堂鲜,但是打開本地文件的時候栈雳,還是出現(xiàn)了一點小問題,因為文檔里面沒有說明缔莲。
基礎配置
現(xiàn)在的Android開發(fā)都使用Android Studio了哥纫,所以只需要在app的build.gradle里面添加依賴,這份文章的日期是2020/9/30酌予,最新id版本是下面這個
api 'com.tencent.tbs.tbssdk:sdk:43939'
權限配置
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
混淆配置
-dontwarn dalvik.**
-dontwarn com.tencent.smtt.**
-keep class com.tencent.smtt.** {
*;
}
-keep class com.tencent.tbs.** {
*;
}
異常上報配置
為了提高合作方的webview場景穩(wěn)定性磺箕,及時發(fā)現(xiàn)并解決x5相關問題,當客戶端發(fā)生crash等異常情況并上報給服務器時請務必帶上x5內核相關信息抛虫。x5內核異常信息獲取接口為:com.tencent.smtt.sdk.WebView.getCrashExtraMessage(context)松靡。以bugly日志上報為例:
UserStrategy strategy = new UserStrategy(appContext);
strategy.setCrashHandleCallback(new CrashReport.CrashHandleCallback() {
public Map<String, String> onCrashHandleStart(
int crashType,
String errorType,
String errorMessage,
String errorStack) {
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
String x5CrashInfo = com.tencent.smtt.sdk.WebView.getCrashExtraMessage(appContext);
map.put("x5crashInfo", x5CrashInfo);
return map;
}
@Override
public byte[] onCrashHandleStart2GetExtraDatas(
int crashType,
String errorType,
String errorMessage,
String errorStack) {
try {
return "Extra data.".getBytes("UTF-8");
} catch (Exception e) {
return null;
}
}
});
CrashReport.initCrashReport(appContext, APPID, true, strategy);
首次初始化冷啟動優(yōu)化
TBS內核首次使用和加載時,ART虛擬機會將Dex文件轉為Oat建椰,該過程由系統(tǒng)底層觸發(fā)且耗時較長雕欺,很容易引起anr問題,解決方法是使用TBS的 ”dex2oat優(yōu)化方案“棉姐。
(1). 設置開啟優(yōu)化方案
// 在調用TBS初始化屠列、創(chuàng)建WebView之前進行如下配置
HashMap map = new HashMap();
map.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true);
map.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true);
QbSdk.initTbsSettings(map);
(2). 增加Service聲明
- 在AndroidManifest.xml中增加內核首次加載時優(yōu)化Service聲明。
- 該Service僅在TBS內核首次Dex加載時觸發(fā)并執(zhí)行dex2oat任務伞矩,任務完成后自動結束笛洛。
<service
android:name="com.tencent.smtt.export.external.DexClassLoaderProviderService"
android:label="dexopt"
android:process=":dexopt" >
</service>
初始化X5內核
QbSdk.setDownloadWithoutWifi(true);
QbSdk.setTbsListener(
new TbsListener() {
@Override
public void onDownloadFinish(int i) {
Log.d("QbSdk", "onDownloadFinish -->下載X5內核完成:" + i);
}
@Override
public void onInstallFinish(int i) {
Log.d("QbSdk", "onInstallFinish -->安裝X5內核進度:" + i);
}
@Override
public void onDownloadProgress(int i) {
Log.d("QbSdk", "onDownloadProgress -->下載X5內核進度:" + i);
}
});
QbSdk.PreInitCallback cb =
new QbSdk.PreInitCallback() {
@Override
public void onViewInitFinished(boolean arg0) {
// x5內核初始化完成的回調,true表x5內核加載成功乃坤,否則表加載失敗苛让,會自動切換到系統(tǒng)內核。
Log.d("QbSdk", " 內核加載 " + arg0);
}
@Override
public void onCoreInitFinished() {}
};
// x5內核初始化接口
QbSdk.initX5Environment(getApplicationContext(), cb);
WebView接入
這部分其實就是將原生webview的東西全部換成帶有com.tencent.smtt包名的湿诊,TBS文檔上有介紹狱杰,這里就不做說明了。
文件接入
這個應該才是重點厅须。
在APP內部打開文件
APP內部打開使用的是TBS里面的TbsReaderView仿畸,在我Demo中有示例。可以看我的TBSDemo
這里有可能會碰到一個問題就是已經(jīng)在一個頁面使用TbsReaderView打開了文件错沽,但是跳轉到另一個界面的時候簿晓,還是使用TbsReaderView再打開文件,這時候就會顯示一直在加載中甥捺。目前我能想到的方法就是在onResume的時候重新初始化TbsReaderView抢蚀,
mFlRoot.removeAllViews();
mTbsReaderView = new TbsReaderView( mActivity, readerCallback );
mTbsReaderView.setBackgroundColor(
ContextCompat.getColor( mActivity, R.color.color_white ) );
mFlRoot.addView(
mTbsReaderView,
new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT ) );
調用打開文件的代碼
private void openFile() {
String extensionName = FileUtils.getFileType( mReaderFile.getPath() );
Bundle bundle = new Bundle();
bundle.putString( TbsReaderView.KEY_FILE_PATH, mReaderFile.getPath() );
bundle.putString( TbsReaderView.KEY_TEMP_PATH, FileUtils.createCachePath( mActivity ) );
boolean result = mTbsReaderView.preOpen( extensionName, false );
MLog.i( "addTbsReaderView: " + result );
if ( result ) {
mTbsReaderView.openFile( bundle );
}
}
在onPause的時候再調用。
if ( mTbsReaderView != null ) {
mTbsReaderView.onStop();
}
使用TBS的openFileReader打開文件
支持格式
TBS已提供9種主流文件格式的本地打開镰禾,如果您需要使用更高級的能力請使用QQ瀏覽器打開文件
- 接入TBS可支持打開文件格式:
doc皿曲、docx、ppt吴侦、pptx屋休、xls、xlsx备韧、pdf劫樟、txt、epub
- 調用QQ瀏覽器可打開:
rar(包含加密格式)织堂、zip(包含加密格式)叠艳、tar、bz2易阳、gz附较、7z(包含加密格式)、doc潦俺、docx拒课、ppt、pptx事示、xls早像、xlsx、txt肖爵、pdf卢鹦、epub、chm劝堪、html/htm法挨、xml、mht幅聘、url、ini窃植、log帝蒿、bat、php巷怜、js葛超、lrc暴氏、jpg、jpeg绣张、png答渔、gif、bmp侥涵、tiff 沼撕、webp、mp3芜飘、m4a务豺、aac、amr嗦明、wav笼沥、ogg、mid娶牌、ra奔浅、wma、mpga诗良、ape汹桦、flac
接口介紹
public static int openFileReader(
Context context,
String filePath,
HashMap<String, String> extraParams,
ValueCallback<String> callback
)
(1)此方法在Qbsdk
類下
(2)調用之后,優(yōu)先調起 QQ 瀏覽器打開文件累榜。如果沒有安裝 QQ 瀏覽器营勤,在 X5 內核下調起簡版 QB 打開文 件。如果使用的系統(tǒng)內核壹罚,則調起文件閱讀器彈框葛作。
(3)暫時只支持本地文件打開
context
: 調起 miniqb
的 Activity
的 context
。此參數(shù)只能是 activity
類型的 context
猖凛,不能設置為 Application
的 context赂蠢。 filePath
: 文件路徑。格式為 android
本地存儲路徑格式辨泳,例如:/sdcard/Download/xxx.doc
. 不支持 file:///
格式虱岂。暫不支持在線文件。 extraParams
: miniqb
的擴展功能菠红。為非必填項第岖,若無需特殊配置,默認可傳入null
试溯。擴展功能參考“文件功能定制” ValueCallback
: 提供 miniqb
打開/關閉時給調用方回調通知,以便應用層做相應處理蔑滓,您可以在出現(xiàn)以下回調時結束您的進程,節(jié)約內存占用。主要回調值如下:
filepath error
TbsReaderDialogClosed
default browser
fileReaderClosed
返回值:
1:用 QQ 瀏覽器打開
2:用 MiniQB 打開
3:調起閱讀器彈框
-1:filePath 為空 打開失敗
public static void closeFileReader(Context context)
主動關閉文件打開ui,并清理相應內存占用键袱。
接入示例
HashMap<String, String> params = new HashMap<String, String>();
params.put("style", "1");
params.put("local", "true");
params.put("memuData", jsondata);
QbSdk.openFileReader(ctx,”/sdcard/xxx.doc”, params,callback);
到這你以為就能順利打開瀏覽文件了嗎燎窘?還是太年輕了,騰訊的文檔不給你挖點坑蹄咖,讓你養(yǎng)成自己解決bug的習慣是不會罷休的褐健。當你滿心歡喜調用QbSdk.openFileReade的時候,log會給你報一個錯澜汤。
QbSdk: Must declare com.tencent.smtt.utils.FileProvider in AndroidManifest above Android 7.0,please view document in x5.tencent.com
意思就是讓你在AndroidManifest.xml中添加provider蚜迅,因為Android版本7.0訪問文件的方式改變了。這時候添加一下就可以了银亲。
首先在AndroidManifest.xml添加以下代碼
<provider
android:name="com.tencent.smtt.utils.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/x5webview_file_paths" />
</provider>
然后在res文件夾下創(chuàng)建xml文件夾慢叨,如果有了則跳過,接著再創(chuàng)建一個x5webview_file_paths.xml的文件务蝠,內容如下
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="sdcard" path="."/>
</paths>
再運行拍谐,應該是沒有問題的了,這里需要保證的是文件的路徑是要對的馏段,因為不能在線瀏覽轩拨,只能把文件下載到本地才能調用。這個錯誤我看了很久的文檔都沒有找到院喜,后面在網(wǎng)上搜索亡蓉,給出了以下的代碼,
<provider
android:name="com.tencent.smtt.utils.FileProvider"
android:authorities="包名.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/xml文件名" />
</provider>
這錯就錯在android:authorities="包名.fileprovider"喷舀,因為后來我去調用服務開啟的QbSdk的類中砍濒,看這個錯誤發(fā)生的時機,發(fā)現(xiàn)他們是通過類型+“.provider”來校驗的硫麻,所以只要改成android:authorities="包名.provider"就可以了爸邢,這個錯誤調試了很久,還是得在源碼中才能發(fā)現(xiàn)春天拿愧。
其他問題
使用TbsReaderView瀏覽文件夜間模式問題
我在使用小米10至尊版的時候杠河,我的APP中沒有適配深黑模式,使用的主題是Theme.AppCompat.Light.NoActionBar浇辜,但是小米10還是強制性的把我的APP使用了深黑模式券敌。并且我發(fā)現(xiàn),不止是我的APP柳洋,而是我手機上所有的APP都是如此待诅,不管有沒有適配,都使用了深黑模式熊镣,也就是夜間模式咱士。這就導致了一個問題立由,使用TbsReaderView來瀏覽文件的時候,背景是黑色的序厉,字體的顏色也是有點黑,導致文檔看的不清楚毕箍,后來我在閱讀TbsReaderView源碼的時候弛房,發(fā)現(xiàn)可以設置強制不使用夜間模式。加入以下代碼即可而柑。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//不使用黑暗模式
mTbsReaderView.setForceDarkAllowed(false);
}
targetAPI為29時
如果Android的targetSdkVersion是29的話文捶,需要在application下面配置android:requestLegacyExternalStorage="true"
targetAPI為Android P時無法下載內核?
由于內核下載安裝和查詢是否可用需要向后臺發(fā)送請求媒咳,目前還有部分請求為http格式粹排,當targetAPI為28時非Https請求將會被block,會導致部分內核功能異常涩澡。您可以手動降低targetAPi到27及以下或者在您的AndroidManifst.xml中的Application標簽中添加
android:networkSecurityConfig="@xml/network_security_config"
并在app的res/xml目錄中添加network_security_config.xml文件顽耳,文件內容為
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
由于內核初始化需要時間,在這段時間里需要等待一會再打開文件瀏覽妙同,否則會加載失敗射富。假如出現(xiàn)加載失敗,卸載重新安裝粥帚。
無法下載胰耗,或者加載不成功
QbSdk.setTbsListener(
new TbsListener() {
@Override
public void onDownloadFinish(int i) {
//下載結束時的狀態(tài),下載成功時errorCode為100,其他均為失敗芒涡,外部不需要關注具體的失敗原因
Log.d("QbSdk", "onDownloadFinish -->下載X5內核完成:" + i);
}
@Override
public void onInstallFinish(int i) {
//安裝結束時的狀態(tài)柴灯,安裝成功時errorCode為200,其他均為失敗,外部不需要關注具體的失敗原因
Log.d("QbSdk", "onInstallFinish -->安裝X5內核進度:" + i);
}
@Override
public void onDownloadProgress(int i) {
//下載過程的通知费尽,提供當前下載進度[0-100]
Log.d("QbSdk", "onDownloadProgress -->下載X5內核進度:" + i);
}
});
上面是下載的監(jiān)聽赠群,但是在實際中我經(jīng)常發(fā)現(xiàn)onDownloadFinish返回是110,或者120等依啰。官網(wǎng)上注明只有100是成功乎串。只要不是100,則X5內核加載肯定是失敗的速警。但是官網(wǎng)又沒有說如何解決叹誉。沒辦法只能自己找出路。在十分艱難閱讀了大部分帶有混淆的TBS的jar包后闷旧,連猜帶蒙的我找到了TbsDownloader.startDownload(this);這個方法长豁。可以實現(xiàn)重新下載忙灼,但是如果只是重新下載了還是不一定能保證x5的加載是一定成功的匠襟。所以我又找到了QbSdk.reset(this);這個方法钝侠。可以重置x5的配置酸舍。kill掉APP后就會重新下載跟初始化帅韧。在實際線上的使用情況是十分復雜的,有的人還沒等下載結束就把APPkill掉了啃勉,導致下載沒完成忽舟,或者是下載完成加載沒完成,所以只是使用TbsDownloader.startDownload(this);重新下載的話要結合QbSdk.setTbsListener里的回調淮阐,還有QbSdk.PreInitCallback的回調來綜合判斷叮阅。
QbSdk.PreInitCallback cb =
new QbSdk.PreInitCallback() {
@Override
public void onViewInitFinished(boolean arg0) {
// x5內核初始化完成的回調,true表x5內核加載成功泣特,否則表加載失敗浩姥,會自動切換到系統(tǒng)內核。
Log.d("QbSdk", " 內核加載 " + arg0);
}
@Override
public void onCoreInitFinished() {
//內核初始化完畢
Log.d("QbSdk", "內核初始化完畢");
}
};
QbSdk.reset(this);是最好的方法状您,簡單粗暴勒叠。可以查看我的demo竞阐,里面有我認為比較好的解決方法缴饭。當然還是得根據(jù)自身的需求要使用。
混淆無法使用
如果使用了混淆骆莹,則要加入以下混淆的規(guī)則
#-optimizationpasses 7
#-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-dontoptimize
-dontusemixedcaseclassnames
-verbose
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontwarn dalvik.**
-dontwarn com.tencent.smtt.**
#-overloadaggressively
# ------------------ Keep LineNumbers and properties ---------------- #
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
# --------------------------------------------------------------------------
# Addidional for x5.sdk classes for apps
-keep class com.tencent.smtt.export.external.**{
*;
}
-keep class com.tencent.tbs.video.interfaces.IUserStateChangedListener {
*;
}
-keep class com.tencent.smtt.sdk.CacheManager {
public *;
}
-keep class com.tencent.smtt.sdk.CookieManager {
public *;
}
-keep class com.tencent.smtt.sdk.WebHistoryItem {
public *;
}
-keep class com.tencent.smtt.sdk.WebViewDatabase {
public *;
}
-keep class com.tencent.smtt.sdk.WebBackForwardList {
public *;
}
-keep public class com.tencent.smtt.sdk.WebView {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.WebView$HitTestResult {
public static final <fields>;
public java.lang.String getExtra();
public int getType();
}
-keep public class com.tencent.smtt.sdk.WebView$WebViewTransport {
public <methods>;
}
-keep public class com.tencent.smtt.sdk.WebView$PictureListener {
public <fields>;
public <methods>;
}
-keepattributes InnerClasses
-keep public enum com.tencent.smtt.sdk.WebSettings$** {
*;
}
-keep public enum com.tencent.smtt.sdk.QbSdk$** {
*;
}
-keep public class com.tencent.smtt.sdk.WebSettings {
public *;
}
-keepattributes Signature
-keep public class com.tencent.smtt.sdk.ValueCallback {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.WebViewClient {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.DownloadListener {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.WebChromeClient {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.WebChromeClient$FileChooserParams {
public <fields>;
public <methods>;
}
-keep class com.tencent.smtt.sdk.SystemWebChromeClient{
public *;
}
# 1. extension interfaces should be apparent
-keep public class com.tencent.smtt.export.external.extension.interfaces.* {
public protected *;
}
# 2. interfaces should be apparent
-keep public class com.tencent.smtt.export.external.interfaces.* {
public protected *;
}
-keep public class com.tencent.smtt.sdk.WebViewCallbackClient {
public protected *;
}
-keep public class com.tencent.smtt.sdk.WebStorage$QuotaUpdater {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.WebIconDatabase {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.WebStorage {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.DownloadListener {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.QbSdk {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.QbSdk$PreInitCallback {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.CookieSyncManager {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.Tbs* {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.utils.LogFileUtils {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.utils.TbsLog {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.utils.TbsLogClient {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.CookieSyncManager {
public <fields>;
public <methods>;
}
# Added for game demos
-keep public class com.tencent.smtt.sdk.TBSGamePlayer {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.TBSGamePlayerClient* {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.TBSGamePlayerClientExtension {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.TBSGamePlayerService* {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.utils.Apn {
public <fields>;
public <methods>;
}
-keep class com.tencent.smtt.** {
*;
}
# end
-keep public class com.tencent.smtt.export.external.extension.proxy.ProxyWebViewClientExtension {
public <fields>;
public <methods>;
}
-keep class MTT.ThirdAppInfoNew {
*;
}
-keep class com.tencent.mtt.MttTraceEvent {
*;
}
# Game related
-keep public class com.tencent.smtt.gamesdk.* {
public protected *;
}
-keep public class com.tencent.smtt.sdk.TBSGameBooter {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.TBSGameBaseActivity {
public protected *;
}
-keep public class com.tencent.smtt.sdk.TBSGameBaseActivityProxy {
public protected *;
}
-keep public class com.tencent.smtt.gamesdk.internal.TBSGameServiceClient {
public *;
}
#---------------------------------------------------------------------------
#------------------ 下方是android平臺自帶的排除項颗搂,這里不要動 ----------------
-keep public class * extends android.app.Activity{
public <fields>;
public <methods>;
}
-keep public class * extends android.app.Application
{
public <fields>;
public <methods>;
}
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepattributes *Annotation*
-keepclasseswithmembernames class *{
native <methods>;
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#------------------ 下方是共性的排除項目 ----------------
# 方法名中含有“JNI”字符的,認定是Java Native Interface方法幕垦,自動排除
# 方法名中含有“JRI”字符的丢氢,認定是Java Reflection Interface方法,自動排除
-keepclasseswithmembers class * {
... *JNI*(...);
}
-keepclasseswithmembernames class * {
... *JRI*(...);
}
-keep class **JNI* {*;}
歡迎訂閱我的博客先改,堅持總是會看到希望的疚察。