App复活

2024-02-10 05:40
文章标签 app 复活

本文主要是介绍App复活,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

APP保活系列(最高支持到Android 7.0)
(1) 探讨一种新型的双进程守护应用保活方法
(2) 探讨Android6.0及以上系统APP常驻内存(保活)实现-争宠篇
(3) 探讨Android6.0及以上系统APP常驻内存(保活)实现-复活篇


      APP常驻内存(保活),又旧事重提,距离上一次的研究亦有半年有余。最近,用户反馈说多进程守护方案在华为Mate8(7.0)保活效果不是很好,有时候还是不能及时收到消息,于是,又带着怀疑的眼光,重新找回原来的代码进行测试,顺便分析了市场上主流运动类APP保活方法(微信、手Q就算了,富人家的孩子,不具代表性),同时也对系统对内存中APP的管理规则进行了进一步探索。本文便是对最近一周的探索、学习、测试的总结之一,以备将来不时之需。

一、APP复活核心思想归纳
     随着AlarmManager唤醒、native进程拉起等方式的失效,APP常驻内存的时代将不复存在,尤其是当APP进程被杀死后,基本很难将其拉起。从用户的角度来讲,这是一种很好的发展,而这一切应该归功于谷歌和各大厂商不断追求良好的用户体验;从开发者的角度来说,尤其是即时通信类应用,这将是毁灭性打击。如果用户使用你的聊天软件,但在使用过程中总是不能及时收到对方的消息,那将是一种什么样的体验,因此,厂商"白名单"便应运而生了。正是因为如此,本文探讨的相关方案不可能保证在任何时候,或者任何机型能够唤醒,一切都是相对存在的。
    接下来,我们先了解下哪些情况下进程会被杀死:
   1. LowMemoryKiller:这种情况是触发了系统进程管理机制,通过系统会参照当前系统资源情况和oom_adj值来回收进程,oom_adj越大,越容易被杀死;
   2. 第三方清理软件:杀死oom_adj值大于4的进程,如果拥有root权限,理论可杀死所有进程;
   3. 厂商杀进程:可杀所有进程
   4. Force-stop:可杀所有非系统进程
二、APP复活方案探讨
1. 使用JobScheduler

     JobScheduler是谷歌在android 5.0引入的一个能够执行某项任务的API,它允许APP在将来达到一定条件时执行指定的任务。通常情况下,即使APP被强制停止,预定的任务仍然会被执行。JobScheduler工作原理:
     首先在一个实现了JobService的子类的onStartJob方法中执行这项任务,使用JobInfo的Builder方法来设定条件并和实现了JobService的子类的组件名绑定,然后调用系统服务JobScheduler的schedule方法。这样,即便在执行任务之前应用程序进程被杀,也不会导致任务不会执行,因为系统服务JobScheduler会使用bindServiceAsUser的方法把实现了JobService的子类服务启动起来,并执行它的onStartJob方法
(1) AliveJobService.Java

