반응형

 

우리는 종종  SDK 가 업데이트 되고 나서 원인 모를 빌드 에러가 발생하는 것을 경험하게 됩니다.

 

SDK가 매번 업데이트 되고, 자동으로 패키지들을 설치해줘서 개발 환경들은 점점 좋아지고 있습니다.

그러나 종종 프로젝트를 만든 시점이 오래되어 최신 SDK에서 프로젝트가 만들어졌을 시점의 환경을 더이상 지원하지 않거나 변경되는 경우가 있죠.

 

다행스럽게도 구글링을 해보면 비슷한 문제를 겪는 사람들을 볼수 있고 그들이 포스팅 해놓은 해결책으로 많은 부분을 해결할 수 있습니다.

 

하지만 종종 전혀 해결이 안되는 문제들이 발생할 경우들이 있죠.

이럴때는 정말  project를 완전히 지우고 새 프로잭트를 만들고 소스코드를 카피해서 사용하고 싶은 생각이 들때도 있습니다.

 

 

일단 기초적인 사례부터 정리해보도록 하겠습니다.

 

디버그 모드로 빌드 잘되는데 릴리즈로 이상하게 잘 안된다.

뭔가 app/build.gradle 에서 에러가 발생한다.

 

1. Signing Key

먼저 확인해보실것이 , release 에 사용하는 signing key 입니다.

종종  signing 키가 삭제되거나 signing key  관련 설정이 잘 안되어 있어서 build error 가 이상한 곳에서 발생할때가 있습니다.

 

2. SDK version 확인

compile sdk나 target sdk 설정이 잘못되어있는 경우에 많이 에러가 발생합니다.

또, 현재 개발 환경에 sdk version이 정상적으로 설치 되었는지 확인 하는 것도 필요합니다.

예를 들면 아래와 같이 SDK build tool 35 버전을 잘 설치가 되어있습니다.

 

 

 

그런데. 빌드할때 이상하게 낮은 버전으로 빌드를 하려고 하고 에러가 발생한다면, SDK 설치가 좀 덜되어있을 가능성이 있습니다.

flutter build 시 현재 최신 버전으로 했을때 android sdk version 34를 사용합니다.

그런데 현재 PC에 35, 30.3 만 download 되어있다면, 아마 낮은버전인 30.3으로 빌드를 하게 될것입니다.

현재 빌드환경이 어떤 버전으로 돌아가고 있는지 확인하고 Show Package Details로 해당 버전이 설치되어있는지 확인할 필요가 있습니다.

 

 

 

3. 드물지만, subproject build 할때 sdk 버전이 안먹히는 경우

아래와 같이 afterEvaluate 를 설정해서 트라이 해볼 수 있습니다.

afterEvaluate {
        android {
            compileSdkVersion 34
        }
    }

 

전체 코드

android/build.gradle 파일

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.buildDir = "../build"
subprojects {
    project.buildDir = "${rootProject.buildDir}/${project.name}"
    // Add this
    afterEvaluate {
        android {
            compileSdkVersion 34
        }
    }
    // End
}
subprojects {
    project.evaluationDependsOn(":app")
}

tasks.register("clean", Delete) {
    delete rootProject.buildDir
}

 

 

#해피코딩 !!

반응형


github의 action 을 사용할때 , 환경변수등을 설정할 필요가 있습니다.

이때 활용 할 수 있는 것이 이와 같습니다.

특히 android release 빌드를 할때는 keystore 가 반드시 필요한데, 이 keystore는 외부에 노출하면 안되는 것이라 참 난감합니다.

그런데 이를 지원해줄 수 있는 github의 secret variable 저장 기능이 있습니다.

이를 한번 알아봅시다.!

 

 

유사한 내용이지만 둘다 모두 필요한 내용이 있어서 정리를 위해 링크 남깁니다.

 


https://medium.com/@dcostalloyd90/automating-android-builds-with-github-actions-a-step-by-step-guide-2a02a54f59cd

 

 

Automating Android Builds with GitHub Actions: A Step-by-Step Guide

In this tutorial, we’ll walk you through the process of automating Android builds using GitHub Actions. This will streamline your…

medium.com

 

 

https://www.droidcon.com/2023/09/08/build-sign-and-create-release-build-using-github-actions/

 

 

