android开发心得

因E2E开发和架构设计需要,对android要有基本的开发能力,结合项目,有一些总结,记录之。

Android开发

Android开发环境搭建

步骤:

  • 安装Android Studio
  • 下载android SDK
  • 配置C:\Users\账号\.gradle 下的gradle.properties,主要也是配置代理
systemProp.https.proxyPassword=密码

systemProp.http.proxyHost=xx.xx.com

systemProp.https.proxyUser=账号

systemProp.https.proxyHost=xx.xx.com

systemProp.https.proxyPort=8080

systemProp.http.proxyPort=8080

systemProp.http.proxyPassword=密码

systemProp.https.nonProxyHosts=*.xx.com,100.*,10.*

systemProp.http.proxyUser=账号

 systemProp.http.nonProxyHosts=*.xx.com,100.*,10.*

也可以将该文件删掉,直接在Android Studio里配http proxy。

  • 去掉工程配置的如下外网maven仓:google和jcenter,改为内网仓访问:
  buildscript {

      repositories {

          maven { url 'https://developer.xx.com/repo/' }
          
          maven {
            url 'http://artifactory.xx.com/artifactory/maven-public/'
        }

          google()

          jcenter()

      }

修改后:

buildscript {
    repositories {
        maven {
            url 'https://developer.xx.com/repo/'
        }
        maven {
            url 'http://artifactory.xx.com/artifactory/maven-public/'
        }

        maven {
            url "http://mirrors.rnd.xx.com/maven/"
        }
//        google()
//        jcenter()

        
    }

主要为避免一些pom取不到。

  • module的build.gradle里配置compileSdkVersion和targetSdkVersion:
android {
    compileSdkVersion 31
    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 19
        targetSdkVersion 31
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

gradle

引入包依赖

在build.gradle里加入依赖,同时更新gradle工程即可:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
    ......
    implementation 'org.litepal.android:core:2.0.0'
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'javax.annotation:javax.annotation-api:1.2'
    implementation 'org.projectlombok:lombok:1.16.18'
    implementation 'com.squareup.okhttp3:okhttp:4.8.1'


}

包引用

api、compile、implementation

api和compile关键字作用效果是一样的,使用时可以互相替换。实际上,api关键字是用来替代compile关键字的,因为compile关键字将来会被弃用。在高版本的gradle,使用compile关键字会报错并提示使用api关键字代替。

api或compile关键字引用的包对于其他module来说是可见的,而implementation关键字引用的包对于其他module来说是不可见的。

模拟器

将android SDK的platform-tools目录加入path环境变量,其下有adb.exe。

常用命令:

adb shell

# 列出当前设备列表
adb devices

进入adb shell后,可以用ls命令查看设备(模拟器或手机)里的文件,就跟普通的linux文件系统操作一样。

刚进入adb shell后并不是root用户,需用su命令转成root用户

报“另一个模拟器实例已启动”错误

删除C:\Users\账号.android\avd\Galaxy_Nexus_API_31.avd下的multiinstance.lock文件即可。

模拟器黑屏问题解决

android模拟器在创建时,一般默认设置为热启动,所以每次关闭模拟器时,会提示保存当前运行界面状态,则下一次启动会以最近一次保存的状态启动显示。如果某次关闭时保存的状态异常,在下一次启动时,可能存在黑屏。

解决方法参考这里(改为冷启动)。

adb deviecs时显示的emulator-5554 offline如何解决

用netstat -ano | findstr 5555查看5555端口被哪个进程占用,杀掉该进程即可解决。
参考这里。

UI开发

不能在子线程里访问UI界面

子线程常见场景比如:回调。

子线程里访问UI程序会崩溃。要解决该问题,需使用Handler,将子线程里的UI访问请求通过消息队列提交给主线程。这点跟咱们早期开发MFC、delphi程序是类似的。

通知

样例代码:

findViewById(R.id.notify).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                //先创建通知信道,通知信道是有优先级别的
                NotificationChannel notificationChannel = new NotificationChannel(NOTIFY_CHAN_ID, "kobe-channel", NotificationManager.IMPORTANCE_HIGH);
                nm.createNotificationChannel(notificationChannel);
                //构建通知体
                Notification notification = new NotificationCompat.Builder(MainActivity.this, NOTIFY_CHAN_ID)
                        .setContentTitle("Information")
                        .setContentText("Receive your gift").setSmallIcon(R.mipmap.ic_launcher)
//                        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                        .build();
                //发送通知
                nm.notify(1, notification);
            }
        });