[java]  view plain copy
  1. /**JobService,支持5.0以上forcestop依然有效 
  2.  * 
  3.  * Created by jianddongguo on 2017/7/10. 
  4.  */  
  5. @TargetApi(21)  
  6. public class AliveJobService extends JobService {  
  7.     private final static String TAG = "KeepAliveService";  
  8.     // 告知编译器,这个变量不能被优化  
  9.     private volatile static Service mKeepAliveService = null;  
  10.   
  11.     public static boolean isJobServiceAlive(){  
  12.         return mKeepAliveService != null;  
  13.     }  
  14.   
  15.     private static final int MESSAGE_ID_TASK = 0x01;  
  16.   
  17.     private Handler mHandler = new Handler(new Handler.Callback() {  
  18.         @Override  
  19.         public boolean handleMessage(Message msg) {  
  20.             // 具体任务逻辑  
  21.             if(SystemUtils.isAPPALive(getApplicationContext(), Contants.PACKAGE_NAME)){  
  22.                 Toast.makeText(getApplicationContext(), "APP活着的", Toast.LENGTH_SHORT)  
  23.                         .show();  
  24.             }else{  
  25.                 Intent intent = new Intent(getApplicationContext(), SportsActivity.class);  
  26.                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  27.                 startActivity(intent);  
  28.                 Toast.makeText(getApplicationContext(), "APP被杀死,重启...", Toast.LENGTH_SHORT)  
  29.                         .show();  
  30.             }  
  31.             // 通知系统任务执行结束  
  32.             jobFinished( (JobParameters) msg.obj, false );  
  33.             return true;  
  34.         }  
  35.     });  
  36.   
  37.     @Override  
  38.     public boolean onStartJob(JobParameters params) {  
  39.         if(Contants.DEBUG)  
  40.             Log.d(TAG,"KeepAliveService----->JobService服务被启动...");  
  41.         mKeepAliveService = this;  
  42.         // 返回false,系统假设这个方法返回时任务已经执行完毕;  
  43.         // 返回true,系统假定这个任务正要被执行  
  44.         Message msg = Message.obtain(mHandler, MESSAGE_ID_TASK, params);  
  45.         mHandler.sendMessage(msg);  
  46.         return true;  
  47.     }  
  48.   
  49.   
  50.     @Override  
  51.     public boolean onStopJob(JobParameters params) {  
  52.         mHandler.removeMessages(MESSAGE_ID_TASK);  
  53.         if(Contants.DEBUG)  
  54.             Log.d(TAG,"KeepAliveService----->JobService服务被关闭");  
  55.         return false;  
  56.     }  
  57. }  

(2) JobSchedulerManager.java

[java]  view plain copy
  1. /**JobScheduler管理类,单例模式 
  2.  * 执行系统任务 
  3.  * 
  4.  * Created by jianddongguo on 2017/7/10. 
  5.  * http://blog.csdn.net/andrexpert 
  6.  */  
  7. public class JobSchedulerManager {  
  8.     private static final int JOB_ID = 1;  
  9.     private static JobSchedulerManager mJobManager;  
  10.     private JobScheduler mJobScheduler;  
  11.     private static Context mContext;  
  12.   
  13.     private JobSchedulerManager(Context ctxt){  
  14.         this.mContext = ctxt;  
  15.         mJobScheduler = (JobScheduler)ctxt.getSystemService(Context.JOB_SCHEDULER_SERVICE);  
  16.     }  
  17.     public final static JobSchedulerManager getJobSchedulerInstance(Context ctxt){  
  18.         if(mJobManager == null){  
  19.             mJobManager = new JobSchedulerManager(ctxt);  
  20.         }  
  21.         return mJobManager;  
  22.     }  
  23.     @TargetApi(21)  
  24.     public void startJobScheduler(){  
  25.         // 如果JobService已经启动或API<21,返回  
  26.         if(AliveJobService.isJobServiceAlive() || isBelowLOLLIPOP()){  
  27.             return;  
  28.         }  
  29.         // 构建JobInfo对象,传递给JobSchedulerService  
  30.         JobInfo.Builder builder = new JobInfo.Builder(JOB_ID,new ComponentName(mContext, AliveJobService.class));  
  31.         // 设置每3秒执行一下任务  
  32.         builder.setPeriodic(3000);  
  33.         // 设置设备重启时,执行该任务  
  34.         builder.setPersisted(true);  
  35.         // 当插入充电器,执行该任务  
  36.         builder.setRequiresCharging(true);  
  37.         JobInfo info = builder.build();  
  38.         //开始定时执行该系统任务  
  39.         mJobScheduler.schedule(info);  
  40.     }  
  41.     @TargetApi(21)  
  42.     public void stopJobScheduler(){  
  43.         if(isBelowLOLLIPOP())  
  44.             return;  
  45.         mJobScheduler.cancelAll();  
  46.     }  
  47.     private boolean isBelowLOLLIPOP(){  
  48.         // API< 21  
  49.         return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;  
  50.     }  
  51. }  

