Android實戰——Tinker的整合和使用
前言
對於熱修復我相信很多小夥伴都已經知道它們普遍的操作套路,Tinker主要是依賴自己的gradlePlugin生成拆分包,所以其拆分包的生成就由Gradle來完成,當然也可以通過命令列的方式,這裡就不對命令列做講解,Tinker接入指南
專案結構
Tinker介紹
來自Tinker官方
1、優點
2、缺點
- Tinker不支援修改AndroidManifest.xml,Tinker不支援新增四大元件(1.9.0支援新增非export的Activity);
- 由於Google Play的開發者條款限制,不建議在GP渠道動態更新程式碼;
- 在Android N上,補丁對應用啟動時間有輕微的影響;
- 不支援部分三星android-21機型,載入補丁時會主動丟擲”TinkerRuntimeException:checkDexInstall failed”;
- 對於資源替換,不支援修改remoteView。例如transition動畫,notification icon以及桌面圖示。
Tinker整合
1、在專案的build.gradle中,新增依賴
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
// Tinker
classpath ("com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}")
}
}
這裡的TINKER_VERSION寫在專案gradle.properties中
TINKER_VERSION=1.7.7
2、在app的build.gradle檔案,新增依賴
provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}")
compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION} ")
compile "com.android.support:multidex:1.0.1"
新增依賴以後,我們在gradle檔案中做以下配置
- 開啟Multidex、配置Java編譯的版本
- 配置簽名檔案,為了後面打包方便除錯
- 引入另一個gradle檔案專門來對Tinker生成拆分包的配置(由於多渠道要用到gradle的引數,所以將引入放在末尾)
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.handsome.thinker"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
dexOptions {
jumboMode = true
}
signingConfigs {
debug {
keyAlias 'hensen'
keyPassword '123456'
storeFile file("../Hensen.jks")
storePassword '123456'
}
release {
keyAlias 'hensen'
keyPassword '123456'
storeFile file("../Hensen.jks")
storePassword '123456'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
minifyEnabled false
signingConfig signingConfigs.debug
}
}
}
// 加入Tinker生成補丁包的gradle
apply from: 'buildTinker.gradle'
3、buildTinker.gradle是專門為Tinker配置和生成拆分包而寫的,具體可以參考官方gradle
//指定生成apk檔案的存放位置
def bakPath = file("${buildDir}/bakApk/")
//引數配置
ext {
//開啟Tinker
tinkerEnable = true
//舊的apk位置,需要我們手動指定
tinkerOldApkPath = "${bakPath}/"
//舊的混淆對映位置,如果開啟了混淆,則需要我們手動指定
tinkerApplyMappingPath = "${bakPath}/"
//舊的resource位置,需要我們手動指定
tinkerApplyResourcePath = "${bakPath}/"
tinkerID = "1.0"
}
def buildWithTinker() {
return ext.tinkerEnable
}
def getOldApkPath() {
return ext.tinkerOldApkPath
}
def getApplyMappingPath() {
return ext.tinkerApplyMappingPath
}
def getApplyResourceMappingPath(){
return ext.tinkerApplyResourcePath
}
def getTinkerIdValue(){
return ext.tinkerID
}
if (buildWithTinker()) {
apply plugin: 'com.tencent.tinker.patch'
tinkerPatch {
oldApk = getOldApkPath() //指定old apk檔案路徑
ignoreWarning = false //不忽略tinker警告,出現警告則中止patch檔案生成
useSign = true //patch檔案必須是簽名後的
tinkerEnable = buildWithTinker() //指定是否啟用tinker
buildConfig {
applyMapping = getApplyMappingPath() //指定old apk打包時所使用的混淆檔案
applyResourceMapping = getApplyResourceMappingPath() //指定old apk的資原始檔
tinkerId = getTinkerIdValue() //指定TinkerID
keepDexApply = false
}
dex {
dexMode = "jar" //jar、raw
pattern = ["classes*.dex", "assets/secondary-dex-?.jar"] //指定dex檔案目錄
loader = ["com.handsome.thinker.AppLike.MyTinkerApplication"] //指定載入patch檔案時用到的類
}
lib {
pattern = ["libs/*/*.so"] //指定so檔案目錄
}
res {
pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] //指定資原始檔目錄
ignoreChange = ["assets/sample_meta.txt"] //指定不受影響的資源路徑
largeModSize = 100 //資源修改大小預設值
}
packageConfig {
configField("patchMessage", "fix the 1.0 version's bugs")
configField("patchVersion", "1.0")
}
}
/**
* 是否配置了多渠道
*/
List<String> flavors = new ArrayList<>();
project.android.productFlavors.each { flavor ->
flavors.add(flavor.name)
}
boolean hasFlavors = flavors.size() > 0
/**
* 複製apk包和其它必須檔案到指定目錄
*/
android.applicationVariants.all { variant ->
/**
* task type, you want to bak
*/
def taskName = variant.name
def date = new Date().format("yyyy-MM-dd-HH-mm-ss")
tasks.all {
if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
it.doLast {
copy {
def fileNamePrefix = "${project.name}-${variant.baseName}"
def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"
def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
from variant.outputs.outputFile
into destPath
rename { String fileName ->
fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
}
from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
into destPath
rename { String fileName ->
fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
}
from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
into destPath
rename { String fileName ->
fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
}
}
}
}
}
}
}
4、記得開啟Manifest許可權,否則生成拆分包的時候有奇怪錯誤
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Tinker封裝
我們提供兩個方法來初始化Tinker
- 預設的方式
- 自定義模組的方式
public class TinkerManager {
private static boolean isInstalled = false;
// 這裡的ApplicationLike可以理解為Application的載體
private static ApplicationLike mAppLike;
private static CustomPatchListener mPatchListener;
/**
* 預設初始化Tinker
*
* @param applicationLike
*/
public static void installTinker(ApplicationLike applicationLike) {
mAppLike = applicationLike;
if (isInstalled) {
return;
}
TinkerInstaller.install(mAppLike);
isInstalled = true;
}
/**
* 初始化Tinker,帶有自定義模組
* <p>
* 1、CustomPatchListener
* 2、CustomResultService
*
* @param applicationLike
* @param md5Value 伺服器下發的md5
*/
public static void installTinker(ApplicationLike applicationLike, String md5Value) {
mAppLike = applicationLike;
if (isInstalled) {
return;
}
mPatchListener = new CustomPatchListener(getApplicationContext());
mPatchListener.setCurrentMD5(md5Value);
// Load補丁包時候的監聽
LoadReporter loadReporter = new DefaultLoadReporter(getApplicationContext());
// 補丁包載入時候的監聽
PatchReporter patchReporter = new DefaultPatchReporter(getApplicationContext());
AbstractPatch upgradePatchProcessor = new UpgradePatch();
TinkerInstaller.install(applicationLike,
loadReporter,
patchReporter,
mPatchListener,
CustomResultService.class,
upgradePatchProcessor);
isInstalled = true;
}
/**
* 增加補丁包
*
* @param path
*/
public static void addPatch(String path) {
if (Tinker.isTinkerInstalled()) {
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path);
}
}
/**
* 獲取上下文
*
* @return
*/
private static Context getApplicationContext() {
if (mAppLike != null) {
return mAppLike.getApplication().getApplicationContext();
}
return null;
}
}
由於Tinker預設Patch檢查是沒有對檔案做Md5校驗,我們可以重寫其檢驗的方法,加上我們自己的檢驗邏輯(需要自定義模組的方式初始化Tinker)
CustomPatchListener.java
public class CustomPatchListener extends DefaultPatchListener {
private String currentMD5;
public void setCurrentMD5(String md5Value) {
this.currentMD5 = md5Value;
}
public CustomPatchListener(Context context) {
super(context);
}
/**
* patch的檢測
*
* @param path
* @return
*/
@Override
protected int patchCheck(String path) {
//MD5校驗的工具可以網上查詢
//這裡要求我們在初始化Tinker的時候加上MD5的引數
//增加patch檔案的md5較驗
if (!MD5Utils.isFileMD5Matched(path, currentMD5)) {
return ShareConstants.ERROR_PATCH_DISABLE;
}
return super.patchCheck(path);
}
}
由於Tinker預設安裝完補丁包之後是刪除補丁包,然後殺掉程序的方式,我們可以修改殺掉程序的行為(需要自定義模組的方式初始化Tinker)
CustomResultService.java
public class CustomResultService extends DefaultTinkerResultService {
private static final String TAG = "Tinker.SampleResultService";
/**
* patch檔案的最終安裝結果,預設是安裝完成後殺掉自己程序
* 此段程式碼主要是複製DefaultTinkerResultService的程式碼邏輯
*/
@Override
public void onPatchResult(PatchResult result) {
if (result == null) {
TinkerLog.e(TAG, "DefaultTinkerResultService received null result!!!!");
return;
}
TinkerLog.i(TAG, "DefaultTinkerResultService received a result:%s ", result.toString());
//first, we want to kill the recover process
TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());
// if success and newPatch, it is nice to delete the raw file, and restart at once
// only main process can load an upgrade patch!
if (result.isSuccess) {
//刪除patch包
deleteRawPatchFile(new File(result.rawPatchFilePath));
//殺掉自己程序,如果不需要則可以註釋,在這裡做自己的邏輯
if (checkIfNeedKill(result)) {
android.os.Process.killProcess(android.os.Process.myPid());
} else {
TinkerLog.i(TAG, "I have already install the newly patch version!");
}
}
}
}
Tinker使用
1、Tinker的使用需要ApplicationLike來生成我們的Application,然後初始化Multidex和Tinker
@DefaultLifeCycle(application = ".MyTinkerApplication", flags = ShareConstants.TINKER_ENABLE_ALL, loadVerifyFlag = false)
public class CustomTinkerLike extends ApplicationLike {
public CustomTinkerLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
MultiDex.install(base);
TinkerManager.installTinker(this);
}
}
2、編譯專案自動生成Application,然後在Manifest中指定我們的生成的Application
<application
android:name=".AppLike.MyTinkerApplication"
3、在主頁面按鈕的點選事件,來載入放在快取目錄下的補丁包
public class MainActivity extends AppCompatActivity {
private String mPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPath = getExternalCacheDir().getAbsolutePath() + File.separatorChar;
}
/**
* 載入Tinker補丁
*
* @param view
*/
public void Fix(View view) {
File patchFile = new File(mPath, "patch_signed.apk");
if (patchFile.exists()) {
TinkerManager.addPatch(patchFile.getAbsolutePath());
Toast.makeText(this, "File Exists,Please wait a moment ", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "File No Exists", Toast.LENGTH_SHORT).show();
}
}
}
Tinker測試
完成Tinker的所有準備工作後,我們通過預設的初始化Tinker方式測試我們的補丁包
1、找到gradle工具欄,點選生成Release包,作為1.0版本的程式
2、將生成的Release包Push到手機上,安裝,執行程式
生成apk的目錄在build的bakApk目錄下
執行程式
3、在專案中,對主介面新增載入圖片的按鈕,同時新增一個drawable檔案
public class MainActivity extends AppCompatActivity {
private String mPath;
private ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv);
mPath = getExternalCacheDir().getAbsolutePath() + File.separatorChar;
}
/**
* 載入Tinker補丁
*
* @param view
*/
public void Fix(View view) {
File patchFile = new File(mPath, "patch_signed.apk");
if (patchFile.exists()) {
TinkerManager.addPatch(patchFile.getAbsolutePath());
Toast.makeText(this, "File Exists,Please wait a moment ", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "File No Exists", Toast.LENGTH_SHORT).show();
}
}
/**
* 新增的按鈕點選事件
*
* @param view
*/
public void Load(View view) {
iv.setImageResource(R.drawable.bg_content_header);
}
}
4、同時記得修改buildTinker.gradle的old安裝包的路徑,Tinker需要比對前後安裝包然後生成補丁包
//引數配置
ext {
//開啟Tinker
tinkerEnable = true
//舊的apk位置,需要我們手動指定
tinkerOldApkPath = "${bakPath}/app-release-2017-11-19-18-34-12.apk"
//舊的混淆對映位置,如果開啟了混淆,則需要我們手動指定
tinkerApplyMappingPath = "${bakPath}/"
//舊的resource位置,需要我們手動指定
tinkerApplyResourcePath = "${bakPath}/app-release-2017-11-19-18-34-12-R.txt"
tinkerID = "1.0"
}
5、找到gradle工具欄,點選thinker生成Release補丁包,作為1.0版本的補丁
6、將生成的Release補丁包Push到手機的快取目錄上,執行程式點選修復補丁包,稍等數秒程式會被殺掉,重啟點選載入圖片按鈕
生成的補丁包
記得將補丁放到快取目錄下,修復補丁後的程式
Tinker多渠道打包
1、Tinker支援多渠道打包,我們採用友盟的打包方式,下載友盟SDK,將jar包增加到專案上
2、初始化友盟SDK(新版本的SDK似乎不用初始化了,找不到初始化入口)
3、Manifest增加友盟的AppKey配置和渠道配置
<meta-data
android:name="UMENG_APPKEY"
android:value="5a116bbea40fa33cf9000150" />
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
4、在app的build.gradle中增加多渠道打包資訊
/**
* 配置多渠道
*/
productFlavors {
googleplayer {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "googleplayer"]
}
baidu {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
}
wangdoujia {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wangdoujia"]
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}
5、在buildTinker.gradle增加配置多渠道補丁包的生成規則
/**
* 生成多渠道補丁包
*/
project.afterEvaluate {
if (hasFlavors) {
task(tinkerPatchAllFlavorRelease) {
group = 'tinker'
def originOldPath = getTinkerBuildFlavorDirectory()
for (String flavor : flavors) {
def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")
dependsOn tinkerTask
def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")
preAssembleTask.doFirst {
String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"
project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"
project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"
}
}
}
task(tinkerPatchAllFlavorDebug) {
group = 'tinker'
def originOldPath = getTinkerBuildFlavorDirectory()
for (String flavor : flavors) {
def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")
dependsOn tinkerTask
def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")
preAssembleTask.doFirst {
String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"
project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"
}
}
}
}
}
6、找到gradle工具欄,點選生成Release包,作為1.0版本的程式
7、同時記得修改buildTinker.gradle的old安裝包的路徑,Tinker需要比對前後安裝包然後生成補丁包
//引數配置
ext {
//開啟Tinker
tinkerEnable = true
//舊的apk位置,需要我們手動指定
tinkerOldApkPath = "${bakPath}/app-2017-11-19-20-35-23"
//舊的混淆對映位置,如果開啟了混淆,則需要我們手動指定
tinkerApplyMappingPath = "${bakPath}/app-2017-11-19-20-35-23"
//舊的resource位置,需要我們手動指定
tinkerApplyResourcePath = "${bakPath}/app-2017-11-19-20-35-23"
//舊的多渠道位置,需要我們手動指定
tinkerBuildFlavorDirectory = "${bakPath}/app-2017-11-19-20-35-23"
tinkerID = "1.0"
}
8、找到gradle工具欄,點選thinker生成Release補丁包,作為1.0版本的補丁
這裡對程式的修改就省略了
後面的測試更上面一樣,也就省略了
原始碼下載
結語
當野心大於現實的能力,只能默默的學習提升自己的能力,互相努力吧
相關推薦
Android實戰——Tinker的整合和使用
前言 對於熱修復我相信很多小夥伴都已經知道它們普遍的操作套路,Tinker主要是依賴自己的gradlePlugin生成拆分包,所以其拆分包的生成就由Gradle來完成,當然也可以通過命令列的方式,這裡就不對命令列做講解,Tinker接入指南 專案結構
Android實戰——輕鬆整合百度自動更新SDK,只需3步
輕鬆整合百度自動更新SDK,只需3步 整合百度自動更新SDK前提: 1、需要上線作品進行測試,本人用自己上線的作品進行了測試。 2、需要上線的作品defaultConfig中的versionCode
Android實戰簡易教程-第六十六槍(server端搭建和server端Json數據交互)
視頻 pack tis sta listen let Coding read ide 學習Android有一段時間了。對server端有非常深的好奇,決定對serve
Android實戰——Retrofit2的使用和封裝
請求 註解 請求頭 and retrofit 內容 部分 targe json 使用項目的原話:Android和Java中類型安全的HTTP客戶端 項目地址:https://github.com/square/retrofit 這裏Retrofit還需要導入它的Gson依賴
Android實戰——第三方服務之Bmob後端雲的推送服務的集成和使用(三)
第一篇 文章 href 第三方服務 log 集成 android實戰 https 分享 第三方服務之Bmob後端雲的推送服務的集成和使用(三) 事先說明:這裏的一切操作都是在集成了BmobSDK之後實現的,如果對Bmob還不了解的話,請關註我第一篇Bmob文章 步驟
cordova整合sencha touch建立APP專案,以及Android Studio專案匯入和打包
-------------------目錄結構------------------------------------------- \app
Android app快速整合Mob shareSDK分享到微信和QQ
Android app整合Mob shareSDK分享到微信和QQ 線上安裝方法介紹 注意需要用到的appKey和appSecret 請自行到http://www.mob.com官網申請,這裡不介紹申請過程了 進入如
【Ionic實戰】一個和AngularJS的跨平臺(iOS,Android) APP框架
關於 使用HTML5和CSS來開發手機應用,一直是廣大前端開發者的理想,並且已經有不少解決方案了。例如 PhoneGap(用javascript來呼叫裝置原生API)JQuery Mobile(UI庫)Titanium(混合方式)AppCan(國產的開發工具) Ioni
React Native for Android 實戰(一):配置和起步
原文地址: http://www.csdn.net/article/2015-09-24/2825787-react-native Facebook開源React Native也勢要統一移動端程式語言,而其提前釋出React Native for Android更是引
Android 融雲IMKit的整合和使用
1.整合 從官網下載SDK,這裡以Rong_Cloud_Android_IMKit_SDK_v2_8_7_Stable_8d65c為例 首先匯入IMKit和IMLib(IMKit以IMLib為基礎) Rong_Cloud_Android_IMKit_SDK_v2_8_7_S
關於android微信支付 和 支付寶支付的整合
最近專案中要整合微信和支付寶支付 在沒有接觸支付之間 覺得還是比較難的 但真正去實現的時候還是比較簡單的 就是有不少坑要去踩過之後才知道 支付寶支付 : 對於支付寶支付 我個人理解還是比較簡單的 https://open.alipay.com/platform/home.h
Tinker的整合和多渠道打包
Tinker是什麼 Tinker是微信官方的Android熱補丁解決方案,它支援動態下發程式碼、So庫以及資源,讓應用能夠在不需要重新安裝的情況下實現更新。當然,你也可以使用Tinker來更新你的外掛。 它主要包括以下幾個部分: 1.gradle編譯外掛: tinke
Android微信支付整合和踩過的坑
近公司需要微信支付,所以不得不去看看微信支付文件。但是你懂得,那文件寫的真帶勁,看不懂。我直接放棄,開始整合。但是調起微信支付的時候:結果碼為-1,心裡一驚,肯定哪裡錯了,就開始找坑。所以把自己解決的過程分享給大家,讓整合微信支付成為很容易的一件事。 2、我們需要的資源
Android實戰簡易教程-第六十六槍(伺服器端搭建和伺服器端Json資料互動)
學習Android有一段時間了,對伺服器端有很深的好奇,決定對伺服器端的實現進行一些研究,這裡實現了一個簡單的小例子,用於獲取伺服器端的json資料,例子很簡單,適合初學者學習使用。伺服器端首先我們搭建伺服器端,伺服器端使用struct2架構,對該架構不熟悉的人可以花一點時間
Facebook Android整合和開發
關鍵字:Facebook | Android studio 3.0 Facebook離線包如何整合?公司的環境決定了無法通過倉庫的方法進行整合,因此只能下載Facebook SDK進行整合。 本文講解了Android Studio 3.0中離線整合Faceb
Tinker整合步驟和整合中所需要的問題
整合Tinker所需要的問題: 1,複製demo中的build.gradle 修改完成以後出現 Error:(9, 0) Could not get unknown property 'TINKER_VERSION' for object of type
Android Codec 整合和 video Overlay
Codec整合和video overlay是現在FSL對android多媒體修改的所有東西,codec library以.so的形式放在prebuilt目錄下,沒有原始檔。而video overlay的實現主要是使用了FSL的ipu底層庫,將視訊資料直接傳送到硬體,由硬體
Android使用EaseUI整合環信3.0 設定頭像和使用者名稱 三
整合環信看了很多文件,還是不會,從官網下載的demo,也不會用,後來才發現應該下載官網的sdk,裡面有個examples的資料夾,使用studio直接開啟裡面的ChatDemoUI3.0,就是一個可以執行的demo.執行的時候會報一個異常 Error:Ex
Android實戰技巧:為從右向左語言定義複雜字串,程式碼和xml設定
程式碼方式,一般是放在一個Utils.java作為公共方法 /// add by xxx.zhou for ArabicRTL support 20141024 begin public static boolean isContainEG_I
Android Studio中git的整合和使用
一、下載安裝git安裝到本地目錄 二、Android Studio的配置 1、android studio找到setting 右上角test配置目錄,git的安裝路徑,點選右上角test出現suc