위 내용들이 mac에서 셋업을 하면서 보니까. 약간 차이점들이 있었습니다. 그리고 오류도 좀 있고요.

저도 순서를 하나씩 짚어보면서 정리해보도록 하겠습니다.

 

1. github 에서 action 과 과련한 secret  파일들을 설정할 수 있는 부분이 있습니다.

github/[내프로젝트]/settings 에서 Secrets and variables 메뉴가 있습니다.

 

 

 

 

Secrets에 secret들을 하나씩 추가해봅시다.

 

 

KEYSTORE_BASE_64

RELEASE_KEYSTORE_ALIAS

RELEASE_KEYSTORE_PASSWORD

RELEASE_KEY_PASSWORD

 

이렇게 4가지를 차례대로 만들 예정입니다.

이미 키가 있다는 전제하에 설명합니다.(키 만드는 것은 여기서 확인!)

 

KEYSTORE_BASE_64 

사용하고 있는 key file이 있으실텐데 해당 key file을 사용하시면 됩니다.

저는 여기서 key.jks 라는 이름의 파일 이라고 가정하고 진행합니다.

base64 인코딩 하기

github의 secret 에는 파일을 추가 할 수 없습니다. 따라서 text를 이용해야 합니다. 때문에 key 파일을 문자열로 변환하기 위해 base64 인코딩합니다.

 

형식 : base64 -i <in-file> -o <outfile>

 

$ base64 -i key.jks -o keystore-b64.txt

 

이렇게 keystore-b64.txt를 열어서 해당 text를 KEYSTORE_BASE_64 에 저장합니다.

 

나머지  RELEASE_KEYSTORE_ALIAS / RELEASE_KEYSTORE_PASSWORD / RELEASE_KEY_PASSWORD

들은 키를 만들때 사용했던 alias, keystore passowrd, key passoword 를 적어주면 됩니다.

 

 

 

2. build.gradle 을 수정합니다.

이제 gradle 파일을 수정하러 가야 합니다.

android/app/build.gradle 

    signingConfigs{
        releaseWithSignedKey {
            
            storeFile file("../keystore.jks")
            storePassword System.getenv("RELEASE_KEYSTORE_PASSWORD")
            keyAlias System.getenv("RELEASE_KEYSTORE_ALIAS")
            keyPassword System.getenv("RELEASE_KEY_PASSWORD")
        }
    }
    
    :
    
    buildTypes {
        release {

            signingConfig = signingConfigs.releaseWithSignedKey
            System.out.println("signingConfig:$signingConfig")
        }
    }

 

signingConfigs 를 추가하고 buildType의 release에서 이 정보를 이용하도록 설정합니다.

 

자! 코드를 보셔서 아시겠지만, 로컬에서 release로 빌드를 하려면 어떻게 해야 할까요?

환경변수에 RELEASE_KEYSTORE_ALIAS,RELEASE_KEYSTORE_PASSWORD,RELEASE_KEY_PASSWORD 를 설정 해야 겠죠.

이건 쉬우니까 구글링 해서 찾어보시면 됩니다.

 

 

3. github action 의 workflow 수정 하기.

github action에서 핵심은 base64로 encoding 된 key를 다시 keystore.jks로 변환하여 사용하는 것과 환경 변수 세팅입니다.

 

    steps:
     - name: Checking out branch
       uses: actions/checkout@v3
     - name: Setup Java
       uses: actions/setup-java@v3
       with:
         distribution: 'zulu' # See 'Supported distributions' for available options
         java-version: '17'
         cache: 'gradle'
     - name: Decode Keystore
       env:
          ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }}
          RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
          RELEASE_KEYSTORE_ALIAS: ${{ secrets.RELEASE_KEYSTORE_ALIAS }}
          RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }} 

       run: |
          echo $ENCODED_STRING > keystore-b64.txt
          base64 -d -o keystore.jks < keystore-b64.txt
          
     - name: Setup flutter
       uses: subosito/flutter-action@v2
       with:
         flutter-version: '3.24.1'
     - run: flutter pub get

     - run: flutter build apk --debug

 

 

위와 같이 Decode Keystore를  actions/setup-java 다음에 추가해줍니다.

