总结Android市场上架和疑难问题

访客 193 0

在Webview加载人脸认证时,如果缺少android.webkit.resource.VIDEO_CAPTURE权限,会导致崩溃。后来通过查阅相关API发现,该权限属于android.webkit.PermissionRequest中的一个用于访问web资源的权限。官方对此进行了如下定义:

该类定义了一个权限请求,用于在Web内容请求访问受保护资源时使用。相关的许可请求事件可以通过onPermissionRequest(PermissionRequest)onPermissionRequestCanceled(PermissionRequest)来处理。为了响应请求,必须在UI线程中调用grant()deny()。即使在较旧的Android版本上运行时,未来版本的WebView可能会请求新名称未在此处定义的受保护资源。为了避免无意中授予对新权限的请求,请将想要授予的特定权限传递给grant()方法。

我们需要在使用webview调用手机硬件功能(如相机、录制音视频等)之前,先授予相关权限。只有获得权限后,才能成功调用webview内的资源。以下是示例代码:

注意:   

请将super.onPermissionRequest(request);要注释掉改写为:需要将super.onPermissionRequest(request)这行代码注释掉。

在UI线程中使用runOnUiThread来授予权限。

Android的各个版本主要关注于适配性改进,每次更新都在不断减少用户对设备的控制权限,以提升产品的用户体验。同时,各应用市场也会根据相应的更新规则来限制不符合规范的应用上架,以维护市场生态和保持纯净。

在Android 6以下的版本中,用户拥有较高的权限。因此,大多数开发板都选择基于Android 5进行生产。

从Android 6(API 23)开始,权限管理得到了改进。对于敏感权限,如相机和存储等权限,需要进行动态申请。而对于普通权限,如网络等权限,则只需在清单文件中进行配置即可。

public class MainActivity extends AppCompatActivity {
    private static final int REQUEST_CODE = 100;
    //读写文件权限
    private static String[] PERMISSIONS_STORAGE = {android.permission.WRITE_EXTERNAL_STORAGE,
            android.permission.READ_EXTERNAL_STORAGE};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //android6以上需要动态申请
        if (Build.VERSION.SDK_INT >= 23) {
            checkPermission();
        }
    }

    private void checkPermission() {
        //权限是否授权存储权限
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            //申请权限
            ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_CODE);
 

如果应用需要在不同应用之间共享文件,必须进行FileProvider适配,否则可能导致应用崩溃。此外,使用file:// URI可能会引发FileUriExposedException异常。

在AndroidManifest.xml文件的app模块中,首先需要添加一个新的provider节点。

    
    

```

请注意,如果您选择使用第二种方法,请确保在 AndroidManifest.xml 文件的 application 标签中添加以下属性:

```xml