(3) AndroidManifest.xml
[html]  view plain copy
  1. <--! AliveJobService需要BIND_JOB_SERVICE权限-->  
  2.  <service  
  3.          android:name=".service.AliveJobService"  
  4.           android:permission="android.permission.BIND_JOB_SERVICE"/>  
Doze模式讲解:
     Doze,即休眠、打盹之意,是谷歌在Android M(6.0)提出为了延长电池使用寿命的一种节能方式,它的核心思想是在手机处于屏幕熄灭、不插电或静止不动一段时间后,手机会自动进入Doze模式,处于Doze模式的手机将停止所有非系统应用的WalkLocks、网络访问、闹钟、GPS/WIFI扫描、JobSheduler活动。当进入Doze模式的手机屏幕被点亮、移动或充电时,会立即从Doze模式恢复到正常,系统继续执行被Doze模式"冷冻"的各项活着。换而言之,Doze模式不会杀死进程,只是停止了进程相关的耗电活动,使其进入"休眠"状态。至Android N(7.0)后,谷歌进一步对Doze休眠机制进行了优化,休眠机制的应用场景和使用规则进行了扩展。Doze在Android 6.0中需要将手机平行放置一段时间才能开启,在7.0中则可随时开启。
      因此,对于Android 5.0,JobSheduler的唤醒是非常有效的;对于Android 6.0,虽然谷歌引入了Doze模式,但通常很难真正进入Doze模式,所以JobSheduler的唤醒依然有效;对于Android 7.0,JobSheduler的唤醒会有一定的影响,我们可以在电池管理中给APP开绿色通道,防止手机Doze模式后阻止APP使用JobSheduler功能。注:如果遇到深度定制机型,这就要看运气了...
(4) 测试结果
     三星C9(6.0):一键清理和强制停止(force stop)都能够唤醒APP;
     三星Note4(5.0):一键清理和强制停止(force stop)都能够唤醒APP;
    华为荣耀4X(6.0):一键清理和强制停止(force stop)都能够唤醒APP;
    华为Mate8(7.0):失效(可能被华为屏蔽了);

    

2. 华为推送SDK
      与个推、小米、极光推送类似,华为Push也是为开发者提供的一个消息推送平台,它建立了从云端到手机端的消息推送通道,支持上报标签、LBS信息、通知推送。换句话来说,就算我们的APP没有自己的服务器,也可以通过这些第三方推送平台,把最新消息及时地通知用户。
华为推送保活原理:华为推送服务以后台Service方式运行在一个独立进程里,主程序不需要常驻内存。当该后台Service接收到push消息后会以广播的方式通知主进程,触发相关回调接口。通常,被强制停止的APP无法接收到广播,但是华为push通道(即推送后台Service,它会常驻内存)能够强行将APP拉起来,这是因为其在发广播时利用了Intent.FLAG_INCLUDE_STOPPED_PACKAGES标记实现的。
(1) MyHwPushReceiver.java