바로 아래 항목입니다.

     - name: Decode Keystore
       env:
          ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }}
          RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
          RELEASE_KEYSTORE_ALIAS: ${{ secrets.RELEASE_KEYSTORE_ALIAS }}
          RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }} 

       run: |
          echo $ENCODED_STRING > keystore-b64.txt
          base64 -d -o keystore.jks < keystore-b64.txt

 

여기까지 해주면 action 에서 release build 테스트를 위한 환경이 끝났습니다.

 

깃 커밋하시면 자동으로 릴리즈 빌드를 시험해보실 수 있습니다.

 

#해피코딩

반응형

안녕하세요.

 

플러터로 android 용 앱을 개발하다가 앱이 좀 괜찮으면 iOS로 확장하고 싶은 욕구가 생기겠죠?

그런데 처음부터 iOS를 염두해두고 프로젝트를 만들지 않았다면, 확장을 어떻게 해야 하나 할겁니다.

 

간단하게 확장하는 방법 정리 해봤습니다.

 

 

먼저 빌드할 플랫폼을 선택합니다.

 

terminal에서 다음과 같이 입력하면 되는데요.

 $ flutter config --enable-<platform>

 

ios 의 경우에는  flutter config --enable-ios 라고 입력 합니다.

 

그리고 나서 프로젝트를 재 생성 합니다.

$ flutter create .

을 입력하면 다시 프로젝트가 재 생성됩니다.

 

 

 

사용 예 )

~/$ flutter config --enable-ios
Setting "enable-ios" value to "true".

You may need to restart any open editors for them to read new settings.


~/github/arbot_care (karzia)$ flutter create .
⣟

Recreating project ....
  windows/runner/flutter_window.cpp (created)
  windows/runner/utils.h (created)
  windows/runner/utils.cpp (created)
  windows/runner/runner.exe.manifest (created)
  windows/runner/CMakeLists.txt (created)
  windows/runner/win32_window.h (created)
  windows/runner/Runner.rc (created)
  windows/runner/win32_window.cpp (created)
  windows/runner/resources/app_icon.ico (created)
  windows/runner/main.cpp (created)
  windows/runner/resource.h (created)
  windows/runner/flutter_window.h (created)
  windows/flutter/CMakeLists.txt (created)
  windows/.gitignore (created)
  windows/CMakeLists.txt (created)
  arbot_care.iml (created)
  web/favicon.png (created)
  web/index.html (created)
  web/manifest.json (created)
  web/icons/Icon-maskable-512.png (created)
  web/icons/Icon-192.png (created)
  web/icons/Icon-maskable-192.png (created)
  web/icons/Icon-512.png (created)
  android/app/src/main/res/mipmap-mdpi/ic_launcher.png (created)
  android/app/src/main/res/mipmap-hdpi/ic_launcher.png (created)
  android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (created)
  android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (created)
  android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (created)
  android/app/src/main/kotlin/io/netplant/arbot_care/MainActivity.kt (created)
  android/arbot_care_android.iml (created)
  ios/RunnerTests/RunnerTests.swift (created)
  .idea/runConfigurations/main_dart.xml (created)
  .idea/libraries/KotlinJavaRuntime.xml (created)
  linux/main.cc (created)
  linux/my_application.h (created)
  linux/my_application.cc (created)
  linux/flutter/CMakeLists.txt (created)
  linux/.gitignore (created)
  linux/CMakeLists.txt (created)
Resolving dependencies... 
Got dependencies.
Wrote 42 files.

All done!
You can find general documentation for Flutter at: https://docs.flutter.dev/
Detailed API documentation is available at: https://api.flutter.dev/
If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev

 

 

참고로 지원 되는 플랫폼 목록은 다음과 같습니다.

 

