因E2E开发和架构设计需要,对android要有基本的开发能力,结合项目,有一些总结,记录之。
步骤:
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。
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取不到。
android {
compileSdkVersion 31
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 19
targetSdkVersion 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
在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模拟器在创建时,一般默认设置为热启动,所以每次关闭模拟器时,会提示保存当前运行界面状态,则下一次启动会以最近一次保存的状态启动显示。如果某次关闭时保存的状态异常,在下一次启动时,可能存在黑屏。
解决方法参考这里(改为冷启动)。
用netstat -ano | findstr 5555查看5555端口被哪个进程占用,杀掉该进程即可解决。
参考这里。
子线程常见场景比如:回调。
子线程里访问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的延迟包装。
在AndroidManifest.xml中,需要进行如下配置:
//加入以下许可
原因:
由于 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的显式方法:
startActivity(new Intent(MainActivity.this, ProvActivity.class));
显式是需要明确指定待启动的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里取出之前的数据。
back stack栈,后进先出。
默认情况下,当我们启动一个Activity时,系统会创建一个Activity实例,并将其入栈;每按下一次back键就会有一个Activity出栈,直到栈空为止,当栈中无任何Activity时,系统会回收这个back stack。
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动作。
在build.gradle里加入:
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
问题解决方法:
首先,把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其实就是混合APP(native app里面套个webview控件),相当于java+js的混合编程。纯粹的web app由于浏览器的限制,无法直接访问手机底层硬件,在混合APP里,则可通过JS Bridge层解决该问题。
H5的优点是跨平台,web技术栈容易上手;缺点是性能比原生差。如果对性能要求不高、或者同时有app和小程序的诉求,建议使用H5开发。
H5的详情参考这篇文章,讲的非常清楚。
微信小程序可以认为是基于微信生态的H5开发,只不过JS Bridge是微信提供的一批API。
很显然,H5开发要解决2个问题:
前者用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);
}
adb install -d -t ${APK路径}
整机角度:
操作系统分android、iOS、各厂家的系统
机型分手机、平板
手机的尺寸和分辨率也要考虑。
从APP角度:
与其它APP的兼容性
耗电情况