[java]  view plain copy
  1. /** 华为接收器子类,用于接收推送消息 
  2.  * 
  3.  * Created by jianddongguo on 2017/7/10. 
  4.  * http://blog.csdn.net/andrexpert 
  5.  * 
  6.  */  
  7.   
  8.   
  9. public class MyHwPushReceiver extends PushEventReceiver{  
  10.     private final static String TAG = "MyHwPushReceiver";  
  11.   
  12.   
  13.     /** 
  14.      * pushToken申请成功后,会自动回调该方法 
  15.      * 应用通过该方法获取token(必须实现) 
  16.      * */  
  17.     @Override  
  18.     public void onToken(Context context, String token, Bundle bundle) {  
  19.         Log.i(TAG,"连接到华为推送服务器,token="+token);  
  20.     }  
  21.   
  22.   
  23.     /** 
  24.      *  推送消息下来时会自动回调onPushMsg方法 
  25.      *  用于接收透传消息(必须实现) 
  26.      * */  
  27.     @Override  
  28.     public boolean onPushMsg(Context context, byte[] msgBytes, Bundle bundle) {  
  29.         if(Contants.DEBUG){  
  30.             try {  
  31.                 Log.i(TAG,"接收透传消息:"+new String(msgBytes,"UTF-8"));  
  32.             } catch (UnsupportedEncodingException e) {  
  33.                 e.printStackTrace();  
  34.             }  
  35.         }  
  36.         // 启动应用  
  37.   
  38.   
  39.         return false;  
  40.     }  
  41.   
  42.   
  43.     /** 
  44.      * 连接状态的回调方法 
  45.      * */  
  46.     @Override  
  47.     public void onPushState(Context context, boolean connectState) {  
  48.         Log.i(TAG,"是否连接到华为推送服务器:"+(connectState?"connected":"disconnected"));  
  49.     }  
  50.   
  51.   
  52.     /** 
  53.      * 该方法会在设置标签、LBS信息之后,点击打开通知栏消息 
  54.      * 点击通知栏上的按钮之后被调用,由业务决定是否调用该函数。 
  55.      * Event类型: 
  56.      * NOTIFICATION_OPEMED,通知栏中的通知被点击打开 
  57.      * NOTIFICATION_CLICK_BTN,通知栏中通知上的按钮被点击 
  58.      * PLUGINRSP,标签上报回应 
  59.      * */  
  60.     @Override  
  61.     public void onEvent(Context context, Event event, Bundle bundle) {  
  62.         super.onEvent(context, event, bundle);  
  63.     }  
  64. }  
讲解一下:
    MyHwPushReceiver是华为接收器(com.huawei.android.pushagent.api.PushEventReceive)的子类,用于接收服务器传递过来的token,获取服务器连接状态,接收服务器推送过来的通知、透传等消息。需要注意的是,onToken方法、onPushMsg方法必须要实现,并且尽量不要在MyHwPushReceiver类中开启线程、处理Handler等。
(2) HwPushManager.java
[java]  view plain copy
  1. /**华为推送控制类 
  2.  * 
  3.  * Created by jianddongguo on 2017/7/15. 
  4.  * http://blog.csdn.net/andrexpert 
  5.  */  
  6.   
  7.   
  8. public class HwPushManager {  
  9.     private static HwPushManager mPushManager;  
  10.     private Context mContext;  
  11.   
  12.   
  13.     private HwPushManager(Context mContext){  
  14.         this.mContext = mContext;  
  15.     }  
  16.   
  17.   
  18.     public static  HwPushManager getInstance(Context mContext){  
  19.         if(mPushManager == null){  
  20.             mPushManager = new HwPushManager(mContext);  
  21.         }  
  22.         return mPushManager;  
  23.     }  
  24.   
  25.   
  26.     /**向服务器请求Token 
  27.      * 前提是已经在华为开发者联盟注册本应用,并申请、审核通过Push权益 
  28.      * */  
  29.     public void startRequestToken(){  
  30.         PushManager.requestToken(mContext);  
  31.     }  
  32.   
  33.   
  34.     /** 
  35.      * 是否接收服务器传递过来的透传消息 
  36.      * */  
  37.     public void isEnableReceiveNormalMsg(boolean isEnable){  
  38.         PushManager.enableReceiveNormalMsg(mContext,isEnable);  
  39.     }  
  40.   
  41.   
  42.     /** 
  43.      * 是否接收自呈现消息 
  44.      * */  
  45.     public  void isEnableReceiverNotifyMsg(boolean isEnable){  
  46.         PushManager.enableReceiveNotifyMsg(mContext,isEnable);  
  47.     }  
  48.   
  49.   
  50. }  