如需在点击通知时有响应动作,需使用setContentIntent函数来设置PendingIntent:

PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0, new Intent("com.example.action.prov_view"), 0);
                Notification notification = new NotificationCompat.Builder(MainActivity.this, NOTIFY_CHAN_ID)
                        .setContentTitle("Information")
                        .setContentText("Receive your gift").setSmallIcon(R.mipmap.ic_launcher)
                        .setContentIntent(pi)
                        .build();

PendingIntent就是对Intent的延迟包装。

常见开发错误

missing INTERNET permission

在AndroidManifest.xml中,需要进行如下配置:



//加入以下许可





not permitted by network security policy

原因:

由于 Android P(版本27以上) 限制了明文流量的网络请求,非加密的流量请求都会被系统禁止掉。 如果当前应用的请求是 htttp 请求,而非 https ,就会导系统禁止当前应用进行该请求 。

开发临时规避方法:

在res/xml目录下新建network_security_config.xml 文件:


<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
network-security-config>

在 AndroidManifest.xml application标签里增加配置

android:networkSecurityConfig="@xml/network_security_config" 

多activity

显式启动新的activity

从主activity启动从activity的显式方法:

startActivity(new Intent(MainActivity.this, ProvActivity.class));

显式是需要明确指定待启动的activity类名的,不够灵活,不便于扩展。于是有了隐式启动。

隐式启动新的activity

隐式启动activity要在AndroidManifest.xml里配置intent-filter标签,然后还是调用startActivity:

<activity android:name=".UI.UrlActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW">action>
                <category android:name="android.intent.category.DEFAULT">category>
                <data android:scheme="http">data>
            intent-filter>
        activity>

代码里这样写:

findViewById(R.id.browser).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //只需指定action即可,android平台会根据AndroidManifest.xml里的配置来自动匹配对应的activity,方便定制和扩展
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setData(Uri.parse("tel:10086"));
                startActivity(intent);
            }
        });

这里activity的action用的是系统预定义的android.intent.action.VIEW,也可以定义我们自己的action字符串。

生命周期

我们的例子中,MainActivity首次启动:

D/MainActivity: MainActivity.onCreate
D/MainActivity: MainActivity.onStart
D/MainActivity: MainActivity.onResume

打开新的activity时,MainActivity要pause+stop:

D/MainActivity: MainActivity.onPause
D/ProvActivity: ProvActivity.onCreate
D/ProvActivity: ProvActivity.onStart
D/ProvActivity: ProvActivity.onResume
D/MainActivity: MainActivity.onStop

按返回键,退出新activity,回到MainActivity:

D/ProvActivity: ProvActivity.onPause
D/MainActivity: MainActivity.onRestart
D/MainActivity: MainActivity.onStart
    MainActivity.onResume
D/ProvActivity: ProvActivity.onStop
D/ProvActivity: ProvActivity.onDestroy

这里注意,新activity ProvActivity销毁掉了。

总结一下,生命周期状态变化是:

Create -》 Start -》 Resume -》 Pause -》 Stop -》 Restart -》 Start -》 Resume -》 .... =》 Destroy

这里有个问题,activity在Stop状态时,是可能由于系统内存不足而被回收的。如果activity后续还要被用到,系统会再次把它Create出来。只是,这里就存在问题,如果之前activity里持有一些临时数据,这些临时数据就会丢失。为解决该问题,可以考虑在onSaveInstanceState里将临时数据存到Bundle里,然后再在onCreate方法里从Bundle里取出之前的数据。

activity切换机制

back stack栈,后进先出。
默认情况下,当我们启动一个Activity时,系统会创建一个Activity实例,并将其入栈;每按下一次back键就会有一个Activity出栈,直到栈空为止,当栈中无任何Activity时,系统会回收这个back stack。

fragment

Fragment是在3.0后引入的组件,由FragmentManager管理,可以由Activity自由控制,引入或者删除。
Fragment的能力跟活动是差不多的,它有自己独立的layout xml,里面可以放普通的android控件。相当于一个动态的activity。