--enable-web Enable Flutter for web. This setting will take effect on the master, dev, beta, and stable channels
--no-enable-web Disable Flutter for web. This setting will take effect on the master, dev, beta, and stable channels
--enable-linux-desktop Enable beta-quality support for desktop on Linux. This setting will take effect on the master, dev, beta, and stable channels. Newer beta versions are available on the beta channel
--no-enable-linux-desktop Disable beta-quality support for desktop on Linux. This setting will take effect on the master, dev, beta, and stable channels. Newer beta versions are available on the beta channel
--enable-macos-desktop Enable beta-quality support for desktop on macOS. This setting will take effect on the master, dev, beta, and stable channels. Newer beta versions are available on the beta channel
--no-enable-macos-desktop Disable beta-quality support for desktop on macOS. This setting will take effect on the master, dev, beta, and stable channels. Newer beta versions are available on the beta channel
--enable-windows-desktop Enable beta-quality support for desktop on Windows. This setting will take effect on the master, dev, beta, and stable channels. Newer beta versions are available on the beta channel
--no-enable-windows-desktop Disable beta-quality support for desktop on Windows. This setting will take effect on the master, dev, beta, and stable channels. Newer beta versions are available on the beta channel
--enable-android Enable Flutter for Android. This setting will take effect on the master, dev, beta, and stable channels
--no-enable-android Disable Flutter for Android. This setting will take effect on the master, dev, beta, and stable channels
--enable-ios Enable Flutter for iOS. This setting will take effect on the master, dev, beta, and stable channels
--no-enable-ios Disable Flutter for iOS. This setting will take effect on the master, dev, beta, and stable channels
--enable-fuchsia Enable Flutter for Fuchsia. This setting will take effect on the master channel
--no-enable-fuchsia Disable Flutter for Fuchsia. This setting will take effect on the master channel

 

enable 시킬때는 --enable-<platform> 을 사용하고 disable 시킬때는 --no-enable-<platform> 을 이용합니다.

(disable이 아니네요 ^^;;;)

 

 

 

해피코딩 !!

반응형

요즘 Flutter 로 ble device를 다루는 작업을 하고 있습니다.

 

아시다 시피 BLE 장치들은 기존 BT classic과 다르게 broadcasting 기능을 제공하고 있어서 S/W로직을 기존 BT classic과 동일하게 가져가기에는 무리가 있습니다.

BLE 장치를 어떤 목적으로 어떻게 사용할것인가를 잘 구상 해야합니다.

구성 하고자 하는 장치를 미미(mimi)라고 합시다.

1.  미미에 탑재한 센서에서 읽은 값을 broadcasting 만 목적으로 한다. 

 => BLE advertizement

2. 미미에 탑재한 센서에서 읽은 값을 연결되었을때만 데이타를 읽어갈 수 있도록 한다.

 => BLE service characteristics

3. 미미에 탑제된 여러 장치들을 제어하려고 한다.

 => connect /read /write

보통 목적은 3가지 형태가 될것 같습니다.

 

1번의 경우는 BLE advertizement를 이용하는 방법을 사용합니다.

- connection 없이 모단말(ble에선 central이라 부름)에서 scanning을 할때 scanning 한 데이타를 활용합니다.

- 이를 위헤서 미미(ble장치)에서도 당연히 advertizement 에 데이타를 설정 해줘야합니다.

- spec이나 가이드 상으로는 advertizementdata에 service data 항목이 있어서 service id와 data를 설정할 수 있습니다.

- 그러나 이렇게 사용할 수 없다면, manufacturer data를 활용하는 방법으로 할 수 도 있습니다.

- 여러 단말에서 동시에 읽어갈 수 있습니다.

 

2번의 경우는 BLE service의 characteristic의 값을 읽는 방식을 취합니다.

- scan후에 scan된 기기에 connect하고 나서 data를 읽어가는 방식입니다.

- 1번의 경우에는 scan만으로 정보를 읽어갈 수 있지만, 보안상 취약합니다. 모든 기기가 값을 다 볼 수 있기 때문이죠.

- 이런 connection 방식은 1:1통신이기 때문에, 연결되고 나면 다른 기기에서는 scan이 안됩니다.

- 때문에 connect후에 data를 읽고 바로 연결을 끊어야 합니다.

 

3번의 경우는 2번과 유사한데 다만 scanning 과 분리해서 connect후 characteristic에 read/write를 하는 방식으로 처리합니다.

- serial 방식( classic 방식) 과 유사한 요청사항이죠.

- 하지만 serical에서는 연결후 data를 보내고 response를 받는 것만 고려 했다면, BLE에서는 특정 characteristic정하고 해당 characteristic 값에 data를 wrtie하고 notifiy 를 설정하고 read를 하는 방식이라, 단순한 기능 구현에는 번거로움이 있지만,

미미(mini, ble 장치)가 복잡하다면 각 기능 제어를 분리해서 사용할 수 있기 때문에 좋습니다.

 

 

