国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Android中的全量更新、增量更新以及熱更新

這篇具有很好參考價值的文章主要介紹了Android中的全量更新、增量更新以及熱更新。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

在客戶端開發(fā)過程中,我們可能會遇到這樣一種需求:點擊某個按鈕彈出一個彈窗,提示我們可以更新到apk的某個版本,或者我們可以通過服務(wù)端接口進(jìn)行強制更新。在這種需求中,我們是不需要通過應(yīng)用商店來更新我們的apk的,而是直接在apk內(nèi)部進(jìn)行版本更新。這次我們就來看看實現(xiàn)這種應(yīng)用內(nèi)更新的幾種方式。當(dāng)然,這種玩法只能在國內(nèi)玩,海外的話會被Googleplay據(jù)審的。如果是海外的應(yīng)用要更新apk,只能在GooglePlay上上傳新版本的包。

全量更新

什么是全量更新呢?舉個例子,假設(shè)現(xiàn)在用戶手機上的apk是1.0版本,如果想要升級到2.0版本,全量更新的處理方式則是把2.0版本的apk全部下載下來進(jìn)行覆蓋安裝。那么,我們該如果設(shè)計一個合理的全量更新方案呢?

  • 服務(wù)端
    需要提供一個接口,這個接口返回來的body中包含新版本的包的下載地址以及該包的md5值用于下載完成之后進(jìn)行校驗用
  • 客戶端
    訪問該服務(wù)端接口,下載新版本的包(其實就是字節(jié)流的讀寫),然后進(jìn)行覆蓋安裝

做完上面這2點其實就可以實現(xiàn)一個較為完整的全量更新功能。

客戶端核心代碼如下:

package com.mvp.myapplication.update;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;

import androidx.core.content.FileProvider;

