主頁 > 移動端開發 > Android開發 如何使用差分演算法實作增量更新支持新建四大組件

Android開發 如何使用差分演算法實作增量更新支持新建四大組件

2020-12-16 13:30:18 移動端開發

在當代App應用大小不斷增大的情況下增量更新代替全量更新已是趨勢,可以節省許多用戶流量,還是老樣子先上效果圖,由于上傳圖片限制壓縮有點嚴重湊合看吧:

如果大家想直接使用,我這里已經為大家封裝成library庫了,大家直接依賴庫就可以直接使用了非常簡單:

allprojects {
    repositories {
        
        maven { url('https://oranges.bintray.com/DiffPatch') }
    }
}
    implementation 'com.xhiston.diffpatch:diffpatch:1.0.0'
DiffPatchUtil diffPatchUtil = new DiffPatchUtil(context);

diffPatchUtil.setOnDiffClickListener(new DiffPatchUtil.OnDiffClickListener() {
            @Override
            public void onStart() {

            }

            @Override
            public void onSuccess() {
                diffPatchUtil.showMessage("Success");
            }

            @Override
            public void one rror(String msg) {
                diffPatchUtil.showMessage(msg);
            }
        });        

 diffPatchUtil.setOnPatchClickListener(new DiffPatchUtil.OnPatchClickListener() {
            @Override
            public void onStart() {

            }

            @Override
            public void onSuccess() {
                diffPatchUtil.showMessage("Success");
            }

            @Override
            public void one rror(String msg) {
                diffPatchUtil.showMessage(msg);
            }
        });       


File file = new File(newApk);
//打出差分補丁包路徑為patch
diffPatchUtil.diff(getApplicationInfo().sourceDir, newApk, patch);        

File file = new File(patch);       
//根據補丁包路徑patch合成新的apk路徑為patchApk       
diffPatchUtil.patch(getApplicationInfo().sourceDir, patchApk, patch);                        
                

當然還有不能忘記加入權限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

最后別忘了下載so庫匯入專案,如果專案中發現有問題隨時歡迎告知我及時修正,

如果準備自己手寫一個增量更新注意你的Android Api Level 為29以上的話請在manifest中加入以下一行代碼,否則就算打開了讀寫權限也會無法讀寫本地檔案,如果依賴的是我的lib就不用寫了因為我已經加入了本行代碼:

<application android:requestLegacyExternalStorage="true" />

如果大家想了解原理準備自己擼一個就往下看吧具體實作方式吧:

首先使用差分演算法bsdiff計算出差分包,感興趣的可以自己點擊進去下載原始碼,然后就是使用bzip2壓縮工具打包生成補丁差分包檔案和合并補丁包檔案;由于這里提供的都是C語言程式所以我們需要借助NDK/JNI實作增量更新了,

我們先去bsdiff地址下載bsdiff.c和bspatch.c這兩個檔案,然后去bzip2下載原始碼包解壓復制粘貼出我們需要的檔案:

                        bzip2/blocksort.c\
                   		bzip2/bzip2.c\
                    	bzip2/bzip2recover.c\
                    	bzip2/bzlib.c\
                    	bzip2/bzlib.h\
                    	bzip2/bzlib_private.h\
                    	bzip2/compress.c\
                    	bzip2/crctable.c\
                        bzip2/decompress.c\
                        bzip2/huffman.c\
                        bzip2/randtable.c

好了準備作業我們都做好了就開始創建JNI檔案吧:

1.創建一個java檔案例如:DiffPatchUtil,然后使用命令進入該檔案目錄下用命令編譯“javac DiffPatchUtil.java”生成DiffPatchUtil.class檔案,再執行“javah com.xhiston.diffpatch

.DiffPatchUtil”生成com_xhiston_diffpatch_DiffPatchUtil.h檔案這一步大家需要注意一下命令目錄回退一下到DiffPatchUtil的最外層包名下面不然命令提示找不包名下檔案,DiffPatchUtil.java創建的時候可以簡單的只放JNI回呼的相關方法,生成com_xhiston_diffpatch_DiffPatchUtil.h檔案后再修改添加其他方法,

package com.xhiston.diffpatch;

import android.content.Context;
import android.os.Looper;
import android.widget.Toast;

/**
 * Created by xie on 2020/10/20.
 */