device scanning 예제 입니다.

package 는 flutter_blue_plus 를 사용했습니다.

 

pubspec.yaml


  flutter_blue_plus: ^1.3.1

 

AndroidManifest.xml

project/android/app/src/main/AndroidManifest.xml

project/android/app/src/profile/AndroidManifest.xml

 

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

 

Android platform 에서 GPS가 켜져있지 않으면, Bt device scan이 안됩니다.

그냥 BT 기기를 사용하는 입장에서는 GPS 없이도 device 인식이 가능한 것이 상식적일 것 같은데, 실제로 android에서는 그렇지 못합니다.

https://stackoverflow.com/questions/33045581/location-needs-to-be-enabled-for-bluetooth-low-energy-scanning-on-android-6-0

 

Location needs to be enabled for Bluetooth Low Energy Scanning on Android 6.0

After upgrading to Android version 6.0 Bluetooth Low Energy (BLE) scanning will only work if Location services are enabled on the device. See here for reference: Bluetooth Low Energy startScan on A...

stackoverflow.com

이럴경우  android:usesPermissionFlags="neverForLocation" 를 이용해서 scan이 가능하도록 설정 할 수 있습니다.

이 Flag는 상당히 미묘한데요.

위에 stackover flow 를 읽어보면, 위치정보를 기반으로 becon ble 등을 인식 하는 서비를 위해 GPS가 있을떄만 scan 이 되도록 한것 같은데, (그 외에 사생활 보안 등등의 이슈?? 도 있겠죠?)

서비스를 운영하는 입장에서 누군가가 GPS가 꺼져 있으니 동작 안합니다.(정확히는 BT가 scan 안됩니다). 라고 한다면, 답하기가 좀 곤란합니다. 

아무튼 플랫폼 정책상 GSP 꺼져있을때 scan이 안되도록 한 이유는 있겠지만, 우리는 항상 잘 BT 기기를 찾아야 하니 neverForLocation 같은 옵션을 사용하게 됩니다.

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" 
                     android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

 

 

 

Sample code

import 'LabBleGenericController.dart';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';



class LabBleDeviceGenericPage extends StatefulWidget{
  const LabBleDeviceGenericPage({super.key});

  @override
  State<LabBleDeviceGenericPage> createState() => _LabBleDeviceGenericPageState();
}


class _LabBleDeviceGenericPageState extends State<LabBleDeviceGenericPage> {
  StringBuffer data =StringBuffer();

  LabBleGenericController controller= LabBleGenericController();

  bool connectAll = true;
  bool isScanning = false;

  startScan()async{
    print("start scan!!!!!!!");
    isScanning = true;
    controller.scan(
        onScan:(val)=>setState(() { }),
        onScanDone:(){
          setState(() { });
          if(isScanning) {
            startScan();
          }
        });
  }
  stopScan() async{
    isScanning = false;
    await controller.stop();
    setState(() {
    });
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title:Text("BleDevice")),
      body:Container(
        child: Column(children:[
          TextField(
            controller: controller.filterEdit,
            decoration: InputDecoration(prefix: Text("Filter:"),border: OutlineInputBorder()),
          ),
          SizedBox(height: 20,),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              ElevatedButton(
                  onPressed: (){
                    setState(() { data.clear(); });
                    if(isScanning){
                      stopScan();
                    }else{
                      startScan();
                    }
                  },
                  child: Text(isScanning?"Stop":"Scan")),
              ElevatedButton(
                  onPressed: (){
                    connectAll?controller.connectAll(): controller.disconnectAll();
                    connectAll =! connectAll;
                  },
                  child: Text(connectAll?"connect all":"disconnect all")),
              ElevatedButton(
                  onPressed: (){
                      setState(() { controller.updateDevices();});
                  },
                  child: Text("update")),
            ],
          ),

          SizedBox(height: 50,),
          

          Expanded(
            child: SingleChildScrollView(
              child: Column(
                children: [
                  SizedBox(
                      child: Text(controller.devicesStatus)),
                  SizedBox(
                      child: Text(controller.getResult()))
                ],
              ),
            ),
          ),

         

        ]),
      ),
    );
  }

  @override
  void dispose() {
    controller.close();
    super.dispose();
  }
}

 

controller

import 'dart:async';

