Android 10 適配攻略小結
相比較去年寫的Android 9適配,這次Android 10的內(nèi)容有點多。沒想到寫了我整整兩天,吐血中。。。
準備工作
老規(guī)矩,首先將我們項目中的 targetSdkVersion
改為 29。
1.Scoped Storage(分區(qū)存儲) 說明
在Android 10之前的版本上,我們在做文件的操作時都會申請存儲空間的讀寫權限。但是這些權限完全被濫用,造成的問題就是手機的存儲空間中充斥著大量不明作用的文件,并且應用卸載后它也沒有刪除掉。為了解決這個問題,Android 10 中引入了 Scoped Storage
的概念,通過添加外部存儲訪問限制來實現(xiàn)更好的文件管理。
首先明確一個概念,外部儲存和內(nèi)部儲存。
- 內(nèi)部儲存:
/data
目錄。一般我們使用getFilesDir()
或getCacheDir()
方法獲取本應用的內(nèi)部儲存路徑,讀寫該路徑下的文件不需要申請儲存空間讀寫權限,且卸載應用時會自動刪除。 - 外部儲存:
/storage
或/mnt
目錄。一般我們使用getExternalStorageDirectory()
方法獲取的路徑來存取文件。
因為不同廠商、系統(tǒng)版本的原因,所以上述的方法并沒有一個固定的文件路徑。了解了上面的概念,那我們所說的外部儲存訪問限制,可以認為是針對 getExternalStorageDirectory()
路徑下的文件。具體的規(guī)則如下表:
上圖將外部存儲空間分為了三部分:
- 特定目錄(App-specific),使用
getExternalFilesDir()
或getExternalCacheDir()
方法訪問。無需權限,且卸載應用時會自動刪除。 - 照片、視頻、音頻這類媒體文件。使用
MediaStore
訪問,訪問其他應用的媒體文件時需要READ_EXTERNAL_STORAGE
權限。 - 其他目錄,使用 存儲訪問框架SAF (Storage Access Framwork)
所以在Android 10上即使你擁有了儲存空間的讀寫權限,也無法保證可以正常的進行文件的讀寫操作。
適配
最簡單粗暴的方法就是在 AndroidManifest.xml
中添加 android:requestLegacyExternalStorage="true"
來請求使用舊的存儲模式。
但是我不推薦此方法。因為在下一個版本的Android中,此條配置將會失效,將強制采用外部儲存限制。其實早在Android Q Beta 3之前都是強制的,但為了給開發(fā)者適配的時間才沒有強制執(zhí)行。所以如果你不抓住這段時間去適配,那么今年下半年出了Android 11。。。直接開花~~
如果你已經(jīng)適配Android 10,這里有個現(xiàn)象要 注意一下 :
如果應用通過升級安裝,那么還會使用以前的儲存模式(Legacy View)。只有通過首次安裝或是卸載重新安裝才能啟用新模式(Filtered View)。
所以在適配時,我們的判斷代碼如下:
// 使用Environment.isExternalStorageLegacy()來檢查APP的運行模式 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !Environment.isExternalStorageLegacy()) { }
這樣的好處是你可以在用戶升級后,能方便的將用戶的數(shù)據(jù)移動至應用的特定目錄。否則你只能通過SAF去移動,這樣會非常麻煩。如果你要移動數(shù)據(jù)注意只適用于Android 10下,所以現(xiàn)在適配反而是一個好時機。當然如果你不需要遷移數(shù)據(jù),那適配會更省事。
下面就說說推薦適配方案:
對于應用中涉及的文件操作,修改一下你的文件路徑。
以前我們習慣使用 Environment.getExternalStorageDirectory()
方法,那么現(xiàn)在可以使用 getExternalFilesDir()
方法(包括下載的安裝包這類的文件)。如果是緩存類型文件,可以放到 getExternalCacheDir()
路徑下。
或者使用 MediaStore
,將文件存至對應的媒體類型中(圖片: MediaStore.Images
,視頻: MediaStore.Video
,音頻: MediaStore.Audio
),不過僅限于多媒體文件。
下面代碼將圖片保存到公共目錄下,返回Uri:
public static Uri createImageUri(Context context) { ContentValues values = new ContentValues(); // 需要指定文件信息時,非必須 values.put(MediaStore.Images.Media.DESCRIPTION, "This is an image"); values.put(MediaStore.Images.Media.DISPLAY_NAME, "Image.png"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/png"); values.put(MediaStore.Images.Media.TITLE, "Image.png"); values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/test"); return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); }
對于媒體資源的訪問:比如圖片選擇器這類的場景。無法直接使用File,而應使用Uri。否則報錯如下:
java.io.FileNotFoundException: open failed: EACCES (Permission denied)
比如我在適配項目中使用的圖片選擇器時,首先修改了 Glide
通過加載File的方式顯示圖片。改為加載Uri的方式,否則圖片無法顯示出來。
Uri的獲取方式還是使用 MediaStore
:
String id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); Uri uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
其次為了便于不影響之前選擇圖片返回File的邏輯(因為一般都是上傳File,沒有直接上傳Uri的操作),所以我將最終選擇的文件又轉(zhuǎn)存進了 getExternalFilesDir()
,主要代碼如下:
File imgFile = this.getExternalFilesDir("image"); if (!imgFile.exists()){ imgFile.mkdir(); } try { File file = new File(imgFile.getAbsolutePath() + File.separator + System.currentTimeMillis() + ".jpg"); // 使用openInputStream(uri)方法獲取字節(jié)輸入流 InputStream fileInputStream = getContentResolver().openInputStream(uri); FileOutputStream fileOutputStream = new FileOutputStream(file); byte[] buffer = new byte[1024]; int byteRead; while (-1 != (byteRead = fileInputStream.read(buffer))) { fileOutputStream.write(buffer, 0, byteRead); } fileInputStream.close(); fileOutputStream.flush(); fileOutputStream.close(); // 文件可用新路徑 file.getAbsolutePath() } catch (Exception e) { e.printStackTrace(); }
如果你要獲取圖片中的地理位置信息,需要申請 ACCESS_MEDIA_LOCATION
權限,并使用MediaStore.setRequireOriginal()獲取。下面是官方的示例代碼:
Uri photoUri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getString(idColumnIndex)); final double[] latLong; // 從ExifInterface類獲取位置信息 photoUri = MediaStore.setRequireOriginal(photoUri); InputStream stream = getContentResolver().openInputStream(photoUri); if (stream != null) { ExifInterface exifInterface = new ExifInterface(stream); double[] returnedLatLong = exifInterface.getLatLong(); // If lat/long is null, fall back to the coordinates (0, 0). latLong = returnedLatLong != null ? returnedLatLong : new double[2]; // Don't reuse the stream associated with the instance of "ExifInterface". stream.close(); } else { // Failed to load the stream, so return the coordinates (0, 0). latLong = new double[2]; }
這樣下來,一個圖片選擇器就基本適配完了。
補充
應用在卸載后,會將 App-specific
目錄下的數(shù)據(jù)刪除,如果在 AndroidManifest.xml
中聲明: android:hasFragileUserData="true"
用戶可以選擇是否保留。
對于 SAF
的使用,可以查看我之前寫的 SAF使用攻略 ,這里就不展開說了。
最后這里有一個介紹Scoped Storage的視頻,推薦 觀看 :
2.權限變化
從6.0開始,基本每次都會有權限方面變動,這次也不例外。(前幾天發(fā)布了Android 11的預覽版,看來也有權限方面的變化。。。單次權限即將到來)
1.在后臺運行時訪問設備位置信息需要權限
Android 10 引入了 ACCESS_BACKGROUND_LOCATION
權限(危險權限)。
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
該權限允許應用程序在后臺訪問位置。如果請求此權限,則還必須請求 ACCESS_FINE_LOCATION
或 ACCESS_COARSE_LOCATION
權限。只請求此權限無效果。
在Android 10的設備上,如果你的應用的 targetSdkVersion
< 29,則在請求 ACCESS_FINE_LOCATION
或 ACCESS_COARSE_LOCATION
權限時,系統(tǒng)會自動同時請求 ACCESS_BACKGROUND_LOCATION
。在請求彈框中,選擇“始終允許”表示同意后臺獲取位置信息,選擇“僅在應用使用過程中允許”或"拒絕"選項表示拒絕授權。
如果你的應用的 targetSdkVersion
>= 29,則請求 ACCESS_FINE_LOCATION
或 ACCESS_COARSE_LOCATION
權限表示在前臺時擁有訪問設備位置信息的權。在請求彈框中,選擇“始終允許”表示前后臺都可以獲取位置信息,選擇“僅在應用使用過程中允許”只表示擁有前臺的權限。
總結一下就是下圖:
其實官方 不推薦你使用申請后臺訪問權的方式 ,因為這樣的結果無非就是多請求一個權限,那么這像變更還有什么意義?申請過多的權限,也會造成用戶的反感。所以官方推薦使用 前臺服務
來實現(xiàn),在前臺服務中獲取位置信息。
首先在清單中對應的 service
中添加 android:foregroundServiceType="location"
:
<service android:name="MyNavigationService" android:foregroundServiceType="location" ... > ... </service>
啟動前臺服務前檢查是否具有前臺的訪問權限:
boolean permissionApproved = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED; if (permissionApproved) { // 啟動前臺服務 } else { // 請求前臺訪問位置權限 }
如此一來就可以在 Service
中獲取位置信息。
2.一些電話、藍牙和WLAN的API需要精確位置權限
下面列舉了Android 10中必須具有 ACCESS_FINE_LOCATION
權限才能使用類和方法:
電話
- TelephonyManager
- getCellLocation()
- getAllCellInfo()
- requestNetworkScan()
- requestCellInfoUpdate()
- getAvailableNetworks()
- getServiceState()
- TelephonyScanManager
- requestNetworkScan()
- TelephonyScanManager.NetworkScanCallback
- onResults()
- PhoneStateListener
- onCellLocationChanged()
- onCellInfoChanged()
- onServiceStateChanged()
WLAN
- WifiManager
- startScan()
- getScanResults()
- getConnectionInfo()
- getConfiguredNetworks()
- WifiAwareManager
- WifiP2pManager
- WifiRttManager
藍牙
- BluetoothAdapter
- startDiscovery()
- startLeScan()
- BluetoothAdapter.LeScanCallback
- BluetoothLeScanner
- startScan()
我們可以根據(jù)上面提供的具體類和方法,在適配項目中檢查是否有使用到并及時處理。
3.ACCESS_MEDIA_LOCATION
Android 10新增權限,上面有提到,不贅述了。
4.PROCESS_OUTGOING_CALLS
Android 10上該權限已廢棄。
3.后臺啟動 Activity 的限制
簡單解釋就是 應用處于后臺時,無法啟動Activity 。比如點開一個應用會進入啟動頁或者廣告頁,一般會有幾秒的延時再跳轉(zhuǎn)至首頁。如果這期間你退到后臺,那么你將無法看到跳轉(zhuǎn)過程。而在之前的版本中,會強制彈出頁面至前臺。
既然是限制,那么肯定有不受限的情況,主要有以下幾點:
- 應用具有可見窗口,例如前臺 Activity。
- 應用在前臺任務的返回棧中已有的 Activity。
- 應用在
Recents
上現(xiàn)有任務的返回棧中已有的 Activity。Recents
就是我們的任務管理列表。 - 應用收到系統(tǒng)的
PendingIntent
通知。 - 應用收到它應該在其中啟動界面的系統(tǒng)廣播。示例包括
ACTION_NEW_OUTGOING_CALL
和SECRET_CODE_ACTION
。應用可在廣播發(fā)送幾秒鐘后啟動 Activity。 - 用戶已向應用授予
SYSTEM_ALERT_WINDOW
權限,或是在應用權限頁開啟后臺彈出頁面
的開關。
因為此項行為變更適用于在 Android 10 上運行的所有應用,所以這一限制導致最明顯的問題就是點擊推送信息時,有些應用無法進行正常的跳轉(zhuǎn)(具體的實現(xiàn)問題導致)。所以針對這類問題,可以采取 PendingIntent
的方式,發(fā)送通知時使用 setContentIntent
方法。
當然你也可以申請相應權限或者白名單:
不過申請白名單這種方法受各種手機廠商所限,很麻煩。感覺還不如引導用戶手動開啟權限。。。
對于全屏 intent,注意設置最高優(yōu)先級和添加 USE_FULL_SCREEN_INTENT
權限,這是一個普通權限。比如微信來語音或者視頻通話時,彈出的接聽頁面就是使用這一功能。
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
Intent fullScreenIntent = new Intent(this, CallActivity.class); PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(this, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.notification_icon) .setContentTitle("Incoming call") .setContentText("(919) 555-1234") .setPriority(NotificationCompat.PRIORITY_HIGH) // <--- 高優(yōu)先級 .setCategory(NotificationCompat.CATEGORY_CALL) // Use a full-screen intent only for the highest-priority alerts where you // have an associated activity that you would like to launch after the user // interacts with the notification. Also, if your app targets Android 10 // or higher, you need to request the USE_FULL_SCREEN_INTENT permission in // order for the platform to invoke this notification. .setFullScreenIntent(fullScreenPendingIntent, true); // <--- 全屏 intent Notification incomingCallNotification = notificationBuilder.build();
注意:在部分手機上,直接設置 setPriority
無效(或者說以渠道優(yōu)先級為準)。所以需要創(chuàng)建通知渠道時將重要性設置為 IMPORTANCE_HIGH
。
NotificationChannel channel = new NotificationChannel(channelId, "xxx", NotificationManager.IMPORTANCE_HIGH);
后臺啟動 Activity 的限制的目的是為了減少對用戶操作的中斷。如果你有要彈出的頁面,推薦你先彈出通知,讓用戶自己選擇接下來的操作,而不是一股腦的強制彈出。(如果你的全屏intent都讓用戶反感,那他也可以關掉你的通知,不至于任你擺布。)
4.深色主題
Android 10 新增了一個系統(tǒng)級的深色主題(在系統(tǒng)設置中開啟)。雖然深色主題并不是強制適配項,但是它可以帶給用戶更好的體驗:
- 可大幅減少耗電量。
OLED
屏幕中每個像素都是自主發(fā)光,所以在顯示深色元素時像素所消耗的電流更低,尤其在純黑顏色時像素點可以完全關閉來達到省電的效果。 - 為弱視以及對強光敏感的用戶提高可視性。深色可以降低屏幕的整體視覺亮度,減少對眼睛的視覺壓力。
- 讓所有人都可以在光線較暗的環(huán)境中更輕松地使用設備。
適配方法有兩種:
1.手動適配(資源替換)
官方文檔中提到的繼承 Theme.AppCompat.DayNight
或者 Theme.MaterialComponents.DayNight
的方法,但這只是將我們使用的各種View的默認樣式進行了適配,并不太適用于實際項目的適配。因為具體的項目中的View都按照設計的風格進行了重定義。
其實適配的方法很簡單,類似屏幕適配、國際化的操作,并不需要繼承上面的主題。比如你要修改顏色,就在 res
下新建 values-night
目錄,創(chuàng)建對應的 colors.xml
文件。將具體要修改的色值定義在里面。圖標之類的也是一個思路,創(chuàng)建對應的 drawable-night
目錄。
只要你之前的代碼不是硬編碼且代碼規(guī)范,那么適配起來還是很輕松。
2.自動適配(Force Dark)
Android 10 提供 Force Dark 功能。一如其名,此功能可讓開發(fā)者快速實現(xiàn)深色主題背景,而無需明確設置 DayNight 主題背景。
如果您的應用采用淺色主題背景,則 Force Dark 會分析應用的每個視圖,并在相應視圖在屏幕上顯示之前,自動應用深色主題背景。有些開發(fā)者會混合使用 Force Dark 和本機實現(xiàn),以縮短實現(xiàn)深色主題背景所需的時間。
應用必須選擇啟用 Force Dark,方法是在其主題背景中設置 android:forceDarkAllowed="true"
。此屬性會在所有系統(tǒng)及 AndroidX 提供的淺色主題背景(例如 Theme.Material.Light)上設置。使用 Force Dark 時,您應確保全面測試應用,并根據(jù)需要排除視圖。
如果您的應用使用 Dark Theme
主題(例如Theme.Material),則系統(tǒng)不會應用 Force Dark。同樣,如果應用的主題背景繼承自 DayNight
主題(例如Theme.AppCompat.DayNight),則系統(tǒng)不會應用 Force Dark,因為會自動切換主題背景。
您可以通過 android:forceDarkAllowed
布局屬性或 setForceDarkAllowed(boolean)
在特定視圖上控制 Force Dark。
上述內(nèi)容我直接照搬文檔的說明。總結一下,使用 Force Dark
需要注意幾點:
- 如果使用的是
DayNight
或Dark Theme
主題,則設置forceDarkAllowed
不生效。 - 如果有需要排除適配的部分,可以在對應的View上設置
forceDarkAllowed
為false。
這里說說我實際使用此方法的感受: 整體還是不錯的,設置的色值會自動取反。但也因此顏色不受控制,能否達到預期效果是個需要注意的問題。追求快速適配可以采取此方案。
手動切換主題
使用 AppCompatDelegate.setDefaultNightMode(@NightMode int mode)
方法,其中參數(shù) mode
有以下幾種:
- 淺色 - MODE_NIGHT_NO
- 深色 - MODE_NIGHT_YES
- 由省電模式設置 - MODE_NIGHT_AUTO_BATTERY
- 系統(tǒng)默認 - MODE_NIGHT_FOLLOW_SYSTEM
下面的代碼是官方Demo中的使用示例:
public class ThemeHelper { public static final String LIGHT_MODE = "light"; public static final String DARK_MODE = "dark"; public static final String DEFAULT_MODE = "default"; public static void applyTheme(@NonNull String themePref) { switch (themePref) { case LIGHT_MODE: { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); break; } case DARK_MODE: { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); break; } default: { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); } else { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY); } break; } } } }
通過 AppCompatDelegate.getDefaultNightMode()
方法,可以獲取到當前的模式,這樣便于代碼中去適配。
監(jiān)聽深色主題是否開啟
首先在清單文件中給對應的Activity配置 android:configChanges="uiMode"
:
<activity android:name=".MyActivity" android:configChanges="uiMode" />
這樣在 onConfigurationChanged
方法中就可以獲?。?/p>
@Override public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; switch (currentNightMode) { case Configuration.UI_MODE_NIGHT_NO: // 關閉 break; case Configuration.UI_MODE_NIGHT_YES: // 開啟 break; default: break; } }
判斷深色主題是否開啟
其實和上面 onConfigurationChanged
方法同理:
public static boolean isNightMode(Context context) { int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; return currentNightMode == Configuration.UI_MODE_NIGHT_YES; }
5.標識符和數(shù)據(jù)
對不可重置的設備標識符實施了限制
受影響的方法包括:
- Build
- TelephonyManager
從 Android 10 開始,應用必須具有 READ_PRIVILEGED_PHONE_STATE
特許權限才能正常使用以上這些方法。
如果你的應用沒有該權限,卻仍然使用了以上的方法,則返回的結果會因目標 SDK 版本而異:
- 如果應用以 Android 10 或更高版本為目標平臺 ,則會發(fā)生
SecurityException
。 - 如果應用以 Android 9(API 級別 28)或更低版本為目標平臺 ,則相應方法會返回 null 或占位符數(shù)據(jù)(如果應用具有
READ_PHONE_STATE
權限)。否則,會發(fā)生SecurityException
。
這項改動表示第三方應用無法獲取 Device ID
這類唯一標識。如果你需要唯一標識符,請參閱文檔: 唯一標識符的最佳做法 。
當然你也可以試試移動安全聯(lián)盟(MSA)聯(lián)合多家廠商共同開發(fā)的 統(tǒng)一補充設備標識調(diào)用SDK 。據(jù)說還有點不穩(wěn)定,因為我暫時還沒有嘗試過,所以不做評價。
限制了對剪貼板數(shù)據(jù)的訪問權限
除非您的應用是默認輸入法 (IME) 或是目前處于焦點的應用,否則它無法訪問 Android 10 或更高版本平臺上的剪貼板數(shù)據(jù)。
對啟用和停用 WLAN 實施了限制
以 Android 10 或更高版本為目標平臺的應用無法啟用或停用 WLAN。
WifiManager.setWifiEnabled()方法始終返回 false。
如果您需要提示用戶啟用或停用 WLAN,請使用設置面板。
6.其他
Android10上對折疊屏設備有了更好的支持,對于有折疊屏適配的需求,可以參看為可折疊設備構建應用 和 華為折疊屏應用開發(fā)指導。
以上內(nèi)容只是Android 10中比較大的幾項變化,完整的內(nèi)容可以查看官方文檔。
參考
到此這篇關于Android 10 適配攻略小結的文章就介紹到這了,更多相關Android 10 適配內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
ShareSDK造成App崩潰的一個BUG原因分析以及Fix方法
這篇文章主要介紹了ShareSDK造成App崩潰的一個BUG原因分析以及Fix方法,使用的是Cocos2d-x專用ShareSDK組件,需要的朋友可以參考下2014-04-04Android基于ImageSwitcher實現(xiàn)圖片切換功能
這篇文章主要介紹了Android基于ImageSwitcher實現(xiàn)圖片切換功能的相關資料,需要的朋友可以參考下2016-02-02Flutter 和 Android 互相傳遞數(shù)據(jù)的實現(xiàn)
這篇文章主要介紹了Flutter 和 Android 互相傳遞數(shù)據(jù)的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11鴻蒙手機版JNI實戰(zhàn)案例解析(JNI開發(fā)、SO庫生成、SO庫使用)
這篇文章主要介紹了鴻蒙手機版JNI實戰(zhàn)(JNI開發(fā)、SO庫生成、SO庫使用)的相關資料,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04Android使用OKHTTP解析JSON數(shù)據(jù)的實例代碼
本篇文章主要介紹了Android使用OKHTTP解析JSON數(shù)據(jù)的實例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07Android中使用achartengine生成圖表的具體方法
這篇文章主要介紹了Android中使用achartengine生成圖表的具體方法,有需要的朋友可以參考一下2014-01-01Jetpack?Compose慣性衰減動畫AnimateDecay詳解
這篇文章主要為大家介紹了Jetpack?Compose慣性衰減動畫AnimateDecay詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11Android連接MySQL數(shù)據(jù)庫詳細教程
在Android應用程序中連接 MySQL 數(shù)據(jù)庫可以幫助開發(fā)人員實現(xiàn)更豐富的數(shù)據(jù)管理功能,本教程將介紹如何在Android應用程序中使用低版本的MySQL Connector/J驅(qū)動程序來連接MySQL數(shù)據(jù)庫,需要的朋友可以參考下2023-05-05