public class DiffPatchUtil {

    static {
        System.loadLibrary("diffpatch");
    }
   
    /**
     * 采用差分演算法將當前包與新包打patch補丁包,生成xxx.patch檔案
     **/
    public native int diff(String oldApk, String newApk, String patch);

    /**
     * 采用差分演算法將patch補丁包與當前包合并生成新包,生成apk檔案
     **/
    public native int patch(String oldApk, String newApk, String patch);

}

com_xhiston_diffpatch_DiffPatchUtil.h檔案:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xhiston_diffpatch_DiffPatchUtil */

#ifndef _Included_com_xhiston_diffpatch_DiffPatchUtil
#define _Included_com_xhiston_diffpatch_DiffPatchUtil
#ifdef __cplusplus
extern "C" {
#endif
    int mybspatch(JNIEnv *env, jobject clazz,int argc, char *argv[]);

    int mybsdiff(JNIEnv *env, jobject clazz, int argc,char *argv[]);

    JNIEXPORT jint JNICALL Java_com_xhiston_diffpatch_DiffPatchUtil_patch
      (JNIEnv *, jobject, jstring, jstring, jstring);

    JNIEXPORT jint JNICALL Java_com_xhiston_diffpatch_DiffPatchUtil_diff
      (JNIEnv *, jobject, jstring, jstring, jstring);

#ifdef __cplusplus
}
#endif
#endif

2.創建com_xhiston_diffpatch_DiffPatchUtil.c以及JNI組態檔Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
APP_ABI := All
APP_PLATFORM := android-16
LOCAL_C_INCLUDES :=bzip2
LOCAL_MODULE    := diffpatch
LOCAL_SRC_FILES := com_xhiston_diffpatch_DiffPatchUtil.h\
				   	com_xhiston_diffpatch_DiffPatchUtil.c\
					bspatch.c\
					bsdiff.c\
					myerr.h\
                    myerr.c\
                    	bzip2/blocksort.c\
                   		bzip2/bzip2.c\
                    	bzip2/bzip2recover.c\
                    	bzip2/bzlib.c\
                    	bzip2/bzlib.h\
                    	bzip2/bzlib_private.h\
                    	bzip2/compress.c\
                    	bzip2/crctable.c\
                        bzip2/decompress.c\
                        bzip2/huffman.c\
                        bzip2/randtable.c\

include $(BUILD_SHARED_LIBRARY)

修改一下bspatch.c、bsdiff.c里的main方法名然后com_xhiston_diffpatch_DiffPatchUtil中就可以重新呼叫了,可以參考我的原始碼進行修改,確保代碼無誤后便可以ndk-buildd編譯生成so庫了,當然編譯的時候也會檢查代碼報錯的需要自行修改,不過這個就要求大家有一定的C語言基礎了,沒基礎的話可以現學一下不是多難,

com_xhiston_diffpatch_DiffPatchUtil.c:


#include <com_xhiston_diffpatch_DiffPatchUtil.h>

jint
Java_com_xhiston_diffpatch_DiffPatchUtil_patch (JNIEnv *env, jclass clazz, jstring old, jstring new, jstring patch) {
    int args=4;
    int resutlt = args;
    char *argv[args];
    argv[0] = "bspatch";
    argv[1] = (char*)((*env)->GetStringUTFChars(env, old, 0));
    argv[2] = (char*)((*env)->GetStringUTFChars(env, new, 0));
    argv[3] = (char*)((*env)->GetStringUTFChars(env, patch, 0));
    resutlt = mybspatch(env,clazz,args,argv);
    (*env)->ReleaseStringUTFChars(env,old, argv[1]);
    (*env)->ReleaseStringUTFChars(env,new, argv[2]);
    (*env)->ReleaseStringUTFChars(env,patch,argv[3]);
    return resutlt;
}


jint
Java_com_xhiston_diffpatch_DiffPatchUtil_diff (JNIEnv *env, jobject clazz, jstring old, jstring new, jstring patch) {
       int args=4;
       int resutlt = args;
       char *argv[args];
       argv[0] = "bsdiff";
       argv[1] = (char*)((*env)->GetStringUTFChars(env, old, 0));
       argv[2] = (char*)((*env)->GetStringUTFChars(env, new, 0));
       argv[3] = (char*)((*env)->GetStringUTFChars(env, patch, 0));
       resutlt= mybsdiff(env,clazz,args,argv);
       (*env)->ReleaseStringUTFChars(env,old, argv[1]);
       (*env)->ReleaseStringUTFChars(env,new, argv[2]);
       (*env)->ReleaseStringUTFChars(env,patch,argv[3]);
 return resutlt;
}