讲解一下:
a. 透传消息,即对于信息传输通道来说不会去关心透传消息的消息体格式及内容,它只是负责消息的传递,不对消息做任何处理,当客户端接收到透传消息后,由客户端自己来决定如何处理消息,比如默默在后台处理消息、以通知的方式向用户展示消息等,因此它弥补了通知栏消息客户端无法捕获到相关动作的不足。
b. 通知栏消息,即被推送的通知发送后会在客户端系统通知栏收到展现,同时响铃或振动提醒用户,但客户端无法捕捉到通知栏消息的相关动作,来做进一步的处理。
c. 富媒体消息,即随着信息技术的升级发展,在互联网上传播的信息,不仅只有文字或图片,同时还可以包括动画、视频、互动、音乐或语音效果等。
(3) AndroidManifest.xml
[html]  view plain copy
  1. <!-- 必需的权限 -->  
  2.   <uses-permission android:name="android.permission.INTERNET" />  
  3.   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  
  4.   <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />  
  5.   <uses-permission android:name="android.permission.READ_PHONE_STATE" />  
  6.   <uses-permission android:name="android.permission.WAKE_LOCK" />  
  7. <!-- 第三方相关 :接收Push消息(注册、Push消息、Push连接状态、标签,LBS上报结果)广播 -->  
  8.       <receiver android:name=".receiver.MyHwPushReceiver" >  
  9.           <intent-filter>  
  10.               <!-- 必须,用于接收token-->  
  11.               <action android:name="com.huawei.android.push.intent.REGISTRATION" />  
  12.               <!-- 必须,用于接收消息-->  
  13.               <action android:name="com.huawei.android.push.intent.RECEIVE" />  
  14.               <!-- 可选,用于点击通知栏或通知栏上的按钮后触发onEvent回调-->  
  15.               <action android:name="com.huawei.android.push.intent.CLICK" />  
  16.               <!-- 可选,查看push通道是否连接,不查看则不需要-->  
  17.               <action android:name="com.huawei.intent.action.PUSH_STATE" />  
  18.               <!-- 可选,标签、地理位置上报回应,不上报则不需要 -->  
  19.               <action android:name="com.huawei.android.push.plugin.RESPONSE" />  
  20.           </intent-filter>  
  21.           <meta-data android:name="CS_cloud_ablitity" android:value="@string/hwpush_ability_value"/>  
  22.       </receiver>  
  23.   
  24.   
  25.       <!-- 备注:Push相关的android组件需要添加到业务的AndroidManifest.xml,  
  26.          Push相关android组件运行在另外一个进程是为了防止Push服务异常而影响主业务 -->  
  27.       <!-- PushSDK:PushSDK接收外部请求事件入口 -->  
  28.       <receiver  
  29.           android:name="com.huawei.android.pushagent.PushEventReceiver"  
  30.           android:process=":pushservice" >  
  31.           <intent-filter>  
  32.            <action android:name="com.huawei.android.push.intent.REFRESH_PUSH_CHANNEL" />  
  33.               <action android:name="com.huawei.intent.action.PUSH" />  
  34.               <action android:name="com.huawei.intent.action.PUSH_ON" />  
  35.               <action android:name="com.huawei.android.push.PLUGIN" />  
  36.           </intent-filter>  
  37.           <intent-filter>  
  38.               <action android:name="android.intent.action.PACKAGE_ADDED" />  
  39.               <action android:name="android.intent.action.PACKAGE_REMOVED" />  
  40.   
  41.   
  42.               <data android:scheme="package" />  
  43.           </intent-filter>  
  44.       </receiver>  
  45.       <receiver  
  46.           android:name="com.huawei.android.pushagent.PushBootReceiver"  
  47.           android:process=":pushservice" >  
  48.           <intent-filter>  
  49.               <action android:name="com.huawei.android.push.intent.REGISTER" />  
  50.               <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />  
  51.           </intent-filter>  
  52.           <meta-data  
  53.               android:name="CS_cloud_version"  
  54.               android:value="\u0032\u0037\u0030\u0035" />  
  55.       </receiver>  
  56.   
  57.   
  58.       <!-- PushSDK:Push服务 -->  
  59.       <service  
  60.           android:name="com.huawei.android.pushagent.PushService"  
  61.           android:process=":pushservice" >  
  62.       </service>  
  63.   
  64.   
  65.       <!-- PushSDK:富媒体呈现页面,用于呈现服务器下发的富媒体消息 -->  
  66.       <!-- locale|layoutDirection 切换语言后不重新创建activity -->  
  67.       <activity  
  68.           android:name="com.huawei.android.pushselfshow.richpush.RichPushActivity"  
  69.           android:process=":pushservice"  
  70.           android:theme="@style/hwpush_NoActionBar"  
  71.           android:configChanges="orientation|screenSize|locale|layoutDirection"  
  72.           android:screenOrientation="portrait">  
  73.           <meta-data android:name="hwc-theme"  
  74.               android:value="androidhwext:style/Theme.Emui"/>  
  75.           <intent-filter>  
  76.               <action android:name="com.huawei.android.push.intent.RICHPUSH" />  
  77.   
  78.   
  79.               <category android:name="android.intent.category.DEFAULT" />  
  80.           </intent-filter>  
  81.       </activity>  
  82.       <activity  
  83.          android:name="com.huawei.android.pushselfshow.permission.RequestPermissionsActivity"  
  84.           android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar"  
  85.           android:launchMode="singleTop"  
  86.           android:screenOrientation="portrait"  
  87.           android:configChanges="orientation|screenSize|locale|layoutDirection"  
  88.           android:exported="false">  
  89.       </activity>  
