国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

我做的網(wǎng)站服務(wù)器別人沒法左鍵點(diǎn)擊下載呢寧波谷歌seo

我做的網(wǎng)站服務(wù)器別人沒法左鍵點(diǎn)擊下載呢,寧波谷歌seo,電商設(shè)計(jì)接單平臺(tái),秭歸建設(shè)局網(wǎng)站在以前的一篇文章構(gòu)建一個(gè)WIFI室內(nèi)定位系統(tǒng)_wifi定位系統(tǒng)-CSDN博客中,我介紹了如何用Android來測量WiFi信號(hào),上傳到服務(wù)器進(jìn)行分析后,生成室內(nèi)不同地方的WiFi指紋,從而幫助進(jìn)行室內(nèi)導(dǎo)航。當(dāng)時(shí)我是用的HTML5的技術(shù)來快速開發(fā)一個(gè)An…

在以前的一篇文章構(gòu)建一個(gè)WIFI室內(nèi)定位系統(tǒng)_wifi定位系統(tǒng)-CSDN博客中,我介紹了如何用Android來測量WiFi信號(hào),上傳到服務(wù)器進(jìn)行分析后,生成室內(nèi)不同地方的WiFi指紋,從而幫助進(jìn)行室內(nèi)導(dǎo)航。當(dāng)時(shí)我是用的HTML5+的技術(shù)來快速開發(fā)一個(gè)Android的應(yīng)用,可以看到HTML5+能很便利的用我們熟悉的Web技術(shù)來進(jìn)行開發(fā),而不需要了解原生Android應(yīng)用繁瑣的開發(fā)知識(shí)。但是Android原生應(yīng)用也有其優(yōu)勢,尤其在性能上以及一些Android核心功能的調(diào)用上。尤其是Google推出了新的Jetpack Compose用于構(gòu)建原生 Android 界面的新工具包,它使用更少的代碼、強(qiáng)大的工具和直觀的 Kotlin API,可以幫助簡化并加快 Android 界面開發(fā),打造生動(dòng)而精彩的應(yīng)用,讓我們能更快速、更輕松地構(gòu)建 Android 界面以及更加便利進(jìn)行原生應(yīng)用的開發(fā)。因此這次我也用Jetpack Compose來重構(gòu)了我之前寫的Wifi信號(hào)測量的應(yīng)用。

WiFi測量主界面

界面UI設(shè)計(jì)

Jepack Compose的精髓在于用可組合函數(shù)來聲明一個(gè)UI界面。界面是不可變的,在繪制后無法進(jìn)行更新。您可以控制的是界面的狀態(tài)。每當(dāng)界面的狀態(tài)發(fā)生變化時(shí),Compose 都會(huì)重新創(chuàng)建界面數(shù)更新的部分。

在Android studio里面新建一個(gè)項(xiàng)目,選擇Empty activity類型,在這種類型的項(xiàng)目,res資源文件夾沒有l(wèi)ayout這個(gè)子文件夾,因?yàn)檫@種類型已經(jīng)是用新的Compose方式來進(jìn)行布局了,不再采用以前的XML方式來定義布局。

新建一個(gè)名為WifiMeasure的class,在里面定義一個(gè)MeasureScreen的Composable函數(shù),用來聲明我們的主界面,代碼如下:

@Composable
fun MeasureScreen() {Column(modifier = Modifier.padding(all = 8.dp)) {Text(text = stringResource(R.string.screen_title),style = MaterialTheme.typography.titleLarge,)Spacer(modifier = Modifier.height(8.dp))val imageModifier = Modifier.height(150.dp).fillMaxWidth().border(BorderStroke(1.dp, Color.Black))Image(painter = painterResource(id = R.drawable.indooratlas),contentDescription = null,contentScale = ContentScale.FillWidth,modifier = imageModifier)Spacer(modifier = Modifier.height(8.dp))OutlinedTextField(value = "",onValueChange = { },label = { Text(text = stringResource(R.string.label_position_name), style = MaterialTheme.typography.bodyMedium)},modifier = Modifier.fillMaxWidth())Spacer(modifier = Modifier.height(8.dp))TextField(value = "0.0",onValueChange = { },label = { Text(text = stringResource(R.string.label_current_angle), style = MaterialTheme.typography.bodyMedium)},readOnly = true,modifier = Modifier.fillMaxWidth())Spacer(modifier = Modifier.height(16.dp))TextButton(onClick = { },shape = RectangleShape,contentPadding = PaddingValues(16.dp),modifier = Modifier.fillMaxWidth().border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)) {Texttext = "Measure",style = MaterialTheme.typography.bodyMedium)}}
}