fragment似乎不能和其他android控件一道放到activity的layout xml中,需要一个子layout包裹一层。

引入fragment的原因,应该是为了界面加载的动态性。像activity已经在AndroidManifest里配死了,而fragment没有这种限制。

fragment在显示到销毁的过程中会执行自己的生命周期。

onAttach(Activity activity) 
onCreate 
onCreateView 
onActivityCreate 
onStart 
onResume 
onPause 
onStop 
onDestroyView 
onDestroy 
onDetach

同时也收到activity生命周期的影响,activity onPause 会导致Fragment也会执行相应的onPause。
相比于activity,我们会看到fragment有个额外的attach和detach动作。

设置语言级别为java8

在build.gradle里加入:

compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }

AS使用Java8报错:Execution failed for task’:app:compileDebugJavaWithJavac’

问题解决方法:

首先,把AS升级到4.1.2;

接着,把gradle升级到6.5,修改gradle-wrapper.properties:

distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

最后,修改android gradle plugin的版本为4.1.0:

dependencies {
        classpath 'com.android.tools.build:gradle:4.1.0'
        ......
    }

android gradle plugin与gradle的版本对应关系参考这里

H5开发

H5其实就是混合APP(native app里面套个webview控件),相当于java+js的混合编程。纯粹的web app由于浏览器的限制,无法直接访问手机底层硬件,在混合APP里,则可通过JS Bridge层解决该问题。

H5的优点是跨平台,web技术栈容易上手;缺点是性能比原生差。如果对性能要求不高、或者同时有app和小程序的诉求,建议使用H5开发。

H5的详情参考这篇文章,讲的非常清楚。

微信小程序可以认为是基于微信生态的H5开发,只不过JS Bridge是微信提供的一批API。

很显然,H5开发要解决2个问题:

  • java如何调用js
  • js里如何调用java API

前者用webview的evaluateJavascript或loadUrl,后者可用addJavascriptInterface把java类对象注册到webview中去供js引擎访问。下面是例子:

// activity里就放了一个webview
public class WebActivity extends AppCompatActivity {

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

        WebView webView = (WebView) findViewById(R.id.webView);
        webView.getSettings().setJavaScriptEnabled(true);
        // 支持alert弹出窗口
        webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
        //注册一个名为jsBridge的对象到js引擎中
        webView.addJavascriptInterface(new NativeCaller(), "jsBridge");
        //执行一段js脚本,注意:要以javascript+冒号打头。hello函数的return值用回调来捕获。
        webView.evaluateJavascript("javascript:jsBridge.hello('just a test');", result -> {
            Log.i("WebActivity", "just test value:" + result);
        });
        //loadUrl可以加载资源文件或一个外部网址。
        webView.loadUrl("file:///android_asset/testJs.html");
        
        // 支持alert弹出窗口,必须设置WebChromeClient,否则前面的设置不生效
        webView.setWebChromeClient(new WebChromeClient());
    }
}

testJs.html这样写:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Carson</title>
    <script>
         function callAndroid(){
            // 这里访问java对象
            let v = jsBridge.hello("H5 call android");
            alert('length is ' + v);
         }
      </script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()">Hit Me!</button>
</body>
</html>

NativeCaller这样实现:

public class NativeCaller {
    private static final String TAG = "NativeCaller";

    //加一个JavascriptInterface注解,确保方法名能暴露到js引擎中
    @JavascriptInterface
    public int hello(String msg) {
        Log.i(TAG, "NativeCaller.hello:" + msg);
        return msg.length();
    }
}

远程访问服务器

使用okhttp3库。

public static void postHttpReq(String addr, Callback cbk) {
        OkHttpClient cli = new OkHttpClient();
        Request req = new Request.Builder().url(addr).build();
        cli.newCall(req).enqueue(cbk);
    }

运行apk

adb install -d -t ${APK路径}

APP测试

范围

整机角度:

操作系统分android、iOS、各厂家的系统

机型分手机、平板

手机的尺寸和分辨率也要考虑。

从APP角度:

与其它APP的兼容性
耗电情况

你可能感兴趣的:(android)