讲解一下:
     这里除了MyHwPushReceiver类,其他直接从华为推送官方Demo拷贝即可。在测试过程中,需要连接到网络,然后我这里是通过华为开发者联盟网页中给KeepAppAlive发送透传消息的,用来测试华为推送保活的可行性。一般来说,我们都是在自己的服务器开一个定时器,定时推送透传消息到客户端。注:部分华为手机可能还需要开启自启动权限;如何集成华为推送SDK,直接看官方文档即可;非华为手机需要安装"华为手机服务.apk"才能使用华为推送,这个有点坑。
(4) 接收日志

(5) 效果演示

3. 第三方应用互相唤醒
      “ 探讨一种新型的双进程守护应用保活方法 ”就是利用第三方应用相互唤醒实现的,只是唤醒的方式比较 "坦率"。不会像腾讯、阿里全家桶那样 "委婉":当你启动其中的一个APP,该APP可能就会通过广播的方式偷偷唤醒它们这一个派系的其他APP,可谓是神不知鬼不觉。


Github项目地址:https://github.com/jiangdongguo/KeepingAppAlive


参考资料:
(1) Android之JobScheduler运行机制源码分析
(2) 安卓推送技术手册——使用透传消息的正确姿势
(3) 华为推送服务官网
(4) 流媒体、富媒体、多媒体到底有什么区别?
(5) 史上最全解析Android消息推送解决方案
(6) 华为推送接口文档
(7) Android集成华为推送的问题总结
(8) android接入华为推送注意点

这篇关于App复活的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/696256

相关文章

MFC中App,Doc,MainFrame,View各指针的互相获取

纸上得来终觉浅,为了熟悉获取方法,我建了个SDI。 首先说明这四个类的执行顺序是App->Doc->Main->View 另外添加CDialog类获得各个指针的方法。 多文档的获取有点小区别,有时间也总结一下。 //  App void CSDIApp::OnApp() {      //  App      //  Doc     CDocument *pD

ConstraintLayout布局里的一个属性app:layout_constraintDimensionRatio