這里采用了Column來作為一個(gè)垂直布局,在里面放置了Text, Image等組件來顯示界面。我們可以添加一個(gè)Preview的函數(shù),這樣在進(jìn)行代碼改動(dòng)的時(shí)候,我們就可以馬上在Android studio的Design里面看到UI的改動(dòng)了,非常方便,代碼如下:

@Preview(showBackground = true)
@Composable
fun PreviewMeasureScreen() {MeasureScreen()
}

這個(gè)界面的效果如下圖:

在要進(jìn)行測量的時(shí)候,我們需要首先輸入當(dāng)前位置的名字,同時(shí)手機(jī)會(huì)實(shí)時(shí)顯示當(dāng)前的朝向,因?yàn)椴煌某驅(qū)ifi信號(hào)的測量也有影響。然后當(dāng)我們點(diǎn)擊Measure這個(gè)按鈕的時(shí)候,就會(huì)把當(dāng)前這個(gè)位置的Wifi信號(hào)信息測量出來。

在MainActivity的onCreate方法的setContent中直接調(diào)用剛才我們定義的函數(shù)MeasureScreen()即可在APP中顯示我們的界面。

定義ViewModel保存UI狀態(tài)

現(xiàn)在在輸入框中輸入位置的名字,可以看到輸入無法顯示,這是因?yàn)樵贠utlinedTextField里面我們沒有定義value是一個(gè)可觀測狀態(tài),因此Compose組件無法進(jìn)行重組更新。為此我們需要定義一個(gè)ViewModel來保存UI的狀態(tài)。新建一個(gè)WifiMeasureViewModel的class,代碼如下:

class WifiMeasureViewModel : ViewModel() {var positionName by mutableStateOf("")private setfun updatePositionName(name: String) {positionName = name}

這個(gè)類里面定義了一個(gè)positionName的mutableStateOf的State容器,通過一個(gè)update方法來更新數(shù)值。

修改WifiMeasure這個(gè)函數(shù),傳入這個(gè)ViewModel進(jìn)行綁定,這里的viewModel()是一個(gè)生命周期的組件,可以使得ViewModel與Compose UI生命周期同步存在。

@Composable
fun MeasureScreen(measureViewModel: WifiMeasureViewModel = viewModel()
) 

修改OutlinedTextField,現(xiàn)在可以正常輸入文字了,如果我們旋轉(zhuǎn)手機(jī),可以看到之前輸入的文字能保留下來。

        OutlinedTextField(value = measureViewModel.positionName,onValueChange = { measureViewModel.updatePositionName(it) },label = { Text(text = stringResource(R.string.label_position_name), style = MaterialTheme.typography.bodyMedium)},modifier = Modifier.fillMaxWidth())

獲取手機(jī)朝向

因?yàn)槭謾C(jī)的朝向?qū)τ赪ifi測量會(huì)有影響,因此通常我們會(huì)在同一個(gè)地點(diǎn)測試不同朝向的WiFi信號(hào)并記錄下來。我們需要在APP上實(shí)時(shí)顯示當(dāng)前的朝向,這就需要用到手機(jī)提供的傳感器數(shù)據(jù)。

傳統(tǒng)的Android應(yīng)用的方法是,在Activity類里面繼承SensorEventListener并重寫相應(yīng)的方法來實(shí)現(xiàn)。但是在Composable function里面如何實(shí)現(xiàn),在官網(wǎng)上并沒有介紹。我的做法是先定義一個(gè)新的類繼承SensorEventListener,例如我們新建一個(gè)SensorDataManager的類,代碼如下:

class SensorDataManager(context: Context): SensorEventListener {private val sensorManager by lazy {context.getSystemService(Context.SENSOR_SERVICE) as SensorManager}private var accelerometerReading = FloatArray(3)private var magnetometerReading = FloatArray(3)private var rotationMatrix = FloatArray(9)private var orientationAngles = FloatArray(3)val data: Channel<Float> = Channel(Channel.UNLIMITED)fun init() {Log.d("SensorDataManager", "init")val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)val magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI)sensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_UI)}override fun onSensorChanged(event: SensorEvent) {if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size)} else if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size)}SensorManager.getRotationMatrix(rotationMatrix,null,accelerometerReading,magnetometerReading)SensorManager.getOrientation(rotationMatrix, orientationAngles)data.trySend(orientationAngles[0])}override fun onAccuracyChanged(p0: Sensor?, p1: Int) {}fun cancel() {Log.d("SensorDataManager", "cancel")sensorManager.unregisterListener(this)}
}

解釋一下代碼,在init函數(shù)中獲取accelerometer和maganetic這兩個(gè)傳感器并注冊(cè)listener,根據(jù)官網(wǎng)的介紹,推薦用這兩個(gè)傳感器數(shù)據(jù)來獲取準(zhǔn)確的朝向。在重寫的onSensorChanged方法中,根據(jù)這兩個(gè)傳感器的數(shù)據(jù)計(jì)算朝向,并通過Channel把協(xié)程的數(shù)據(jù)發(fā)送出去。最后在cancel中取消listener注冊(cè)。

現(xiàn)在修改一下MeasureScreen這個(gè)函數(shù),增加以下代碼:

    val context = LocalContext.currentval scope = rememberCoroutineScope()var angle by remember { mutableStateOf<Float>(0f) }DisposableEffect(Unit) {val dataManager = SensorDataManager(context)dataManager.init()val job = scope.launch {dataManager.data.receiveAsFlow().onEach { angle = it }.collect()}onDispose {dataManager.cancel()job.cancel()}}

這里用到了Compose里面的附帶效應(yīng)Side effects,按照官網(wǎng)的解釋附帶效應(yīng)是指發(fā)生在可組合函數(shù)作用域之外的應(yīng)用狀態(tài)的變化。DisposableEffect可以在鍵發(fā)生變化或可組合項(xiàng)退出組合后進(jìn)行清理。因此我采用DiposableEffect(Unit)來監(jiān)控這個(gè)MeasureScreen函數(shù),完成初始化和清除Sensor Listener的工作。

修改一下顯示朝向角度的TextField,設(shè)置其Value

        TextField(value = angle.toString(),onValueChange = { },label = { Text(text = stringResource(R.string.label_current_angle), style = MaterialTheme.typography.bodyMedium)},readOnly = true,modifier = Modifier.fillMaxWidth())

現(xiàn)在這個(gè)測量頁面可以正常工作了。

WiFi測量報(bào)告頁面

增加導(dǎo)航

當(dāng)點(diǎn)擊測量頁面的Measure按鈕的時(shí)候,應(yīng)該能跳轉(zhuǎn)到另一個(gè)頁面,顯示W(wǎng)iFi的測量結(jié)果。要實(shí)現(xiàn)導(dǎo)航的功能,我們需要用到Navigation組件。新增一個(gè)名為Navigation的class,代碼如下:

object Destinations {const val MEASURE_ROUTE = "measure"const val REPORT_ROUTE = "report/{positionName}"
}@Composable
fun AppNavHost(modifier: Modifier = Modifier,navController: NavHostController = rememberNavController(),startDestination: String = MEASURE_ROUTE
) {NavHost(navController = navController,startDestination = startDestination) {composable(MEASURE_ROUTE) {MeasureScreen(navController = navController)}composable(REPORT_ROUTE,arguments = listOf(navArgument("positionName") {type = NavType.StringType},)) { backStackEntry ->val positionName = backStackEntry.arguments?.getString("positionName")WifiMeasureReport(positionName)}}
}

這里定義了兩個(gè)route,分別對(duì)應(yīng)APP的兩個(gè)頁面。在跳轉(zhuǎn)到測量報(bào)告頁面的時(shí)候,route會(huì)帶上positionName這個(gè)參數(shù)。