import com.mvp.myapplication.utils.MD5Util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class UpdateService extends Service {

    public static final String KEY_MD5 = "MD5";
    public static final String URL = "downloadUrl";


    private boolean startDownload;//開始下載
    public static final String TAG = "UpdateService";
    private DownloadApk downloadApkTask;
    private String downloadUrl;
    private String mMd5;
    private UpdateProgressListener updateProgressListener;
    private LocalBinder localBinder = new LocalBinder();

    public class LocalBinder extends Binder {
        public void setUpdateProgressListener(UpdateProgressListener listener) {
            UpdateService.this.setUpdateProgressListener(listener);
        }
    }

    private void setUpdateProgressListener(UpdateProgressListener listener) {
        this.updateProgressListener = listener;
    }

    /**
     * 獲取FileProvider的auth
     */
    private static String getFileProviderAuthority(Context context) {
        try {
            for (ProviderInfo provider : context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS).providers) {
                if (FileProvider.class.getName().equals(provider.name) && provider.authority.endsWith(".update_app.file_provider")) {
                    return provider.authority;
                }
            }
        } catch (PackageManager.NameNotFoundException ignore) {
        }
        return null;
    }

    private static Intent installIntent(Context context, String path) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Uri fileUri = FileProvider.getUriForFile(context, getFileProviderAuthority(context), new File(path));
            intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else {
            intent.setDataAndType(Uri.fromFile(new File(path)), "application/vnd.android.package-archive");
        }
        return intent;
    }

    public UpdateService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!startDownload && intent != null) {
            startDownload = true;
            mMd5 = intent.getStringExtra(KEY_MD5);
            downloadUrl = intent.getStringExtra(URL);

            downloadApkTask = new DownloadApk(this, mMd5);
            downloadApkTask.execute(downloadUrl);

        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return localBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return true;
    }

    @Override
    public void onDestroy() {
        if (downloadApkTask != null) {
            downloadApkTask.cancel(true);
        }
        if (updateProgressListener != null) {
            updateProgressListener = null;
        }
        super.onDestroy();
    }

    private static String getSaveFileName(String downloadUrl) {
        if (downloadUrl == null || TextUtils.isEmpty(downloadUrl)) {
            return System.currentTimeMillis() + ".apk";
        }
        return downloadUrl.substring(downloadUrl.lastIndexOf("/"));
    }

    private static File getDownloadDir(UpdateService service) {
        File downloadDir = null;
        if (Environment.getExternalStorageDirectory().equals(Environment.MEDIA_MOUNTED)) {
            downloadDir = new File(service.getExternalCacheDir(), "update");
        } else {
            downloadDir = new File(service.getCacheDir(), "update");
        }
        if (!downloadDir.exists()) {
            downloadDir.mkdirs();
        }
        return downloadDir;
    }

    private void start() {
        if (updateProgressListener != null) {
            updateProgressListener.start();
        }
    }

    private void update(int progress) {
        if (updateProgressListener != null) {
            updateProgressListener.update(progress);
        }
    }

    private void success(String path) {
        if (updateProgressListener != null) {
            updateProgressListener.success(path);
        }
        Intent i = installIntent(this, path);
        startActivity(i);//自動安裝
        stopSelf();
    }

    private void error() {
        if (updateProgressListener != null) {
            updateProgressListener.error();
        }
        stopSelf();
    }

    private static class DownloadApk extends AsyncTask<String, Integer, String> {
        private final String md5;
        private UpdateService updateService;

        public DownloadApk(UpdateService service, String md5) {
            this.updateService = service;
            this.md5 = md5;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            if (updateService != null) {
                updateService.start();
            }
        }

        @Override
        protected String doInBackground(String... strings) {
            final String downloadUrl = strings[0];

            final File file = new File(UpdateService.getDownloadDir(updateService),
                    UpdateService.getSaveFileName(downloadUrl));

            Log.d(TAG, "download url is " + downloadUrl);
            Log.d(TAG, "download apk cache at " + file.getAbsolutePath());

            File dir = file.getParentFile();
            if (!dir.exists()) {
                dir.mkdirs();
            }

            HttpURLConnection httpConnection = null;
            InputStream is = null;
            FileOutputStream fos = null;
            long updateTotalSize = 0;
            URL url;
            try {
                url = new URL(downloadUrl);
                httpConnection = (HttpURLConnection) url.openConnection();
                httpConnection.setConnectTimeout(20000);
                httpConnection.setReadTimeout(20000);

                Log.d(TAG, "download status code: " + httpConnection.getResponseCode());


                if (httpConnection.getResponseCode() != 200) {
                    return null;
                }

                updateTotalSize = httpConnection.getContentLength();

                if (file.exists()) {
                    if (updateTotalSize == file.length()) {
                        // 下載完成
                        if (TextUtils.isEmpty(md5) || MD5Util.getMD5String(file).toUpperCase().equals(md5.toUpperCase())) {
                            return file.getAbsolutePath();
                        }
                    } else {
                        file.delete();
                    }
                }
                file.createNewFile();
                is = httpConnection.getInputStream();
                fos = new FileOutputStream(file, false);
                byte buffer[] = new byte[4096];

                int readSize = 0;
                long currentSize = 0;

                while ((readSize = is.read(buffer)) > 0) {
                    fos.write(buffer, 0, readSize);
                    currentSize += readSize;
                    publishProgress((int) (currentSize * 100 / updateTotalSize));
                }
                // download success
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            } finally {
                if (httpConnection != null) {
                    httpConnection.disconnect();
                }
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            try {
                if (TextUtils.isEmpty(md5) || MD5Util.getMD5String(file).toUpperCase().equals(md5.toUpperCase())) {
                    return file.getAbsolutePath();
                }
            } catch (IOException e) {
                e.printStackTrace();
                return file.getAbsolutePath();
            }
            Log.e(TAG, "md5 invalid");
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            if (updateService != null) {
                updateService.update(values[0]);
            }
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            if (updateService != null) {
                if (s != null) {
                    updateService.success(s);
                } else {
                    updateService.error();
                }
            }
        }
    }

    public static class Builder {
        private String downloadUrl;
        private String md5;
        private ServiceConnection serviceConnection;

        protected Builder(String downloadUrl) {
            this.downloadUrl = downloadUrl;
        }

        public static Builder create(String downloadUrl) {
            if (downloadUrl == null) {
                throw new NullPointerException("downloadUrl == null");
            }
            return new Builder(downloadUrl);
        }

        public String getMd5() {
            return md5;
        }

        public Builder setMd5(String md5) {
            this.md5 = md5;
            return this;
        }

        public Builder build(Context context, UpdateProgressListener listener) {
            if (context == null) {
                throw new NullPointerException("context == null");
            }
            Intent intent = new Intent();
            intent.setClass(context, UpdateService.class);
            intent.putExtra(URL, downloadUrl);
            intent.putExtra(KEY_MD5, md5);

            UpdateProgressListener delegateListener = new UpdateProgressListener() {
                @Override
                public void start() {
                    if (listener != null) {
                        listener.start();
                    }
                }

                @Override
                public void update(int var1) {
                    if (listener != null) {
                        listener.update(var1);
                    }
                }

                @Override
                public void success(String path) {
                    try {
                        context.unbindService(serviceConnection);
                    } catch (Throwable t) {
                        Log.e("UpdateService", "解綁失敗" + t.getMessage());
                    }

                    if (listener != null) {
                        listener.success(path);
                    }
                }

                @Override
                public void error() {
                    try {
                        context.unbindService(serviceConnection);
                    } catch (Throwable t) {
                        Log.e("UpdateService", "解綁失敗" + t.getMessage());
                    }
                    if (listener != null) {
                        listener.error();
                    }
                }
            };
            serviceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    LocalBinder binder = (LocalBinder) service;
                    binder.setUpdateProgressListener(delegateListener);
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                }
            };
            context.bindService(intent, serviceConnection, Context.BIND_IMPORTANT);
            context.startService(intent);
            return this;
        }
    }

    public interface UpdateProgressListener {
        void start();

        void update(int var);

        void success(String path);

        void error();
    }
}
package com.mvp.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.mvp.myapplication.update.UpdateService;