ConstraintLayout 这是一个约束布局,可以尽可能的减少布局的嵌套。有一个属性特别好用,可以用来动态限制宽或者高app:layout_constraintDimensionRatio 关于app:layout_constraintDimensionRatio参数 app:layout_constraintDimensionRatio=“h,1:1” 表示高度height是动态变化

App Store最低版本要求汇总

1,自此日期起: 2024 年 4 月 29 日 自 2024 年 4 月 29 日起,上传到 App Store Connect 的 App 必须是使用 Xcode 15 为 iOS 17、iPadOS 17、Apple tvOS 17 或 watchOS 10 构建的 App。将 iOS App 提交至 App Store - Apple Developer 2,最低XCode版本 Xcod

鸿蒙自动化发布测试版本app

创建API客户端 API客户端是AppGallery Connect用于管理用户访问AppGallery Connect API的身份凭据,您可以给不同角色创建不同的API客户端,使不同角色可以访问对应权限的AppGallery Connect API。在访问某个API前,必须创建有权访问该API的API客户端。 1.登录AppGallery Connect网站,选择“用户与访问”。选择左侧

Xinstall助力App全渠道统计,参数传递下载提升用户体验!

在移动互联网时代,App已成为我们日常生活中不可或缺的一部分。然而,对于App开发者来说,如何有效地推广和运营自己的应用,却是一个不小的挑战。尤其是在面对众多渠道、复杂的数据统计和用户需求多样化的情况下,如何精准地触达目标用户,提升用户的下载、安装和活跃度,更是考验着每一个运营者的智慧。 今天,我们就来揭秘一个能够帮助App开发者解决这些痛点的神器——Xinstall。作为一家一站式App全渠道

Flask 创建app 时候传入的 static_folder 和 static_url_path参数理解

Flask 在创建app的时候 是用 app = Flask(__name__) 来创建的,不传入 static_folder参数的话 ,默认的静态文件的位置是在 static目录下 我们可以进入 Flask的源码里面查看 ctrl+鼠标左键进入 这是Flask的 __init__源码(后面还有一些,我就选了需要的代码)     def __init__(self,import_

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArra

这个错误说的是一个不可变数组负值给了一个可变的数组。有可能你前面定义的数组是一个可变数组,但是在你其他方法里面用他的时候,他就是一个不可变数组,因为在可变数组拿到别的地方用的时候,他会默认为不可变的,可能这只是一个类里面你只是简单的声明了他吧,并没有进行对他初始化,或者分配什么内存,所以他只是一个不可变的数组,当你在其他地方用他的时候,他就默认为不可变的数组,他可能因为你的没分配内存,而变回不可变

app提交到腾讯开发平台,提示无法获取签名信息,请上传有效包(110506)

最近提交APP时遇到的,一般情况下是因为打包时至勾选v2没有勾选v1的原因,如下图: 这个时候将v1勾选即可。 但是在打包时ˉv1和v2都勾选了也可能会出现这个报错,那就要看一下gradle的 minSdkVersion,如果这个版本在24-26之间也可能会提示这个错误,所以降低这个版本就可以了

基于Python的电商导购APP设计与实现

基于Python的电商导购APP设计与实现 Design and Implementation of an E-commerce Shopping Guide App based on Python 完整下载链接:基于Python的电商导购APP设计与实现 文章目录 基于Python的电商导购APP设计与实现摘要第一章 简介1.1 研究背景1.2 研究目的1.3 研究方法1.4 论文结

uni-app填坑指南——解决处理处理静态资源的问题

说实在话,这个标题其实有点夸大了。 uni-app并没有不解决,而是解决的不够充分不够彻底。这里我们来讨论一下uni-app在处理静态资源的问题上的一些不足之处。 1. 问题描述 在uni-app中,我们可以将静态资源放在static目录下,然后通过相对路径的方式引用。比如我们有一个图片资源logo.png,我们可以通过<img src="@/static/logo.png" />的方式引用。