```

以上是对原始文本进行编辑的两种方式。根据您的需求和具体情况选择适合您的方式即可。 
<?xml version="1.0" encoding="utf-8"?><network-security-config>    <base-config cleartextTrafficPermitted="true">        <trust-anchors>            <!--trust system while release only-->            <certificates src="system" />        </trust-anchors>    </base-config></network-security-config>
<application    android:networkSecurityConfig="@xml/network_config"></application>

2.6 Android10(API29)

Android在外部存储设备中为每个应用提供了一个隔离存储沙盒,这意味着其他应用无法直接访问您应用的沙盒文件。此外,用户存储权限也会发生变更,包括分区存储等。

内部应用私有目录:无需申请权限,仅限本应用内访问。

获取缓存目录的方法是通过调用context.getCacheDir(),返回的路径为/data/data/包名/cache。

获取应用程序的文件目录路径可以使用context.getFilesDir()方法,该方法返回的是/data/data/包名/files。

外部存储目录的使用要求不仅包括存储权限,还需要获得所有文件管理权限。

Environment.getExternalStorageDirectory() refers to the path /storage/emulated/0.

外部公共目录:仅需存储权限。

在设备中,有许多标准路径可用于存储不同类型的文件,例如照片、视频和音乐。其中一个常见的路径是/storage/emulated/0/DCIM。另外,还可以使用Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)来获取相同的路径:/storage/emulated/0/DCIM。

访问文件的位置所需权限访问方法应用的私有目录无需权限即可访问getExternalFilesDir()其他应用的私有目录无,但目标文件要被其应用使用FileProvider标记为可共享文件通过ParcelFileDescriptor媒体文件目录(音频、照片、视频文件)READ_EXTERNAL_STORAGE仅当访问其他应用的文件时需要)MediaStore API下载目录无需权限即可访问存储访问框架SAF

2.7 强制分区存储 Android 11(API 30)

使用以下代码可以标记Android 10(API 29)使用旧版存储方式:

在Android 11(API 30)中,强制使用Scoped Storage(分区存储)作为存储方式。要在应用程序中启用旧版外部存储访问权限,请将以下代码添加到AndroidManifest.xml文件中的标签内:android:requestLegacyExternalStorage=true。 

2.8 Android 12 (API 31)

后台启动 Activity 的限制,应用处于后台时,无法启动Activity。比如启动页,广告页倒计时结束可能会从后台启动activity,这种情况google市场可能拒绝该应用上架

谷歌建议,在后台时,可以通过创建通知向用户提供信息。用户可以通过点击通知来启动Activity,而不是直接启动。

如果需要的话,还可以使用 setFullScreenIntent() 方法来突出显示这是一个需要立即处理的通知。

您可以参考Android 8中的通知适配方式,来处理后台启动activity时的通知。

2.9 版本的主要更新适配概览如下:从 6.0 升级到 13 的变化。

Android 6: 动态申请运行时权限 Android 7: 禁止在应用外公开 file:// URI,需使用FileProvider类共享文件 Android 8: 引入通知渠道,禁止后台应用启动后台服务,需通过startForegroundService()指定为前台服务。应用有五秒的时间来调用该 Service 的 startForeground() 方法以显示可见通知。 Android 9: 网络请求中要求使用https、支持刘海屏API Android 10: 定位权限、分区存储、限制后台启动Activity、深色主题 Android 11: 更新存储机制和权限更新机制,增加软件包可见性、前台服务类型和消息框的更新

Android 12:全新的应用启动页已经宣布为exported,新增了精确闹钟SCHEDULE_EXACT_ALARM权限和精确位置权限。此外,禁止从后台启动前台服务,并且搜索蓝牙功能不再需要位置权限。

Android 13的更新内容包括以下方面:细分了媒体权限,废弃了WebView中的setAppCacheEnabled和setForceDark方法,要求在注册静态广播时设置可见性,新增了通知权限POST_NOTIFICATIONS、Wi-Fi运行时权限NEARBY_WIFI_DEVICES以及身体传感器后台权限BODY_SENSORS_BACKGROUND。此外还隐藏了剪切板内容的API,并对非SDK接口进行限制。

三、谷歌市场被拒原因

自2021年起,Google和国内应用市场对未经用户告知的情况下收集用户信息进行了严格的管控。涉及用户隐私的功能必须明确向用户说明收集这些信息的目的,并让用户选择是否同意应用程序收集相关信息,其中包括但不限于位置、Mac地址、设备ID等个人信息。

解决方案:

首先,用户在首次安装和打开应用时,必须通过弹窗形式展示将要获取的权限和信息。用户需确认同意这些内容后方可进入应用首页并使用其功能。此外,在应用设置中必须提供二次查看用户协议和隐私政策的选项。

第二步:用户登录时需先浏览并勾选注册协议和隐私协议,用户选择即表示同意这些协议。

第三步:涉及到推荐时,对于敏感的广告内容,用户必须能够自由选择关闭。一般来说,我们会在设置中增加一个选项,让用户可以自行决定是否显示或关闭这些推荐内容。我们建议不推荐这些内容给用户。

从2021年开始,Google禁止使用HTTP协议,只允许使用安全协议HTTPS,以防止网络安全和WebView的漏洞被利用。

解决方案:

首先,我们需要增加对HTTPS的支持。

第二部分:改进WebView SSL错误处理程序,通过提醒方式来检查证书的有效性。以下是一个示例:

```java
class MyWebClient extends WebViewClient {
    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
        String message = SSL Certificate error.;
        switch (error.getPrimaryError()) {
            case SslError.SSL_UNTRUSTED:
                message = The certificate authority is not trusted.;
                break;
            case SslError.SSL_EXPIRED:
                message = The certificate has expired.;
                break;
            case SslError.SSL_IDMISMATCH:
                message = The certificate Hostname mismatch.;
                break;
            case SslError.SSL_NOTYETVALID:
                message = The certificate is not yet valid.;
                break;
            case SslError.SSL_DATE_INVALID:
                message = The date of the certificate is invalid.;
       

解决方案:

在第二部分中,提到了适配Android 10和Android 11后台启动活动的方法。这些方法通过通知方式提醒用户后台启动活动,或者在后台状态下禁止活动跳转,并进行拦截处理。当应用恢复到前台时,再重新进行跳转操作。

示例:用于判断前后台状态的案例

public class AppFrontBackHelper {    private OnAppStatusListener mOnAppStatusListener;    public AppFrontBackHelper() {    }    /**     * 注册状态监听,仅在Application中使用     *     * @param application     * @param listener     */    public void register(Application application, OnAppStatusListener listener) {        mOnAppStatusListener = listener;        application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks);    }    public void unRegister(Application application) {        application.unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks);    }    private Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {        //打开的Activity数量统计        private int activityStartCount = 0;        @Override        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {        }        @Override        public void onActivityStarted(Activity activity) {            activityStartCount++;            //数值从0变到1说明是从后台切到前台            if (activityStartCount == 1) {                //从后台切到前台                if (mOnAppStatusListener != null) {                    mOnAppStatusListener.onFront();                }            }        }        @Override        public void onActivityResumed(Activity activity) {        }        @Override        public void onActivityPaused(Activity activity) {        }        @Override        public void onActivityStopped(Activity activity) {            activityStartCount--;            //数值从1到0说明是从前台切到后台            if (activityStartCount == 0) {                //从前台切到后台                if (mOnAppStatusListener != null) {                    mOnAppStatusListener.onBack();                }            }        }        @Override        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {        }        @Override        public void onActivityDestroyed(Activity activity) {        }    };    public interface OnAppStatusListener {        void onFront();        void onBack();    }}

使用:

```
// 监听前后端状态
AppFrontBackHelper helper = new AppFrontBackHelper();
helper.register(this, new AppFrontBackHelper.OnAppStatusListener() {
    @Override
    public void onFront() {
        // 应用切到前台处理
        Log.e(aaa, 前台);
        Consts.ISBACKPROCESS = false;
    }
    
    @Override
    public void onBack() {
        // 应用切到后台处理
        Log.e(aaa, 后台);
        Consts.ISBACKPROCESS = true;
    }
});
```

从2021年8月3.4版本开始,新上架的应用必须使用aab格式的包进行上传,不再支持apk包。 

但又出现一个问题,上架aab时老提示app bundle 签名无效

总结Android市场上架和疑难问题-第1张图片-谷歌商店上架

解决方案:

1、这是非常重要的一点,在打包之前,请务必不要在项目的build.gradle文件中配置签名。如果你配置了签名,那么Google将无法识别你的签名。

选择aab打包方式即可完成打包。

3,上架选择google签名计划,让Google管理签名

4,然后就可以传aab包了,补充完相关信息

3.5 android.permission.REQUEST_INSTALL_PACKAGES 权限允许拒绝由安装第三方软件引起的情况

自2021年起,谷歌已实施了一项政策,禁止通过非官方渠道安装应用程序。用户只能通过谷歌市场更新应用程序。因此,清单文件中不再允许配置REQUEST_INSTALL_PACKAGES权限。

谷歌的拒绝回复原因如下:

总结Android市场上架和疑难问题-第2张图片-谷歌商店上架