public class MainActivity extends AppCompatActivity {
    private Button btnAllUpdate, btnAddUpdate, btnHotUpdate;

    private String url,md5;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnAddUpdate = findViewById(R.id.btn_add_update);
        btnAllUpdate = findViewById(R.id.btn_all_update);
        btnHotUpdate = findViewById(R.id.btn_hot_update);

        Log.e("MainActivity","onCreate");

        btnAllUpdate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                UpdateService.Builder.create(url)
                        .setMd5(md5)
                        .build(MainActivity.this, new UpdateService.UpdateProgressListener() {
                            @Override
                            public void start() {
                                Log.e("MainActivity", "start");
                            }

                            @Override
                            public void update(int var) {
                                Log.e("MainActivity", "update ===> " + var);
                            }

                            @Override
                            public void success(String path) {
                                Log.e("MainActivity", "success ===> " + path);
                            }

                            @Override
                            public void error() {
                                Log.e("MainActivity", "error");
                            }
                        });
            }
        });
    }
}
  • AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        tools:targetApi="31">
        <service
            android:name=".update.UpdateService"
            android:enabled="true"
            android:exported="true"></service>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
        <!-- Android7以上需要 -->
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.update_app.file_provider"
            android:exported="false"
            android:grantUriPermissions="true"
            tools:replace="android:authorities">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/update_app_path" />
        </provider>
    </application>

</manifest>
  • update_app_path
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <cache-path
        name="update_app_cache_files"
        path="/update" />
    <external-path
        name="update_app_external_files"
        path="/" />
    <external-cache-path
        name="update_app_external_cache_files"
        path="/update" />
</paths>

熱更新

嚴(yán)格意義上來說,個人認(rèn)為熱更新并不是用來進(jìn)行包體升級,更多的用來進(jìn)行修復(fù)bug的。例如,由于某個程序員的失誤,在某個類中拋出了一個空指針異常,導(dǎo)致程序執(zhí)行到該類后一直崩潰。這種情況下,其實就可以使用熱更新來處理。因為,我們并沒有大改app中的功能,只是某個類報錯了。但個人認(rèn)為熱更新其實也不能解決所有的奔潰問題的,這些黑科技或多或少都是有一些兼容性的問題的,像Tinker就必須要冷啟動才能修復(fù),而且受限于Android的版本。
具體是技術(shù)實現(xiàn)方式可以參考筆者之前寫的一篇博客:Android熱修復(fù)1以及Android熱更新十:自己寫一個Android熱修復(fù)

增量更新