修改MainActivity,把setContent的內(nèi)容替換為調(diào)用AppNavHost(),如以下代碼:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {WifiPositionTheme {// A surface container using the 'background' color from the themeSurface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {AppNavHost()}}}}
}

WiFi掃描服務(wù)

增加一個(gè)Wifi掃描的服務(wù),實(shí)現(xiàn)對(duì)Wifi信號(hào)的測量。新增一個(gè)WifiScanService的class,代碼如下:

class WifiScanService(context: Context) {private val wifiManager by lazy {context.getSystemService(Context.WIFI_SERVICE) as WifiManager}private val context: Context = contextval data: Channel<List<WifiMeasureData>> = Channel(Channel.UNLIMITED)private val wifiScanReceiver = object : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent) {val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)if (success) {scanSuccess()} else {scanFailure()}}}fun init() {val intentFilter = IntentFilter()intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)context.registerReceiver(wifiScanReceiver, intentFilter)val success = wifiManager.startScan()if (!success) {scanFailure()}}private fun scanFailure() {Log.d("WIFI", "Scan failure")}@SuppressLint("MissingPermission")private fun scanSuccess() {val results = wifiManager.scanResultsif (!results.isNullOrEmpty()) {val wifiMeasureData = results.map {WifiMeasureData(it.BSSID,it.level)}data.trySend(wifiMeasureData)}}fun cancel() {context.unregisterReceiver(wifiScanReceiver)}
}

定義一個(gè)WifiMeasureData的數(shù)據(jù)class,保存測量數(shù)據(jù)

data class WifiMeasureData (val bssId: String,val signalStrength: Int
)

另外,要開啟WiFi測量,還需要申請(qǐng)相應(yīng)的權(quán)限,在AndroidManifest.xml里面,增加以下權(quán)限申請(qǐng)

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

報(bào)告頁面設(shè)計(jì)

同樣是采用Compose的方式來設(shè)計(jì)一個(gè)頁面展示W(wǎng)ifi測量數(shù)據(jù)的結(jié)果,新建一個(gè)WifiMeasureReport的class,代碼如下:

@Composable
fun WifiMeasureReport (positionName: String?) {val context = LocalContext.currentval scope = rememberCoroutineScope()var wifiScanResult by remember { mutableStateOf<List<WifiMeasureData>>(listOf(WifiMeasureData("", 0))) }DisposableEffect(Unit) {val wifiScanService = WifiScanService(context)wifiScanService.init()val job = scope.launch {wifiScanService.data.receiveAsFlow().onEach { wifiScanResult = it }.collect()}onDispose {wifiScanService.cancel()job.cancel()}}Column() {Text(text = stringResource(id = R.string.report_title),style = MaterialTheme.typography.titleLarge,textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth())Spacer(modifier = Modifier.height(8.dp))Text(text = stringResource(id = R.string.report_position_name)+": "+positionName,style = MaterialTheme.typography.bodyLarge,modifier = Modifier.padding(10.dp))LazyColumn(Modifier.fillMaxWidth(),contentPadding = PaddingValues(horizontal = 4.dp)){item {ItemHeader()}itemsIndexed(wifiScanResult) { index: Int, item: WifiMeasureData ->ItemRow(index, item)}}}
}@Composable
fun ItemHeader() {Row(Modifier.fillMaxWidth()//.border(BorderStroke(0.5.dp, Color.Black))) {Text(text = stringResource(R.string.report_header_bssid), fontWeight = FontWeight.Bold, modifier = Modifier.weight(5f).padding(10.dp))Text(text = stringResource(R.string.report_header_strength), fontWeight = FontWeight.Bold, modifier = Modifier.weight(5f).padding(10.dp))}Divider(color = Color.LightGray,modifier = Modifier.height(1.dp).fillMaxHeight().fillMaxWidth())
}@Composable
fun ItemRow(index: Int, item: WifiMeasureData) {val modifier: Modifier = Modifier.fillMaxWidth()Row(modifier = if (index%2 == 0) modifier.background(Color.LightGray) else modifier) {Text(text = item.bssId, modifier = Modifier.weight(5f).padding(10.dp))Text(text = item.signalStrength.toString(), modifier = Modifier.weight(5f).padding(10.dp))}Divider(color = Color.LightGray,modifier = Modifier.height(1.dp).fillMaxHeight().fillMaxWidth())
}@Preview(showBackground = true)
@Composable
fun PreviewMeasureReportScreen() {WifiMeasureReport("grid_1")
}

這里同樣采用了Column垂直布局,其中用了一個(gè)LazyColumn來展示W(wǎng)ifi測量結(jié)果的列表。這個(gè)LazyColumn類似于以前的RecyclerView。

測試結(jié)果上報(bào)

WiFi測試的結(jié)果要上報(bào)到服務(wù)器來進(jìn)行匯總分析,最后生成Wifi指紋,這部分的內(nèi)容可以參考我之前提到的博客內(nèi)容,這里不再重復(fù)。我們只需要修改一下WifiMeasureReport,把拿到的結(jié)果通過REST API上傳即可。改動(dòng)如下:

待補(bǔ)充。。。

運(yùn)行效果

最后把項(xiàng)目打包為APK后上傳到手機(jī)運(yùn)行,實(shí)際效果如下:

待補(bǔ)充。。。

http://aloenet.com.cn/news/46070.html

相關(guān)文章:

  • 有賬號(hào)和密碼怎么進(jìn)公司網(wǎng)站后臺(tái)百度seo如何做
  • ??诰W(wǎng)站建設(shè)解決方案云南疫情最新數(shù)據(jù)消息中高風(fēng)險(xiǎn)地區(qū)
  • wordpress免費(fèi)主題模板seo運(yùn)營是做什么的
  • 中國網(wǎng)庫做網(wǎng)站線下推廣方案
  • 動(dòng)漫做h在線觀看網(wǎng)站b2b模式的電商平臺(tái)有哪些
  • 創(chuàng)業(yè)初期要建立公司的網(wǎng)站嗎鄭州百度關(guān)鍵詞seo
  • 網(wǎng)站大屏輪播圖效果怎么做公眾號(hào)排名優(yōu)化軟件
  • 怎樣做永久網(wǎng)站二維碼北京seo公司有哪些
  • 網(wǎng)站做線上銷售蘇州百度關(guān)鍵詞優(yōu)化
  • wordpress兼職sem seo
  • 精品網(wǎng)站建設(shè)平臺(tái)如何自己免費(fèi)制作網(wǎng)站
  • 哈爾濱網(wǎng)站建設(shè)外包公司申請(qǐng)自媒體平臺(tái)注冊(cè)
  • 網(wǎng)站建設(shè)銷售問答品牌營銷是什么
  • ps 怎么做網(wǎng)站杭州網(wǎng)站定制
  • win7 iis創(chuàng)建網(wǎng)站網(wǎng)絡(luò)推廣營銷網(wǎng)
  • 重慶網(wǎng)站建設(shè)吧營銷型網(wǎng)站
  • 自己制作的網(wǎng)站怎么做分頁今日時(shí)事新聞
  • 寧波網(wǎng)站建設(shè)的企業(yè)太原今日頭條
  • 百度推廣做網(wǎng)站什么價(jià)位網(wǎng)站開發(fā)流程的8個(gè)步驟
  • asp網(wǎng)站vps搬家2024年重大新聞簡短
  • 武漢做網(wǎng)站找哪家好世界球隊(duì)最新排名榜
  • 杭州未來科技網(wǎng)站建設(shè)高端建站
  • 企業(yè)信用網(wǎng)查詢網(wǎng)站優(yōu)化推廣培訓(xùn)
  • 建甌網(wǎng)站制作百度網(wǎng)站怎么提升排名
  • 自己做產(chǎn)品品牌網(wǎng)站谷歌官網(wǎng)網(wǎng)址
  • 杭州富陽區(qū)網(wǎng)站建設(shè)公司seo排名軟件有用嗎
  • 鄭州響應(yīng)式網(wǎng)站建設(shè)如何找外包的銷售團(tuán)隊(duì)
  • 網(wǎng)絡(luò)工作室頭像seo積分優(yōu)化
  • 用flash做的網(wǎng)站展示品牌策劃方案模板
  • 網(wǎng)站二級(jí)域名 權(quán)重 盧松松學(xué)seo網(wǎng)絡(luò)推廣