 这样只能去掉该权限,如果强烈要求内部更新,可以通过其它方案,毕竟这个权限只是限制自动安装用的

方案一:点击更新后,将直接跳转至手机内置浏览器。您可以通过三方浏览器进行下载和安装。毕竟,Google只能限制自己的应用程序,并不能对其他应用程序进行限制。

方案二:由于无法进行自动安装,我们可以选择手动安装。首先,在应用内下载升级包到内存文件夹,然后手动找到对应的升级包并点击进行安装。

为了确保用户能够及时更新,可以采用以下两种方案的结合。首先,通过判断用户是否拥有浏览器来决定是否跳转至浏览器下载更新。如果用户有浏览器,则直接跳转至相应页面进行下载更新。其次,如果用户没有浏览器或者无法使用浏览器进行下载更新,则可以选择将更新文件下载到内存中,并手动进行更新操作。

//下载升级包到公共下载目录
private void downloadUpgradePackageToPublicDirectory(String url) {
    try {
        Intent intent = new Intent();
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(android.intent.action.VIEW);
        intent.setData(Uri.parse(url));
        activity.startActivity(intent);
        dismiss();
    } catch (Exception e) {
       //浏览器不存在
       downloadFaceFiles(url);
    }
} 
```java
// 跳转到Google市场
public void launchAppGoogle() {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(Uri.parse(https://play.google.com/store/apps/details?id= + activity.getPackageName()));
    activity.startActivity(intent);
}
```

四、TextView单词换行问题 

在布局中存在一个问题,即一行右侧有很多空白空间,但当遇到长数字或单词时会自动换行,导致界面显得非常不整齐。

总结Android市场上架和疑难问题-第3张图片-谷歌商店上架

修正后,可以正常换行,不会在根据单词数据折行

总结Android市场上架和疑难问题-第4张图片-谷歌商店上架

方法:有人在网上说可以在xml中设置android:breakStrategy=simple属性,但实际尝试后发现并不能解决问题。那么就只能自己编写一个控件来解决了。

public class AlignTextView extends android.support.v7.widget.AppCompatTextView {
    public AlignTextView(Context context) {
        super(context);
    }
    
    public AlignTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        Layout layout = getLayout();
        if (layout == null) return;
        
        final int lineCount = layout.getLineCount();
        
        if (lineCount 

标签: 权限 通知 后台 版本

发表评论 (已有0条评论)

还木有评论哦,快来抢沙发吧~