什么是增量更新呢?舉了例子,假設(shè)我們需要將apk從v1.0升級到v2.0,這時我們可以通過全量更新的方式,下載2.0版本的apk然后進(jìn)行覆蓋安裝。但是,一般情況下2.0版本的apk往往包含了1.0版本的功能,理論上我們只需要下載二者的差分包,然后將差分包與1.0版本的包進(jìn)行合并即可生成一個2.0版本的包。這樣做的好處自然就是節(jié)約了流量了。像幾乎所有的應(yīng)用商店都使用增量更新的方式來更新apk。那么,我們該我們使用增量更新呢?這個就要借助一個工具:bsdiff。
注意:如果想要使用增量更新,那么必須要有一個舊版本的apk,如果用戶安裝完apk后直接把舊版本的apk刪掉了,那么還是老老實實使用全量更新的方式吧。

  • 拆——拆分出差分包

bsdiff oldfile newfile1 patchfile

  • 合——將舊版本的包與差分包進(jìn)行合并

bspatch oldfile newfile2 patchfile

使用上面兩步便可以完成差分包的拆分與合并,新生成的newfile2 與newfile1的md5是一致的。但是上面這兩步法我們是在pc端進(jìn)行的,我們該如何在代碼中實現(xiàn)上面的邏輯呢?首先,拆分的邏輯還是在pc端中進(jìn)行,客戶端只需要關(guān)注如何合并差分包。
首先,我們需要導(dǎo)入bspatch相關(guān)的類
全量更新,android,java,android studio
接著,我們新建一個類用于調(diào)用c相關(guān)的代碼:

package com.mvp.myapplication.utils;

public class BSPatchUtil {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("bspatch");
    }

    /**
     * native方法 使用路徑為oldApkPath的apk與路徑為patchPath的補丁包,合成新的apk,并存儲于newApkPath
     *
     * 返回:0,說明操作成功
     *
     * @param oldApkPath 示例:/sdcard/old.apk
     * @param outputApkPath 示例:/sdcard/output.apk
     * @param patchPath  示例:/sdcard/xx.patch
     * @return
     */
    public static native int bspatch(String oldApkPath, String outputApkPath,
                                   String patchPath);

}

package com.mvp.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.mvp.myapplication.update.UpdateService;
import com.mvp.myapplication.utils.BSPatchUtil;

import java.io.File;

public class MainActivity extends AppCompatActivity {
    private Button btnAllUpdate, btnAddUpdate, btnHotUpdate;

    private String url, md5;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnAddUpdate = findViewById(R.id.btn_add_update);
        btnAllUpdate = findViewById(R.id.btn_all_update);
        btnHotUpdate = findViewById(R.id.btn_hot_update);

        Log.e("MainActivity", "onCreate");

        btnAllUpdate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                UpdateService.Builder.create(url)
                        .setMd5(md5)
                        .build(MainActivity.this, new UpdateService.UpdateProgressListener() {
                            @Override
                            public void start() {
                                Log.e("MainActivity", "start");
                            }

                            @Override
                            public void update(int var) {
                                Log.e("MainActivity", "update ===> " + var);
                            }

                            @Override
                            public void success(String path) {
                                Log.e("MainActivity", "success ===> " + path);
                            }

                            @Override
                            public void error() {
                                Log.e("MainActivity", "error");
                            }
                        });
            }
        });

        btnAddUpdate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                genNewApk();
            }
        });
    }

    private void genNewApk() {
        String oldpath = getApplicationInfo().sourceDir;
        String newpath = (this.getCacheDir().getAbsolutePath()+ File.separator
                + "composed_hivebox_apk.apk");

        String patchpath = (this.getCacheDir().getAbsolutePath()+ File.separator
                + "bs_patch");
        Log.e("MainActivity", "oldpath is " + oldpath + "\n newpath is " + newpath + "\n patchpath is " + patchpath);
        BSPatchUtil.bspatch(oldpath, newpath, patchpath);

    }
}

注意:需要修改bspatch.c文件中的Java_com_mvp_myapplication_utils_BSPatchUtil_bspatch方法簽名:改為BSPatchUtil 的包名,例如BSPatchUtil對應(yīng)的路徑為com.dxl.testbatch.util.BSPatchUtil,那么native方法簽名就是Java_com_mvp_myapplication_utils_BSPatchUtil_bspatch
這樣便可以通過jni調(diào)用到c層面的代碼。