import 'package:flutter/cupertino.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/get.dart';



class LabBleGenericController extends GetxController{

  StreamSubscription? sub;

  StringBuffer resBuffer =StringBuffer();
  final filterEdit =TextEditingController();
  
  final devices = <BluetoothDevice>[];

  LabBleGenericController(){
    filterEdit.text = "arbot";
  }


  @override
  void onClose() {
    print("LabBleGenericController:onClose()");
    sub?.cancel();
    super.onClose();
  }

  Future connectAll() async{
    for(final d in devices){
      d.connect();
    }
    updateDevices();
  }


  closeAll() async{


    for(final d in await FlutterBluePlus.instance.connectedDevices){
      await d.disconnect();
    }
    devices.clear();
  }

  disconnectAll() async{

    for(final d in devices){
      await d.disconnect();
    }
    updateDevices();
  }

  stop()async{
    await sub?.cancel();
    await FlutterBluePlus.instance.stopScan();
    sub= null;
  }

  scan({Function(ScanResult)? onScan,Function()? onScanDone}) async{

    await updateDevices();
    sub?.cancel();
    resBuffer.clear();

    await FlutterBluePlus.instance.stopScan();
    await closeAll();

    sub = FlutterBluePlus.instance.scan(timeout: Duration(seconds: 10)).listen((ScanResult result) {
      final localName = result.advertisementData.localName;
      if(!localName.toLowerCase().contains(filterEdit.text)) return;
      
      devices.add(result.device);
      
      print("localname:$localName");
      resBuffer.writeln(localName);
      resBuffer.writeln("[advertisementData]");
      resBuffer.writeln("manufacturerData:");

      int i =0;
      for(final k in result.advertisementData.manufacturerData.keys){
        final data = result.advertisementData.manufacturerData[k];
        resBuffer.write("\nkey($k):");
        if(data == null) continue;
        print("idx[$i]:${data.toString()}");
        print("str: ${String.fromCharCodes(data)}");
        resBuffer.writeln(" ${data.toString()}");
        i++;
      }
      resBuffer.writeln("[serviceUuids:${result.advertisementData.serviceUuids.length}]");
      i=0;
      for(final d in result.advertisementData.serviceUuids){
        resBuffer.writeln("[$i]: $d");
        i++;
      }
      resBuffer.writeln("serviceData:");
      i=0;
      for(final k in result.advertisementData.serviceData.keys){
        resBuffer.write("\n$k:");
        final data = result.advertisementData.serviceData[k];
        if(data == null) continue;
        print("idx[$i]:${data.toString()}");
        print("str: ${String.fromCharCodes(data)}");
        resBuffer.write("$data");
        i++;
      }
      print("buffer[${resBuffer.length}]:${resBuffer.toString()}");
      resBuffer.writeln("----------");
      onScan?.call(result);
    },
    onDone: (){
      sub?.cancel();
      onScanDone?.call();
      return ;
    });
  }

  String getResult(){
    return resBuffer.toString();
  }

  final _devicesStatus = "".obs;
  get devicesStatus => _devicesStatus.value;

  updateDevices() async{
    final buff = StringBuffer();
    for(final d in devices){
      buff.writeln("[${d.name}]: ${(await d.state.first).toString()}");
    }
    _devicesStatus.value =  buff.toString();
  }

  close(){
    sub?.cancel();
  }
}

 

해피 코딩!!

반응형

android 프로그램을 개발하다 보면, 종종 C++ 또는 C로 작성된 코드를 사용해야 하거나, xxx.a 또는 yyy.so 와 같은 static library 또는 shared library 를 사용해야 하는 상황들이 발생합니다.

 

static library 

static library는 소스(source) 빌드시  링킹 시점에 심볼(symbol)이 연결됩니다.

주로  xxx.a 처럼 .a 확장자를 사용합니다.

shared library 

shared library는 런타임(runtime, 실행시점)때 필요시점에 library를 로딩해서 symbol이 연결됩니다.

주로 .so 또는 .dll 확장자로 사용됩니다.

 

이런 경우를 위해서 java에서는 JNI(java native interface) 를 제공하고 있습니다.

 

Android Studio 에서 기본적으로 CMake와 make file 을 통해서 구현이 가능합니다.

 

    static {
        System.loadLibrary("native-lib");
    }
    private native String stringFromJNI();

 