bspatch.c:

int mybspatch(JNIEnv *env, jobject clazz,int argc,char * argv[])
{
    onPatchStart(env,clazz);
	FILE * f, * cpf, * dpf, * epf;
	BZFILE * cpfbz2, * dpfbz2, * epfbz2;
	int cbz2err, dbz2err, ebz2err;
	int fd;
	ssize_t oldsize,newsize;
	ssize_t bzctrllen,bzdatalen;
	u_char header[32],buf[8];
	u_char *old, *new;
	off_t oldpos,newpos;
	off_t ctrl[3];
	off_t lenread;
	off_t i ;
    errno =0;
	if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);

	/* Open patch file */
	if ((f = fopen(argv[3], "r")) == NULL){
		errx(1, "fopen(%s)", argv[3]);
	    LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
		onPatchError(env,clazz,strerror(errno));
		return -1;
    }

	/* Read header */
	if (fread(header, 1, 32, f) < 32) {
		if (feof(f))
			err(1, "Corrupt patch\n");
		errx(1, "fread(%s)", argv[3]);
		LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
        onPatchError(env,clazz,strerror(errno));
        return -1;
	}

	/* Check for appropriate magic */
	if (memcmp(header, "BSDIFF40", 8) != 0)
		err(1, "Corrupt patch\n");

	/* Read lengths from header */
	bzctrllen=offtin(header+8);
	bzdatalen=offtin(header+16);
	newsize=offtin(header+24);
	if((bzctrllen<0) || (bzdatalen<0) || (newsize<0))
		err(1,"Corrupt patch\n");

	/* Close patch file and re-open it via libbzip2 at the right places */
	if (fclose(f))
		errx(1, "fclose(%s)", argv[3]);
	if ((cpf = fopen(argv[3], "r")) == NULL){
		errx(1, "fopen(%s)", argv[3]);
		LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
		onPatchError(env,clazz,strerror(errno));
		return -1;
	}
	if (fseeko(cpf, 32, SEEK_SET))
		errx(1, "fseeko( %lld)",  (long long)32);
	if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL)
		errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);
	if ((dpf = fopen(argv[3], "r")) == NULL){
		errx(1, "fopen(%s)", argv[3]);
		LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
		onPatchError(env,clazz,strerror(errno));
		return -1;
	}
	if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))
		errx(1, "fseeko( %lld)",  (long long)(32 + bzctrllen));
	if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL)
		errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);
	if ((epf = fopen(argv[3], "r")) == NULL){
		errx(1, "fopen(%s)", argv[3]);
		LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
        onPatchError(env,clazz,strerror(errno));
        return -1;
	}
	if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))
		errx(1, "fseeko(%lld)",  (long long)(32 + bzctrllen + bzdatalen));
	if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL)
		errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);

	if(((fd=open(argv[1],O_RDONLY,0))<0) ||
		((oldsize=lseek(fd,0,SEEK_END))==-1) ||
		((old=malloc(oldsize+1))==NULL) ||
		(lseek(fd,0,SEEK_SET)!=0) ||
		(read(fd,old,oldsize)!=oldsize) ||
		(close(fd)==-1)) errx(1,"當前檔案 %s",argv[1]);
	if((new=malloc(newsize+1))==NULL) err(1,NULL);
	oldpos=0;newpos=0;
	while(newpos<newsize) {
		/* Read control data */
		for(i=0;i<=2;i++) {
			lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);
			if ((lenread < 8) || ((cbz2err != BZ_OK) &&
			    (cbz2err != BZ_STREAM_END)))
				err(1, "Corrupt patch\n");
			ctrl[i]=offtin(buf);
		};

		/* Sanity-check */
		if(newpos+ctrl[0]>newsize)
			err(1,"Corrupt patch\n");

		/* Read diff string */
		lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]);
		if ((lenread < ctrl[0]) ||
		    ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))
			err(1, "Corrupt patch\n");

		/* Add old data to diff string */
		for(i=0;i<ctrl[0];i++)
			if((oldpos+i>=0) && (oldpos+i<oldsize))
				new[newpos+i]+=old[oldpos+i];

		/* Adjust pointers */
		newpos+=ctrl[0];
		oldpos+=ctrl[0];

		/* Sanity-check */
		if(newpos+ctrl[1]>newsize)
			err(1,"Corrupt patch\n");

		/* Read extra string */
		lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]);
		if ((lenread < ctrl[1]) ||
		    ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))
			err(1, "Corrupt patch\n");

		/* Adjust pointers */
		newpos+=ctrl[1];
		oldpos+=ctrl[2];
	};

	/* Clean up the bzip2 reads */
	BZ2_bzReadClose(&cbz2err, cpfbz2);
	BZ2_bzReadClose(&dbz2err, dpfbz2);
	BZ2_bzReadClose(&ebz2err, epfbz2);
	if (fclose(cpf) || fclose(dpf) || fclose(epf))
		errx(1, "fclose(%s)", argv[3]);

	/* Write the new file */
	if(((fd=open(argv[2],O_CREAT|O_TRUNC|O_WRONLY,0666))<0) ||
		(write(fd,new,newsize)!=newsize) || (close(fd)==-1))
		errx(1,"%s",argv[2]);

	free(new);
	free(old);
    LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
    if(errno == 0){
        onPatchSuccess(env,clazz);
    }else{
        onPatchError(env,clazz,strerror(errno));
        return -1;
    }
	return 0;
}