接著,修改build.gradle文件,添加下面圈中的閉包
全量更新,android,java,android studio
最后,我們執(zhí)行Make Project命令,正常情況下便可以生成如下幾個so庫
全量更新,android,java,android studio
最后,我們在把so庫放入jniLibs文件夾中,然后build下生成apk包
全量更新,android,java,android studio

然后,我們將差分包bs_patch放入手機的data/data目錄下,點擊按鈕就會生成composed_hivebox_apk.apk這個apk包,將其與v2.0的包進(jìn)行MD5對比,發(fā)現(xiàn)是一致的。如此,我們便實現(xiàn)了一個簡單的增量更新邏輯。
Demo地址:https://gitee.com/hzulwy/add_-update/tree/master/MyApplication文章來源地址http://www.zghlxwxcb.cn/news/detail-654381.html

到了這里,關(guān)于Android中的全量更新、增量更新以及熱更新的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 離線數(shù)據(jù)倉庫-關(guān)于增量和全量

    離線數(shù)據(jù)倉庫-關(guān)于增量和全量

    應(yīng)用系統(tǒng)所產(chǎn)生的業(yè)務(wù)數(shù)據(jù)是數(shù)據(jù)倉庫的重要數(shù)據(jù)來源,我們需要每日定時從業(yè)務(wù)數(shù)據(jù)庫中抽取數(shù)據(jù),傳輸?shù)綌?shù)據(jù)倉庫中,之后再對數(shù)據(jù)進(jìn)行分析統(tǒng)計。 為了方便上層指標(biāo)的統(tǒng)計,數(shù)據(jù)的同步策略有 全量同步 和 增量同步 。 同步方式是針對對應(yīng)的表而言的! 為什么要做數(shù)據(jù)

    2024年01月17日
    瀏覽(38)
  • 如何選擇離線數(shù)據(jù)集成方案 - 全量&增量

    如何選擇離線數(shù)據(jù)集成方案 - 全量&增量

    1 前言 我在上一篇中介紹了實時集成與離線集成該怎么選擇,接著介紹一下離線集成中的增量與全量的選擇問題。 要設(shè)計方案,我們先分析一下數(shù)據(jù)產(chǎn)生的方式。我們把音視頻流這種非結(jié)構(gòu)化的數(shù)據(jù)集成從這里排除出去,因為這種音視頻流一般都是專業(yè)的廠商和系統(tǒng)來處理。

    2024年02月02日
    瀏覽(20)
  • 全量、增量數(shù)據(jù)在HBase遷移的多種技巧實踐

    全量、增量數(shù)據(jù)在HBase遷移的多種技巧實踐

    作者經(jīng)歷了多次基于HBase實現(xiàn)全量與增量數(shù)據(jù)的遷移測試,總結(jié)了在使用HBase進(jìn)行數(shù)據(jù)遷移的多種實踐,本文針對全量與增量數(shù)據(jù)遷移的場景不同,提供了1+2的技巧分享。 1.背景 在HBase使用過程中,使用的HBase集群經(jīng)常會因為某些原因需要數(shù)據(jù)遷移。大多數(shù)情況下,可以用離線

    2024年02月06日
    瀏覽(19)
  • 【環(huán)境配置】Android-Studio-OpenCV-JNI以及常見錯誤 ( 持續(xù)更新 )

    【環(huán)境配置】Android-Studio-OpenCV-JNI以及常見錯誤 ( 持續(xù)更新 )

    最近一個項目要編譯深度學(xué)習(xí)的庫,需要用到 opencv 和 JNI,本文檔用于記錄環(huán)境配置中遇到的常見錯誤以及解決方案 解決辦法: 刪除文件 .idea/gradle.xml 和 .idea/workspace.xml , 重新編譯; 解決辦法:Invalid Gradle JDK configuration found 原因是NDK版本過高,跟當(dāng)前的AndroidStudio版本不匹配

    2024年02月11日
    瀏覽(19)
  • Android中的SDK以及利用Android Studio生成aar

    Android中的SDK以及利用Android Studio生成aar

    廣義上的SDK: 指的是為特定的軟件包、軟件框架、硬件平臺、操作系統(tǒng)等建立應(yīng)用程序時所使用的開發(fā)工具的集合。 比如你在編輯器里敲代碼的時候它會自動補全代碼,自動錯誤檢查,你點一下Run,它會調(diào)用編譯器來自動編譯,編譯完它會調(diào)用iPhone的模擬器來運行,這就是

    2024年02月12日
    瀏覽(29)
  • Redis主從架構(gòu)、數(shù)據(jù)同步原理、全量同步、增量同步

    Redis主從架構(gòu)、數(shù)據(jù)同步原理、全量同步、增量同步

    大家好,我是哪吒。 2023年再不會Redis,就要被淘汰了 圖解Redis,談?wù)凴edis的持久化,RDB快照與AOF日志 Redis單線程還是多線程?IO多路復(fù)用原理 Redis集群的最大槽數(shù)為什么是16384個? Redis緩存穿透、擊穿、雪崩到底是個啥?7張圖告訴你 Redis分布式鎖的實現(xiàn)方式 Redis分布式緩存、

    2024年02月07日
    瀏覽(25)
  • 【大數(shù)據(jù)精講】全量同步與CDC增量同步方案對比

    【大數(shù)據(jù)精講】全量同步與CDC增量同步方案對比

    目錄 背景 名詞解釋 問題與挑戰(zhàn) FlinkCDC DataX 工作原理 調(diào)度流程 五、DataX 3.0六大核心優(yōu)勢 性能優(yōu)化 CDC ? ? ? ?CDC又稱變更數(shù)據(jù)捕獲(Change Data Capture),開啟cdc的源表在插入INSERT、更新UPDATE和刪除DELETE活動時會插入數(shù)據(jù)到日志表中。CDC通過捕獲進(jìn)程將變更數(shù)據(jù)捕獲到變更表中

    2024年01月24日
    瀏覽(19)
  • Oracle通過函數(shù)調(diào)用dblink同步表數(shù)據(jù)方案(全量/增量)

    Oracle通過函數(shù)調(diào)用dblink同步表數(shù)據(jù)方案(全量/增量)

    創(chuàng)建對應(yīng)的包,以方便觸發(fā)調(diào)用 觸發(fā)同步任務(wù): SELECT yjb.pkg_scene_job.F_SYNC_DRUG_STOCK() AS a FROM dual WHERE 1=0; 沒有結(jié)果行時是不會觸發(fā)的,以下方式可觸發(fā): SELECT yjb.pkg_scene_job.F_SYNC_DRUG_STOCK() AS a FROM dual; PS:一定是使用(調(diào)用)到 觸發(fā)函數(shù)yjb.pkg_scene_job.F_SYNC_DRUG_STOCK(),才可完成觸

    2024年02月16日
    瀏覽(30)
  • 什么是全量數(shù)據(jù)、增量數(shù)據(jù)?如何統(tǒng)一一套系統(tǒng)?

    一、什么是全量數(shù)據(jù)、增量數(shù)據(jù)? 1.全量數(shù)據(jù) 2.增量數(shù)據(jù) 二、如何統(tǒng)一一套系統(tǒng) 1.為什么需要統(tǒng)一一套系統(tǒng)來處理全量數(shù)據(jù)和增量數(shù)據(jù)? 2.如何實踐? 全量數(shù)據(jù)和增量數(shù)據(jù)是在數(shù)據(jù)庫系統(tǒng)遷移時的概念。 ? ? ? ? 當(dāng)前需要遷移的數(shù)據(jù)庫系統(tǒng)的全部數(shù)據(jù)。 ? ? ? ? 在數(shù)據(jù)庫系

    2024年02月05日
    瀏覽(26)
  • hive 全量表、增量表、快照表、切片表和拉鏈表

    hive 全量表、增量表、快照表、切片表和拉鏈表

    全量表 :記錄每天的所有的最新狀態(tài)的數(shù)據(jù), 增量表 :記錄每天的新增數(shù)據(jù),增量數(shù)據(jù)是上次導(dǎo)出之后的新數(shù)據(jù)。 快照表 :按日分區(qū),記錄截止數(shù)據(jù)日期的全量數(shù)據(jù) 切片表 :切片表根據(jù)基礎(chǔ)表,往往只反映某一個維度的相應(yīng)數(shù)據(jù)。其表結(jié)構(gòu)與基礎(chǔ)表結(jié)構(gòu)相同,但數(shù)據(jù)往往

    2024年02月13日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包