extern "C" JNIEXPORT jstring JNICALL
Java_com_my_package_JniClass_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

 

그러나 android 가 vm 위에서 동작하는 것과 달리 native code는 platform에 종속성(dependency)를 가지게 되니, 빌드 환경이나 플랫폼에 영향을 받게 됩니다.

 

 

C++ 관련 link error

ld: error: undefined symbol: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::operator=(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
  >>> referenced by string:1078 (external/libcxx/include/string:1078)

 

때문에 ndk build 시 에러가 발생하는 경우들이 나옵니다.

android jni 의 native build error 는 주로 아래 so들 때문에 발생하는 경우들이 많습니다.

c++관련 toolchain file 인데,  android 에서는 어떤 이유에선지 해당 so file들을 제공하지 않습니다.

build 시 또는 runtime에 에러가 발생하게 됩니다.

 

libc++.so, libbase.so, libcutils.so

 

https://developer.android.com/about/versions/nougat/android-7.0-changes?hl=ko 

 

Android 7.0 동작 변경 사항  |  Android 개발자  |  Android Developers

Along with new features and capabilities, Android 7.0 includes a variety of system and API behavior changes. This document highlights some of the key changes that you should understand and account for in your apps. If you have previously published an …

developer.android.com

 

만들고자 하는 native lib을 static으로 만들면 build는 해결할 수 있지만, java에서 c++ native  symbol link 과정은 컴파일(compile)시에 결정되는 것이 아니라 런타임(runtime linking 또는 dynamic linking )을 하기 때문에 so file을 사용해야 합니다.

 

즉 해결 방법은, libc++.so, libbase.so, libcutils.so 파일들을 모두 application library로 포함 시켜야 합니다.

 

 

 

'Android, Java,Kotlin' 카테고리의 다른 글

[Copy&Paste]String 다루기  (0) 2020.11.11
[Copy&Paste] Array를 List로 바꾸기  (0) 2020.11.04
[Android] JavaDoc 사용법 링크.  (0) 2020.10.28
반응형

 

byte[] to String

 byte[] ={'a','b',};
 String str = new String(data, StandardCharsets.UTF_8);
 
 
 
 byte[] ={'a','b',};
 String str = new String(data);

 

String.toCharArray()

 

String str = "abcdefg";
char [] array = str.toCharArray();

 

 

String.getBytes();

String str= "Hello";
byte buff[] = str.getBytes();
//charset
byte buff1[] = str.getBytes(StandardCharsets.UTF_8);

//charset string
try {
	byte buff2[] = str.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
	e.printStackTrace();
}

 

byte[] to String

String str = "Hello";
byte buff[] = str.getBytes();
String str1 = new String(buff);

 

 

InputStream -> ByteArrayOutputStream -> String

(주로 File IO, Asset , network등 이용하는 경우 많이 사용하게 됨.)

 

InputStream input = this.getContext().getAssets().open("mytextfile.txt");

try {
	ByteArrayOutputStream result = new ByteArrayOutputStream();

	byte buff[] = new byte[1024];
	int length;
	while( (length = input.read(buff,0, 1024)) != -1){
		result.write(buff,0,length);
	}
	String str = result.toString("UTF-8");

} catch (IOException e) {
	e.printStackTrace();
}

 

 

 

'Android, Java,Kotlin' 카테고리의 다른 글

JNI, Native code build 시 유의점  (0) 2022.04.21
[Copy&Paste] Array를 List로 바꾸기  (0) 2020.11.04
[Android] JavaDoc 사용법 링크.  (0) 2020.10.28
반응형

 

flutter:  from install to product

 

 

 

flutter-ko.dev/docs/get-started/install/linux

 

리눅스 설치

 

flutter-ko.dev

 

반응형

 

Java doc 의 문법으로 현재 구현중인 프로젝트의 문서화를 편리하게 관리할 수 있습니다.

 

 

 

www.devkuma.com/books/pages/1249

'Android, Java,Kotlin' 카테고리의 다른 글

JNI, Native code build 시 유의점  (0) 2022.04.21
[Copy&Paste]String 다루기  (0) 2020.11.11
[Copy&Paste] Array를 List로 바꾸기  (0) 2020.11.04

+ Recent posts