bsdiff.c:

int mybsdiff(JNIEnv *env,jobject clazz,int argc,char *argv[])
{
    onDiffStart(env,clazz);
	int fd;
	u_char *old,*new;
	off_t oldsize,newsize;
	off_t *I,*V;
	off_t scan,pos,len;
	off_t lastscan,lastpos,lastoffset;
	off_t oldscore,scsc;
	off_t s,Sf,lenf,Sb,lenb;
	off_t overlap,Ss,lens;
	off_t i;
	off_t dblen,eblen;
	u_char *db,*eb;
	u_char buf[8];
	u_char header[32];
	FILE * pf;
	BZFILE * pfbz2;
	int bz2err;
    errno =0;
	if(((fd=open(argv[1],O_RDONLY,0))<0) ||
    		((oldsize=lseek(fd,0,SEEK_END))==-1) ||
    		((old=malloc(oldsize+1))==NULL) ||
    		(lseek(fd,0,SEEK_SET)!=0) ||
    		(read(fd,old,oldsize)!=oldsize) ||
    		(close(fd)==-1)) errx(1,"%s 非設備檔案",argv[1]);
    LOG_E("設備檔案 %s",argv[1]);
	if(((I=malloc((oldsize+1)*sizeof(off_t)))==NULL) ||
		((V=malloc((oldsize+1)*sizeof(off_t)))==NULL)) err(1,"設備檔案長度為0");
	qsufsort(I,V,old,oldsize);
    free(V);

 	if(((fd=open(argv[2],O_RDONLY,0))<0) ||
 		((newsize=lseek(fd,0,SEEK_END))==-1) ||
 		((new=malloc(newsize+1))==NULL) ||
 		(lseek(fd,0,SEEK_SET)!=0) ||
 		(read(fd,new,newsize)!=newsize) ||
 		(close(fd)==-1)) errx(1,"%s 非存盤卡檔案",argv[2]);
 	if(((db=malloc(newsize+1))==NULL) ||
 		((eb=malloc(newsize+1))==NULL)) err(1,"存盤卡檔案長度為0");
 	LOG_E("存盤卡檔案 %s",argv[2]);
 	dblen=0;
 	eblen=0;
    if ((pf = fopen(argv[3], "w")) == NULL){
    	errx(1, "創建 %s 失敗", argv[3]);
        LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
    	onPatchError(env,clazz,strerror(errno));
    	return -1;
    }
    memcpy(header,"BSDIFF40",8);
	offtout(0, header + 8);
	offtout(0, header + 16);
	offtout(newsize, header + 24);
    if (fwrite(header, 32, 1, pf) != 1)
		errx(1, "fwrite(%s) 寫入失敗", argv[3]);
    if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
		errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
	scan=0;len=0;
	lastscan=0;lastpos=0;lastoffset=0;
    while(scan<newsize) {
		oldscore=0;
		for(scsc=scan+=len;scan<newsize;scan++) {
			len=search(I,old,oldsize,new+scan,newsize-scan,
					0,oldsize,&pos);

			for(;scsc<scan+len;scsc++)
			if((scsc+lastoffset<oldsize) &&
				(old[scsc+lastoffset] == new[scsc]))
				oldscore++;

			if(((len==oldscore) && (len!=0)) ||
				(len>oldscore+8)) break;

			if((scan+lastoffset<oldsize) &&
				(old[scan+lastoffset] == new[scan]))
				oldscore--;
		};

		if((len!=oldscore) || (scan==newsize)) {
			s=0;Sf=0;lenf=0;
			for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) {
				if(old[lastpos+i]==new[lastscan+i]) s++;
				i++;
				if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
			};

			lenb=0;
			if(scan<newsize) {
				s=0;Sb=0;
				for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
					if(old[pos-i]==new[scan-i]) s++;
					if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
				};
			};

			if(lastscan+lenf>scan-lenb) {
				overlap=(lastscan+lenf)-(scan-lenb);
				s=0;Ss=0;lens=0;
				for(i=0;i<overlap;i++) {
					if(new[lastscan+lenf-overlap+i]==
					   old[lastpos+lenf-overlap+i]) s++;
					if(new[scan-lenb+i]==
					   old[pos-lenb+i]) s--;
					if(s>Ss) { Ss=s; lens=i+1; };
				};

				lenf+=lens-overlap;
				lenb-=lens;
			};

			for(i=0;i<lenf;i++)
				db[dblen+i]=new[lastscan+i]-old[lastpos+i];
			for(i=0;i<(scan-lenb)-(lastscan+lenf);i++)
				eb[eblen+i]=new[lastscan+lenf+i];

			dblen+=lenf;
			eblen+=(scan-lenb)-(lastscan+lenf);

			offtout(lenf,buf);
			BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
			if (bz2err != BZ_OK)
				errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);

			offtout((scan-lenb)-(lastscan+lenf),buf);
			BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
			if (bz2err != BZ_OK)
				errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);

			offtout((pos-lenb)-(lastpos+lenf),buf);
			BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
			if (bz2err != BZ_OK)
				errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);

			lastscan=scan-lenb;
			lastpos=pos-lenb;
			lastoffset=pos-scan;
		};
	};

	BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
	if (bz2err != BZ_OK)
    		errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);

    /* Compute size of compressed ctrl data */
    if ((len = ftello(pf)) == -1)
    		err(1, "ftello");
    offtout(len-32, header + 8);

    /* Write compressed diff data */
    if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
    		errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
    BZ2_bzWrite(&bz2err, pfbz2, db, dblen);
    if (bz2err != BZ_OK)
    		errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
    	BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
    if (bz2err != BZ_OK)
    		errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);

    /* Compute size of compressed diff data */
   if ((newsize = ftello(pf)) == -1)
    		err(1, "ftello");
   offtout(newsize - len, header + 16);

   /* Write compressed extra data */
   if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
    		errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
   BZ2_bzWrite(&bz2err, pfbz2, eb, eblen);
    if (bz2err != BZ_OK)
    		errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
    BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
    if (bz2err != BZ_OK)
    		errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);

    /* Seek to the beginning, write the header, and close the file */
    if (fseeko(pf, 0, SEEK_SET))
    		err(1, "fseeko");
    if (fwrite(header, 32, 1, pf) != 1)
    		errx(1, "fwrite(%s)", argv[3]);
    if (fclose(pf))
    		err(1, "fclose");

    /* Free the memory we used */
    free(db);
    free(eb);
    free(I);
    free(old);
    free(new);
    LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
    if(errno == 0){
        onPatchSuccess(env,clazz);
    }else{
        onPatchError(env,clazz,strerror(errno));
        return -1;
    }
	return 0;
}

如果我的文章對你有幫助,"一鍵三連"給個鼓勵,讓我更新文章更有動力,“關注”我會有更多干貨不定時的更新哦!

原始碼

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/235525.html

標籤:其他

上一篇:Taro小程式自定義頂部導航欄

下一篇:性能測驗-性能狗(Perfdog)測驗與資料分析

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more